diff --git a/teos-common/build.rs b/teos-common/build.rs index c132feb..52c390f 100644 --- a/teos-common/build.rs +++ b/teos-common/build.rs @@ -7,6 +7,9 @@ fn main() -> Result<(), Box> { .field_attribute("user_id", "#[serde(with = \"hex::serde\")]") .field_attribute("locator", "#[serde(with = \"hex::serde\")]") .field_attribute("encrypted_blob", "#[serde(with = \"hex::serde\")]") + .field_attribute("dispute_txid", "#[serde(with = \"crate::ser::serde_be\")]") + .field_attribute("penalty_txid", "#[serde(with = \"crate::ser::serde_be\")]") + .field_attribute("penalty_rawtx", "#[serde(with = \"hex::serde\")]") .field_attribute( "GetAppointmentResponse.status", "#[serde(with = \"crate::ser::serde_status\")]", diff --git a/teos-common/src/ser.rs b/teos-common/src/ser.rs index 84879d2..663ff66 100644 --- a/teos-common/src/ser.rs +++ b/teos-common/src/ser.rs @@ -15,9 +15,50 @@ where seq.end() } -pub mod serde_status { +pub mod serde_be { + use super::*; + use serde::de::{self, Deserializer}; + + pub fn serialize(v: &[u8], s: S) -> Result + where + S: Serializer, + { + let mut v = v.to_owned(); + v.reverse(); + hex::serialize(v, s) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct BEVisitor; + + impl<'de> de::Visitor<'de> for BEVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex encoded string") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let mut v = + hex::decode(v).map_err(|_| E::custom("cannot deserialize the given value"))?; + v.reverse(); + Ok(v) + } + } + + deserializer.deserialize_any(BEVisitor) + } +} + +pub mod serde_status { + use super::*; use serde::de::{self, Deserializer}; - use serde::ser::Serializer; use std::str::FromStr; use crate::appointment::AppointmentStatus; diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 8e5030b..521be61 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -156,3 +156,36 @@ def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) assert l2.rpc.gettowerinfo(tower_id)["status"] == "misbehaving" assert l2.rpc.gettowerinfo(tower_id)["misbehaving_proof"] + + +def test_get_appointment(node_factory, bitcoind, teosd, directory): + l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) + + # We need to register l2 with the tower + tower_id = teosd.cli.get_tower_info()["tower_id"] + l2.rpc.registertower(tower_id) + + # Force a new commitment + l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) + tx = l1.rpc.dev_sign_last_tx(l2.info["id"])["tx"] + + # Now make sure it is out of date + l1.rpc.pay(l2.rpc.invoice(25000000, "lbl2", "desc2")["bolt11"]) + + # Now l1 cheats + dispute_txid = bitcoind.rpc.sendrawtransaction(tx) + locator = change_endianness(dispute_txid[32:]) + + # Check the appointment before mining a block + appointment = l2.rpc.getappointment(tower_id, locator)["appointment"] + assert "locator" in appointment and "encrypted_blob" in appointment and "to_self_delay" in appointment + + bitcoind.generate_block(1) + time.sleep(1) + + # And after. Now this should be a tracker + tracker = l2.rpc.getappointment(tower_id, locator)["appointment"] + assert "dispute_txid" in tracker and "penalty_txid" in tracker and "penalty_rawtx" in tracker + + # Manually stop l2, otherwise the tower may be stopped before the tower client and we may get some BROKEN logs. + l2.stop()