mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
287 lines
8.1 KiB
Rust
287 lines
8.1 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("../README.md")]
|
|
#![deny(missing_docs)]
|
|
|
|
#[global_allocator]
|
|
static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
|
|
zalloc::ZeroizingAlloc(std::alloc::System);
|
|
|
|
use bitcoin_serai::rpc::Rpc as BRpc;
|
|
|
|
use ::primitives::task::{Task, ContinuallyRan};
|
|
|
|
mod primitives;
|
|
pub(crate) use crate::primitives::*;
|
|
|
|
// Internal utilities for scanning transactions
|
|
mod scan;
|
|
|
|
// App-logic trait satisfactions
|
|
mod key_gen;
|
|
use crate::key_gen::KeyGenParams;
|
|
mod rpc;
|
|
use rpc::Rpc;
|
|
mod scheduler;
|
|
use scheduler::{Planner, Scheduler};
|
|
|
|
// Our custom code for Bitcoin
|
|
mod db;
|
|
mod txindex;
|
|
use txindex::TxIndexTask;
|
|
|
|
pub(crate) fn hash_bytes(hash: bitcoin_serai::bitcoin::hashes::sha256d::Hash) -> [u8; 32] {
|
|
use bitcoin_serai::bitcoin::hashes::Hash;
|
|
|
|
let mut res = hash.to_byte_array();
|
|
res.reverse();
|
|
res
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let db = bin::init();
|
|
let feed = Rpc {
|
|
db: db.clone(),
|
|
rpc: loop {
|
|
match BRpc::new(bin::url()).await {
|
|
Ok(rpc) => break rpc,
|
|
Err(e) => {
|
|
log::error!("couldn't connect to the Bitcoin node: {e:?}");
|
|
tokio::time::sleep(core::time::Duration::from_secs(5)).await;
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
let (index_task, index_handle) = Task::new();
|
|
tokio::spawn(TxIndexTask(feed.clone()).continually_run(index_task, vec![]));
|
|
core::mem::forget(index_handle);
|
|
|
|
bin::main_loop::<(), _, KeyGenParams, _>(db, feed.clone(), Scheduler::new(Planner), feed).await;
|
|
}
|
|
|
|
/*
|
|
use bitcoin_serai::{
|
|
bitcoin::{
|
|
hashes::Hash as HashTrait,
|
|
key::{Parity, XOnlyPublicKey},
|
|
consensus::{Encodable, Decodable},
|
|
script::Instruction,
|
|
Transaction, Block, ScriptBuf,
|
|
opcodes::all::{OP_SHA256, OP_EQUALVERIFY},
|
|
},
|
|
wallet::{
|
|
tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError,
|
|
SignableTransaction as BSignableTransaction, TransactionMachine,
|
|
},
|
|
rpc::{RpcError, Rpc},
|
|
};
|
|
|
|
#[cfg(test)]
|
|
use bitcoin_serai::bitcoin::{
|
|
secp256k1::{SECP256K1, SecretKey, Message},
|
|
PrivateKey, PublicKey,
|
|
sighash::{EcdsaSighashType, SighashCache},
|
|
script::PushBytesBuf,
|
|
absolute::LockTime,
|
|
Amount as BAmount, Sequence, Script, Witness, OutPoint,
|
|
transaction::Version,
|
|
blockdata::transaction::{TxIn, TxOut},
|
|
};
|
|
|
|
use serai_client::{
|
|
primitives::{MAX_DATA_LEN, Coin, NetworkId, Amount, Balance},
|
|
networks::bitcoin::Address,
|
|
};
|
|
*/
|
|
|
|
/*
|
|
impl TransactionTrait<Bitcoin> for Transaction {
|
|
#[cfg(test)]
|
|
async fn fee(&self, network: &Bitcoin) -> u64 {
|
|
let mut value = 0;
|
|
for input in &self.input {
|
|
let output = input.previous_output;
|
|
let mut hash = *output.txid.as_raw_hash().as_byte_array();
|
|
hash.reverse();
|
|
value += network.rpc.get_transaction(&hash).await.unwrap().output
|
|
[usize::try_from(output.vout).unwrap()]
|
|
.value
|
|
.to_sat();
|
|
}
|
|
for output in &self.output {
|
|
value -= output.value.to_sat();
|
|
}
|
|
value
|
|
}
|
|
}
|
|
|
|
impl Bitcoin {
|
|
pub(crate) async fn new(url: String) -> Bitcoin {
|
|
let mut res = Rpc::new(url.clone()).await;
|
|
while let Err(e) = res {
|
|
log::error!("couldn't connect to Bitcoin node: {e:?}");
|
|
sleep(Duration::from_secs(5)).await;
|
|
res = Rpc::new(url.clone()).await;
|
|
}
|
|
Bitcoin { rpc: res.unwrap() }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) async fn fresh_chain(&self) {
|
|
if self.rpc.get_latest_block_number().await.unwrap() > 0 {
|
|
self
|
|
.rpc
|
|
.rpc_call(
|
|
"invalidateblock",
|
|
serde_json::json!([hex::encode(self.rpc.get_block_hash(1).await.unwrap())]),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
// This function panics on a node which doesn't follow the Bitcoin protocol, which is deemed fine
|
|
async fn median_fee(&self, block: &Block) -> Result<Fee, NetworkError> {
|
|
let mut fees = vec![];
|
|
if block.txdata.len() > 1 {
|
|
for tx in &block.txdata[1 ..] {
|
|
let mut in_value = 0;
|
|
for input in &tx.input {
|
|
let mut input_tx = input.previous_output.txid.to_raw_hash().to_byte_array();
|
|
input_tx.reverse();
|
|
in_value += self
|
|
.rpc
|
|
.get_transaction(&input_tx)
|
|
.await
|
|
.map_err(|_| NetworkError::ConnectionError)?
|
|
.output[usize::try_from(input.previous_output.vout).unwrap()]
|
|
.value
|
|
.to_sat();
|
|
}
|
|
let out = tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
|
|
fees.push((in_value - out) / u64::try_from(tx.vsize()).unwrap());
|
|
}
|
|
}
|
|
fees.sort();
|
|
let fee = fees.get(fees.len() / 2).copied().unwrap_or(0);
|
|
|
|
// The DUST constant documentation notes a relay rule practically enforcing a
|
|
// 1000 sat/kilo-vbyte minimum fee.
|
|
Ok(Fee(fee.max(1)))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn sign_btc_input_for_p2pkh(
|
|
tx: &Transaction,
|
|
input_index: usize,
|
|
private_key: &PrivateKey,
|
|
) -> ScriptBuf {
|
|
use bitcoin_serai::bitcoin::{Network as BNetwork, Address as BAddress};
|
|
|
|
let public_key = PublicKey::from_private_key(SECP256K1, private_key);
|
|
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
|
|
|
|
let mut der = SECP256K1
|
|
.sign_ecdsa_low_r(
|
|
&Message::from_digest_slice(
|
|
SighashCache::new(tx)
|
|
.legacy_signature_hash(
|
|
input_index,
|
|
&main_addr.script_pubkey(),
|
|
EcdsaSighashType::All.to_u32(),
|
|
)
|
|
.unwrap()
|
|
.to_raw_hash()
|
|
.as_ref(),
|
|
)
|
|
.unwrap(),
|
|
&private_key.inner,
|
|
)
|
|
.serialize_der()
|
|
.to_vec();
|
|
der.push(1);
|
|
|
|
ScriptBuf::builder()
|
|
.push_slice(PushBytesBuf::try_from(der).unwrap())
|
|
.push_key(&public_key)
|
|
.into_script()
|
|
}
|
|
}
|
|
|
|
impl Network for Bitcoin {
|
|
// 2 inputs should be 2 * 230 = 460 weight units
|
|
// The output should be ~36 bytes, or 144 weight units
|
|
// The overhead should be ~20 bytes at most, or 80 weight units
|
|
// 684 weight units, 171 vbytes, round up to 200
|
|
// 200 vbytes at 1 sat/weight (our current minimum fee, 4 sat/vbyte) = 800 sat fee for the
|
|
// aggregation TX
|
|
const COST_TO_AGGREGATE: u64 = 800;
|
|
|
|
#[cfg(test)]
|
|
async fn get_block_number(&self, id: &[u8; 32]) -> usize {
|
|
self.rpc.get_block_number(id).await.unwrap()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
async fn get_transaction_by_eventuality(&self, _: usize, id: &Eventuality) -> Transaction {
|
|
self.rpc.get_transaction(&id.0).await.unwrap()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
async fn mine_block(&self) {
|
|
use bitcoin_serai::bitcoin::{Network as BNetwork, Address as BAddress};
|
|
|
|
self
|
|
.rpc
|
|
.rpc_call::<Vec<String>>(
|
|
"generatetoaddress",
|
|
serde_json::json!([1, BAddress::p2sh(Script::new(), BNetwork::Regtest).unwrap()]),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
#[cfg(test)]
|
|
async fn test_send(&self, address: Address) -> Block {
|
|
use bitcoin_serai::bitcoin::{Network as BNetwork, Address as BAddress};
|
|
|
|
let secret_key = SecretKey::new(&mut rand_core::OsRng);
|
|
let private_key = PrivateKey::new(secret_key, BNetwork::Regtest);
|
|
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
|
|
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
|
|
|
|
let new_block = self.get_latest_block_number().await.unwrap() + 1;
|
|
self
|
|
.rpc
|
|
.rpc_call::<Vec<String>>("generatetoaddress", serde_json::json!([100, main_addr]))
|
|
.await
|
|
.unwrap();
|
|
|
|
let tx = self.get_block(new_block).await.unwrap().txdata.swap_remove(0);
|
|
let mut tx = Transaction {
|
|
version: Version(2),
|
|
lock_time: LockTime::ZERO,
|
|
input: vec![TxIn {
|
|
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
|
|
script_sig: Script::new().into(),
|
|
sequence: Sequence(u32::MAX),
|
|
witness: Witness::default(),
|
|
}],
|
|
output: vec![TxOut {
|
|
value: tx.output[0].value - BAmount::from_sat(10000),
|
|
script_pubkey: address.clone().into(),
|
|
}],
|
|
};
|
|
tx.input[0].script_sig = Self::sign_btc_input_for_p2pkh(&tx, 0, &private_key);
|
|
|
|
let block = self.get_latest_block_number().await.unwrap() + 1;
|
|
self.rpc.send_raw_transaction(&tx).await.unwrap();
|
|
for _ in 0 .. Self::CONFIRMATIONS {
|
|
self.mine_block().await;
|
|
}
|
|
self.get_block(block).await.unwrap()
|
|
}
|
|
}
|
|
*/
|