Analyze rawtx msg and add sp_tweak
This commit is contained in:
parent
eb6699baca
commit
2d044ec2c2
92
Cargo.lock
generated
92
Cargo.lock
generated
@ -26,6 +26,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -58,24 +64,43 @@ dependencies = [
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.10.0-beta"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
|
||||
|
||||
[[package]]
|
||||
name = "bimap"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd00f3c09b5f21fb357abe32d29946eb8bb7a0862bae62c0b5e4a692acbbe73c"
|
||||
dependencies = [
|
||||
"bech32",
|
||||
"bech32 0.10.0-beta",
|
||||
"bitcoin-internals",
|
||||
"bitcoin_hashes",
|
||||
"hex-conservative",
|
||||
"hex_lit",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -83,6 +108,9 @@ name = "bitcoin-internals"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
@ -92,6 +120,31 @@ checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
|
||||
dependencies = [
|
||||
"bitcoin-internals",
|
||||
"hex-conservative",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoincore-rpc"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eb70725a621848c83b3809913d5314c0d20ca84877d99dd909504b564edab00"
|
||||
dependencies = [
|
||||
"bitcoincore-rpc-json",
|
||||
"jsonrpc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoincore-rpc-json"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "856ffbee2e492c23bca715d72ea34aae80d58400f2bda26a82015d6bc2ec3662"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -386,6 +439,12 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-conservative"
|
||||
version = "0.1.1"
|
||||
@ -456,6 +515,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwalk"
|
||||
version = "0.8.1"
|
||||
@ -675,12 +745,16 @@ dependencies = [
|
||||
name = "sdk_relay"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitcoin",
|
||||
"bitcoincore-rpc",
|
||||
"bitcoincore-zmq",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"silentpayments",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
@ -693,7 +767,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"rand",
|
||||
"secp256k1-sys",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -756,6 +832,20 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "silentpayments"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/cygnet3/rust-silentpayments?branch=master#e915f5f8daef4b39ea32902963c143a5c0ed1746"
|
||||
dependencies = [
|
||||
"bech32 0.9.1",
|
||||
"bimap",
|
||||
"bitcoin_hashes",
|
||||
"hex",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -4,12 +4,16 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitcoin = { version = "0.31.1", features = ["serde"] }
|
||||
bitcoincore-rpc = { version = "0.18" }
|
||||
bitcoincore-zmq = "1.4.0"
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.28", default-features = false, features = ["sink", "std"] }
|
||||
log = "0.4.20"
|
||||
serde = { version = "1.0.193", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
silentpayments = { git = "https://github.com/cygnet3/rust-silentpayments", branch = "master", features = ['utils'] }
|
||||
tokio = { version = "1.0.0", features = ["io-util", "rt-multi-thread", "macros", "sync"] }
|
||||
tokio-stream = "0.1"
|
||||
tokio-tungstenite = "0.21.0"
|
||||
|
304
src/daemon.rs
Normal file
304
src/daemon.rs
Normal file
@ -0,0 +1,304 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use bitcoin::{consensus::deserialize, hashes::hex::FromHex};
|
||||
use bitcoin::{Amount, BlockHash, Transaction, Txid};
|
||||
use bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi};
|
||||
// use crossbeam_channel::Receiver;
|
||||
// use parking_lot::Mutex;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct SensitiveAuth(pub Auth);
|
||||
|
||||
impl SensitiveAuth {
|
||||
pub(crate) fn get_auth(&self) -> Auth {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
enum PollResult {
|
||||
Done(Result<()>),
|
||||
Retry,
|
||||
}
|
||||
|
||||
fn rpc_poll(client: &mut Client, skip_block_download_wait: bool) -> PollResult {
|
||||
match client.get_blockchain_info() {
|
||||
Ok(info) => {
|
||||
if skip_block_download_wait {
|
||||
// bitcoind RPC is available, don't wait for block download to finish
|
||||
return PollResult::Done(Ok(()));
|
||||
}
|
||||
let left_blocks = info.headers - info.blocks;
|
||||
if info.initial_block_download || left_blocks > 0 {
|
||||
log::info!(
|
||||
"waiting for {} blocks to download{}",
|
||||
left_blocks,
|
||||
if info.initial_block_download {
|
||||
" (IBD)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
return PollResult::Retry;
|
||||
}
|
||||
PollResult::Done(Ok(()))
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(e) = extract_bitcoind_error(&err) {
|
||||
if e.code == -28 {
|
||||
log::debug!("waiting for RPC warmup: {}", e.message);
|
||||
return PollResult::Retry;
|
||||
}
|
||||
}
|
||||
PollResult::Done(Err(err).context("daemon not available"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_cookie(path: &Path) -> Result<(String, String)> {
|
||||
// Load username and password from bitcoind cookie file:
|
||||
// * https://github.com/bitcoin/bitcoin/pull/6388/commits/71cbeaad9a929ba6a7b62d9b37a09b214ae00c1a
|
||||
// * https://bitcoin.stackexchange.com/questions/46782/rpc-cookie-authentication
|
||||
let mut file = File::open(path)
|
||||
.with_context(|| format!("failed to open bitcoind cookie file: {}", path.display()))?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.with_context(|| format!("failed to read bitcoind cookie from {}", path.display()))?;
|
||||
|
||||
let parts: Vec<&str> = contents.splitn(2, ':').collect();
|
||||
anyhow::ensure!(
|
||||
parts.len() == 2,
|
||||
"failed to parse bitcoind cookie - missing ':' separator"
|
||||
);
|
||||
Ok((parts[0].to_owned(), parts[1].to_owned()))
|
||||
}
|
||||
|
||||
fn rpc_connect() -> Result<Client> {
|
||||
let rpc_url = "http://127.0.0.1:39332";
|
||||
// Allow `wait_for_new_block` to take a bit longer before timing out.
|
||||
// See https://github.com/romanz/electrs/issues/495 for more details.
|
||||
let builder = jsonrpc::simple_http::SimpleHttpTransport::builder()
|
||||
.url(&rpc_url)?
|
||||
.timeout(Duration::from_secs(30));
|
||||
let daemon_auth = SensitiveAuth(Auth::CookieFile(PathBuf::from_str("/home/sosthene/.bitcoin/signet/.cookie").unwrap()));
|
||||
let builder = match daemon_auth.get_auth() {
|
||||
Auth::None => builder,
|
||||
Auth::UserPass(user, pass) => builder.auth(user, Some(pass)),
|
||||
Auth::CookieFile(path) => {
|
||||
let (user, pass) = read_cookie(&path)?;
|
||||
builder.auth(user, Some(pass))
|
||||
}
|
||||
};
|
||||
Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport(
|
||||
builder.build(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub struct Daemon {
|
||||
// p2p: Mutex<Connection>,
|
||||
rpc: Client,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
pub(crate) fn connect(
|
||||
// config: &Config,
|
||||
// exit_flag: &ExitFlag,
|
||||
// metrics: &Metrics,
|
||||
) -> Result<Self> {
|
||||
let mut rpc = rpc_connect()?;
|
||||
|
||||
loop {
|
||||
match rpc_poll(&mut rpc, true) {
|
||||
PollResult::Done(result) => {
|
||||
result.context("bitcoind RPC polling failed")?;
|
||||
break; // on success, finish polling
|
||||
}
|
||||
PollResult::Retry => {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait a bit before polling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let network_info = rpc.get_network_info()?;
|
||||
// if network_info.version < 21_00_00 {
|
||||
// bail!("electrs requires bitcoind 0.21+");
|
||||
// }
|
||||
if !network_info.network_active {
|
||||
anyhow::bail!("electrs requires active bitcoind p2p network");
|
||||
}
|
||||
let info = rpc.get_blockchain_info()?;
|
||||
if info.pruned {
|
||||
anyhow::bail!("electrs requires non-pruned bitcoind node");
|
||||
}
|
||||
|
||||
// let p2p = tokio::sync::Mutex::new(Connection::connect(
|
||||
// config.network,
|
||||
// config.daemon_p2p_addr,
|
||||
// metrics,
|
||||
// config.signet_magic,
|
||||
// )?);
|
||||
Ok(Self { rpc })
|
||||
}
|
||||
|
||||
pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result<Option<Amount>> {
|
||||
Ok(self
|
||||
.rpc
|
||||
.estimate_smart_fee(nblocks, None)
|
||||
.context("failed to estimate fee")?
|
||||
.fee_rate)
|
||||
}
|
||||
|
||||
pub(crate) fn get_relay_fee(&self) -> Result<Amount> {
|
||||
Ok(self
|
||||
.rpc
|
||||
.get_network_info()
|
||||
.context("failed to get relay fee")?
|
||||
.relay_fee)
|
||||
}
|
||||
|
||||
pub(crate) fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||
self.rpc
|
||||
.send_raw_transaction(tx)
|
||||
.context("failed to broadcast transaction")
|
||||
}
|
||||
|
||||
pub(crate) fn get_transaction_info(
|
||||
&self,
|
||||
txid: &Txid,
|
||||
blockhash: Option<BlockHash>,
|
||||
) -> Result<Value> {
|
||||
// No need to parse the resulting JSON, just return it as-is to the client.
|
||||
self.rpc
|
||||
.call(
|
||||
"getrawtransaction",
|
||||
&[json!(txid), json!(true), json!(blockhash)],
|
||||
)
|
||||
.context("failed to get transaction info")
|
||||
}
|
||||
|
||||
pub(crate) fn get_transaction_hex(
|
||||
&self,
|
||||
txid: &Txid,
|
||||
blockhash: Option<BlockHash>,
|
||||
) -> Result<Value> {
|
||||
use bitcoin::consensus::serde::{hex::Lower, Hex, With};
|
||||
|
||||
let tx = self.get_transaction(txid, blockhash)?;
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(transparent)]
|
||||
struct TxAsHex(#[serde(with = "With::<Hex<Lower>>")] Transaction);
|
||||
serde_json::to_value(TxAsHex(tx)).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) fn get_transaction(
|
||||
&self,
|
||||
txid: &Txid,
|
||||
blockhash: Option<BlockHash>,
|
||||
) -> Result<Transaction> {
|
||||
self.rpc
|
||||
.get_raw_transaction(txid, blockhash.as_ref())
|
||||
.context("failed to get transaction")
|
||||
}
|
||||
|
||||
pub(crate) fn get_block_txids(&self, blockhash: BlockHash) -> Result<Vec<Txid>> {
|
||||
Ok(self
|
||||
.rpc
|
||||
.get_block_info(&blockhash)
|
||||
.context("failed to get block txids")?
|
||||
.tx)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mempool_txids(&self) -> Result<Vec<Txid>> {
|
||||
self.rpc
|
||||
.get_raw_mempool()
|
||||
.context("failed to get mempool txids")
|
||||
}
|
||||
|
||||
pub(crate) fn get_mempool_entries(
|
||||
&self,
|
||||
txids: &[Txid],
|
||||
) -> Result<Vec<Result<json::GetMempoolEntryResult>>> {
|
||||
let client = self.rpc.get_jsonrpc_client();
|
||||
log::debug!("getting {} mempool entries", txids.len());
|
||||
let args: Vec<_> = txids
|
||||
.iter()
|
||||
.map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()])
|
||||
.collect();
|
||||
let reqs: Vec<_> = args
|
||||
.iter()
|
||||
.map(|a| client.build_request("getmempoolentry", a))
|
||||
.collect();
|
||||
let res = client.send_batch(&reqs).context("batch request failed")?;
|
||||
log::debug!("got {} mempool entries", res.len());
|
||||
Ok(res
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
r.context("missing response")?
|
||||
.result::<json::GetMempoolEntryResult>()
|
||||
.context("invalid response")
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn get_mempool_transactions(
|
||||
&self,
|
||||
txids: &[Txid],
|
||||
) -> Result<Vec<Result<Transaction>>> {
|
||||
let client = self.rpc.get_jsonrpc_client();
|
||||
log::debug!("getting {} transactions", txids.len());
|
||||
let args: Vec<_> = txids
|
||||
.iter()
|
||||
.map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()])
|
||||
.collect();
|
||||
let reqs: Vec<_> = args
|
||||
.iter()
|
||||
.map(|a| client.build_request("getrawtransaction", a))
|
||||
.collect();
|
||||
let res = client.send_batch(&reqs).context("batch request failed")?;
|
||||
log::debug!("got {} mempool transactions", res.len());
|
||||
Ok(res
|
||||
.into_iter()
|
||||
.map(|r| -> Result<Transaction> {
|
||||
let tx_hex = r
|
||||
.context("missing response")?
|
||||
.result::<String>()
|
||||
.context("invalid response")?;
|
||||
let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?;
|
||||
deserialize(&tx_bytes).context("invalid transaction")
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result<Vec<NewHeader>> {
|
||||
// self.p2p.lock().get_new_headers(chain)
|
||||
// }
|
||||
|
||||
// pub(crate) fn for_blocks<B, F>(&self, blockhashes: B, func: F) -> Result<()>
|
||||
// where
|
||||
// B: IntoIterator<Item = BlockHash>,
|
||||
// F: FnMut(BlockHash, SerBlock),
|
||||
// {
|
||||
// self.p2p.lock().for_blocks(blockhashes, func)
|
||||
// }
|
||||
|
||||
// pub(crate) fn new_block_notification(&self) -> Receiver<()> {
|
||||
// self.p2p.lock().new_block_notification()
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) type RpcError = bitcoincore_rpc::jsonrpc::error::RpcError;
|
||||
|
||||
pub(crate) fn extract_bitcoind_error(err: &bitcoincore_rpc::Error) -> Option<&RpcError> {
|
||||
use bitcoincore_rpc::{
|
||||
jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError,
|
||||
};
|
||||
match err {
|
||||
JsonRpcError(ServerError(e)) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
98
src/main.rs
98
src/main.rs
@ -6,6 +6,7 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use bitcoin::{consensus::deserialize, secp256k1::PublicKey};
|
||||
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
||||
|
||||
use log::{debug, error};
|
||||
@ -14,27 +15,16 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
|
||||
mod sp;
|
||||
mod daemon;
|
||||
|
||||
use crate::daemon::Daemon;
|
||||
use crate::sp::VinData;
|
||||
|
||||
type Tx = UnboundedSender<Message>;
|
||||
|
||||
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;
|
||||
|
||||
// #[derive(Deserialize, Serialize, Debug)]
|
||||
// struct AnkMessage {
|
||||
// dest: SilentPaymentId,
|
||||
// payload: String
|
||||
// }
|
||||
|
||||
// impl TryFrom<Message> for AnkMessage {
|
||||
// type Error = serde_json::Error;
|
||||
|
||||
// fn try_from(value: Message) -> Result<Self, Self::Error> {
|
||||
// match value {
|
||||
// Message::Text(s) => serde_json::from_str(&s),
|
||||
// _ => Err(serde_json::Error::custom("Unsupported message type")),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: SocketAddr) {
|
||||
debug!("Incoming TCP connection from: {}", addr);
|
||||
|
||||
@ -82,7 +72,17 @@ async fn handle_connection(peer_map: PeerMap, raw_stream: TcpStream, addr: Socke
|
||||
peer_map.lock().unwrap().remove(&addr);
|
||||
}
|
||||
|
||||
async fn handle_zmq(peer_map: PeerMap) {
|
||||
fn flatten_msg(parts: &[Vec<u8>]) -> Vec<u8> {
|
||||
let total_len = parts.iter().fold(0, |acc, v| acc + v.len());
|
||||
let mut final_vec = Vec::with_capacity(total_len);
|
||||
for p in parts {
|
||||
final_vec.extend(p);
|
||||
}
|
||||
|
||||
final_vec
|
||||
}
|
||||
|
||||
async fn handle_zmq(peer_map: PeerMap, daemon: Daemon) {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
debug!("Starting listening on Core");
|
||||
for msg in bitcoincore_zmq::subscribe_receiver(&["tcp://127.0.0.1:29000"]).unwrap() {
|
||||
@ -91,11 +91,60 @@ async fn handle_zmq(peer_map: PeerMap) {
|
||||
debug!("Received a {} message", core_msg.topic_str());
|
||||
let peers = peer_map.lock().unwrap();
|
||||
|
||||
let vectors = core_msg.serialize_to_vecs();
|
||||
let payload: Vec<u8>;
|
||||
match core_msg.topic_str() {
|
||||
"rawtx" => {
|
||||
let tx: bitcoin::Transaction = deserialize(&core_msg.serialize_data_to_vec()).unwrap();
|
||||
|
||||
let mut payload: Vec<u8> = Vec::with_capacity(vectors[0].len() + vectors[1].len() + vectors[2].len());
|
||||
for v in vectors {
|
||||
payload.extend(v);
|
||||
if tx.is_coinbase() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
|
||||
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
|
||||
for input in tx.input {
|
||||
outpoints.push((input.previous_output.txid.to_string(), input.previous_output.vout));
|
||||
let prev_tx = daemon.get_transaction(&input.previous_output.txid, None).unwrap();
|
||||
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
|
||||
let vin_data = VinData {
|
||||
script_sig: input.script_sig.to_bytes().to_vec(),
|
||||
txinwitness: input.witness.to_vec(),
|
||||
script_pub_key: output.script_pubkey.to_bytes()
|
||||
};
|
||||
match sp::get_pubkey_from_input(&vin_data) {
|
||||
Ok(res) => {
|
||||
if let Some(pubkey) = res {
|
||||
pubkeys.push(pubkey);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Can't extract pubkey from input: {}", e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Transaction with a non existing input");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
|
||||
match silentpayments::utils::receiving::recipient_calculate_tweak_data(&input_pub_keys, &outpoints) {
|
||||
Ok(partial_tweak) => {
|
||||
let mut vecs = core_msg.serialize_to_vecs().to_vec();
|
||||
vecs.push(partial_tweak.serialize().to_vec());
|
||||
payload = flatten_msg(&vecs);
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to compute tweak data: {}", e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
payload = flatten_msg(&core_msg.serialize_to_vecs());
|
||||
}
|
||||
}
|
||||
|
||||
for tx in peers.values() {
|
||||
@ -121,8 +170,11 @@ async fn main() -> Result<(), IoError> {
|
||||
|
||||
let state = PeerMap::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// Connect the rpc daemon
|
||||
let daemon = Daemon::connect().unwrap();
|
||||
|
||||
// Subscribe to Bitcoin Core
|
||||
tokio::spawn(handle_zmq(state.clone()));
|
||||
tokio::spawn(handle_zmq(state.clone(), daemon));
|
||||
|
||||
// Create the event loop and TCP listener we'll accept connections on.
|
||||
let try_socket = TcpListener::bind(&addr).await;
|
||||
|
155
src/sp.rs
Normal file
155
src/sp.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use bitcoin::secp256k1::{Parity::Even, PublicKey, XOnlyPublicKey};
|
||||
use bitcoin::hashes::{hash160, Hash};
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
// ** Putting all the pubkey extraction logic in the test utils for now. **
|
||||
// NUMS_H (defined in BIP340)
|
||||
const NUMS_H: [u8; 32] = [
|
||||
0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e,
|
||||
0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0,
|
||||
];
|
||||
|
||||
// Define OP_CODES used in script template matching for readability
|
||||
const OP_1: u8 = 0x51;
|
||||
const OP_0: u8 = 0x00;
|
||||
const OP_PUSHBYTES_20: u8 = 0x14;
|
||||
const OP_PUSHBYTES_32: u8 = 0x20;
|
||||
const OP_HASH160: u8 = 0xA9;
|
||||
const OP_EQUAL: u8 = 0x87;
|
||||
const OP_DUP: u8 = 0x76;
|
||||
const OP_EQUALVERIFY: u8 = 0x88;
|
||||
const OP_CHECKSIG: u8 = 0xAC;
|
||||
|
||||
// Only compressed pubkeys are supported for silent payments
|
||||
const COMPRESSED_PUBKEY_SIZE: usize = 33;
|
||||
|
||||
pub struct VinData {
|
||||
pub script_sig: Vec<u8>,
|
||||
pub txinwitness: Vec<Vec<u8>>,
|
||||
pub script_pub_key: Vec<u8>,
|
||||
}
|
||||
|
||||
// script templates for inputs allowed in BIP352 shared secret derivation
|
||||
pub fn is_p2tr(spk: &[u8]) -> bool {
|
||||
matches!(spk, [OP_1, OP_PUSHBYTES_32, ..] if spk.len() == 34)
|
||||
}
|
||||
|
||||
fn is_p2wpkh(spk: &[u8]) -> bool {
|
||||
matches!(spk, [OP_0, OP_PUSHBYTES_20, ..] if spk.len() == 22)
|
||||
}
|
||||
|
||||
fn is_p2sh(spk: &[u8]) -> bool {
|
||||
matches!(spk, [OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUAL] if spk.len() == 23)
|
||||
}
|
||||
|
||||
fn is_p2pkh(spk: &[u8]) -> bool {
|
||||
matches!(spk, [OP_DUP, OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUALVERIFY, OP_CHECKSIG] if spk.len() == 25)
|
||||
}
|
||||
|
||||
pub fn get_pubkey_from_input(vin: &VinData) -> Result<Option<PublicKey>, Error> {
|
||||
if is_p2pkh(&vin.script_pub_key) {
|
||||
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
|
||||
(true, false) => {
|
||||
let spk_hash = &vin.script_pub_key[3..23];
|
||||
for i in (COMPRESSED_PUBKEY_SIZE..=vin.script_sig.len()).rev() {
|
||||
if let Some(pubkey_bytes) = &vin.script_sig.get(i - COMPRESSED_PUBKEY_SIZE..i) {
|
||||
let pubkey_hash = hash160::Hash::hash(pubkey_bytes);
|
||||
if pubkey_hash.to_byte_array() == spk_hash {
|
||||
return Ok(Some(PublicKey::from_slice(pubkey_bytes)?));
|
||||
}
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, true) => return Err(Error::msg("Empty script_sig for spending a p2pkh")),
|
||||
(false, _) => return Err(Error::msg("non empty witness for spending a p2pkh")),
|
||||
}
|
||||
} else if is_p2sh(&vin.script_pub_key) {
|
||||
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
|
||||
(false, false) => {
|
||||
let redeem_script = &vin.script_sig[1..];
|
||||
if is_p2wpkh(redeem_script) {
|
||||
if let Some(value) = vin.txinwitness.last() {
|
||||
if let Ok(pubkey) = PublicKey::from_slice(value) {
|
||||
return Ok(Some(pubkey));
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, true) => {
|
||||
return Err(Error::msg(
|
||||
"Empty script_sig for spending a p2sh".to_owned(),
|
||||
))
|
||||
}
|
||||
(true, false) => {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
} else if is_p2wpkh(&vin.script_pub_key) {
|
||||
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
|
||||
(false, true) => {
|
||||
if let Some(value) = vin.txinwitness.last() {
|
||||
if let Ok(pubkey) = PublicKey::from_slice(value) {
|
||||
return Ok(Some(pubkey));
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
return Err(Error::msg("Empty witness".to_owned()));
|
||||
}
|
||||
}
|
||||
(_, false) => {
|
||||
return Err(Error::msg(
|
||||
"Non empty script sig for spending a segwit output".to_owned(),
|
||||
))
|
||||
}
|
||||
(true, _) => {
|
||||
return Err(Error::msg(
|
||||
"Empty witness for spending a segwit output".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else if is_p2tr(&vin.script_pub_key) {
|
||||
match (&vin.txinwitness.is_empty(), &vin.script_sig.is_empty()) {
|
||||
(false, true) => {
|
||||
// check for the optional annex
|
||||
let annex = match vin.txinwitness.last().and_then(|value| value.get(0)) {
|
||||
Some(&0x50) => 1,
|
||||
Some(_) => 0,
|
||||
None => return Err(Error::msg("Empty or invalid witness".to_owned())),
|
||||
};
|
||||
|
||||
// Check for script path
|
||||
let stack_size = vin.txinwitness.len();
|
||||
if stack_size > annex && vin.txinwitness[stack_size - annex - 1][1..33] == NUMS_H {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Return the pubkey from the script pubkey
|
||||
return XOnlyPublicKey::from_slice(&vin.script_pub_key[2..34])
|
||||
.map_err(|e| Error::new(e))
|
||||
.map(|x_only_public_key| {
|
||||
Some(PublicKey::from_x_only_public_key(x_only_public_key, Even))
|
||||
});
|
||||
}
|
||||
(_, false) => {
|
||||
return Err(Error::msg(
|
||||
"Non empty script sig for spending a segwit output".to_owned(),
|
||||
))
|
||||
}
|
||||
(true, _) => {
|
||||
return Err(Error::msg(
|
||||
"Empty witness for spending a segwit output".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't support this kind of output
|
||||
return Err(Error::msg("Unsupported script pubkey type"));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user