Skip to content

Commit

Permalink
Try another maker if signature request fails
Browse files Browse the repository at this point in the history
Taker will try to request senders contract transaction signatures in a loop
until a maker replies successfully, or we run out of makers to try.

Consider when the taker coordinates a coinswap between two makers in a routed
coinswap. The way this recovery from failure works is that the taker may need
to send the ProofOfFunding message to a taker multiple times, once for each
time a new "next_maker" is attempted. Because of that makers must now deal
with a ProofOfFunding message sent multiple times. This commit also has code
for that.

Also created a optional "special behavior" flag which can configure makers to
do weird stuff like stall or abort. This flag is useful for testing the new
feature of recovering if a maker fails to send signatures.

Signed-off-by: chris-belcher <chris-belcher@users.noreply.github.com>
  • Loading branch information
chris-belcher committed Dec 27, 2021
1 parent d5e38f3 commit 1f63310
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 164 deletions.
67 changes: 45 additions & 22 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ fn print_receive_invoice(wallet_file_name: &PathBuf) {
println!("{}", addr);
}

fn run_maker(wallet_file_name: &PathBuf, port: u16) {
fn run_maker(wallet_file_name: &PathBuf, port: u16, special_behavior: Option<String>) {
let rpc = match get_bitcoin_rpc() {
Ok(rpc) => rpc,
Err(error) => {
Expand All @@ -318,7 +318,18 @@ fn run_maker(wallet_file_name: &PathBuf, port: u16) {

let rpc_ptr = Arc::new(rpc);
let wallet_ptr = Arc::new(RwLock::new(wallet));
maker_protocol::start_maker(rpc_ptr, wallet_ptr, port);
let config = maker_protocol::MakerConfig {
port,
rpc_ping_interval: 30,
watchtower_ping_interval: 300,
maker_behavior: match special_behavior.unwrap_or(String::new()).as_str() {
"closeonsignsenderscontracttx" => {
maker_protocol::MakerBehavior::CloseOnSignSendersContractTx
}
_ => maker_protocol::MakerBehavior::Normal,
},
};
maker_protocol::start_maker(rpc_ptr, wallet_ptr, config);
}

fn run_taker(wallet_file_name: &PathBuf) {
Expand Down Expand Up @@ -408,6 +419,20 @@ fn run_watchtower() {
watchtower_protocol::start_watchtower(&rpc);
}

static INIT: Once = Once::new();

/// Setup function that will only run once, even if called multiple times.
fn setup_logger() {
INIT.call_once(|| {
env_logger::Builder::from_env(
env_logger::Env::default()
.default_filter_or("teleport=info,main=info")
.default_write_style_or("always"),
)
.init();
});
}

#[derive(Debug, StructOpt)]
#[structopt(name = "teleport", about = "A tool for CoinSwap")]
struct ArgsWithWalletFile {
Expand Down Expand Up @@ -441,8 +466,13 @@ enum Subcommand {
/// Prints receive invoice.
GetReceiveInvoice,

/// Runs Maker server on provided port.
RunMaker { port: u16 },
/// Runs Maker server
RunMaker {
/// Port to listen on, default is 6102
port: Option<u16>,
/// Special behavior used for testing e.g. "closeonsignsenderscontracttx"
special_behavior: Option<String>,
},

/// Runs Taker.
CoinswapSend,
Expand All @@ -465,20 +495,6 @@ enum Subcommand {
},
}

static INIT: Once = Once::new();

/// Setup function that will only run once, even if called multiple times.
fn setup_logger() {
INIT.call_once(|| {
env_logger::Builder::from_env(
env_logger::Env::default()
.default_filter_or("teleport=info,main=info")
.default_write_style_or("always"),
)
.init();
});
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
setup_logger();

Expand All @@ -500,8 +516,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Subcommand::GetReceiveInvoice => {
print_receive_invoice(&args.wallet_file_name);
}
Subcommand::RunMaker { port } => {
run_maker(&args.wallet_file_name, port);
Subcommand::RunMaker {
port,
special_behavior,
} => {
run_maker(
&args.wallet_file_name,
port.unwrap_or(6102),
special_behavior,
);
}
Subcommand::CoinswapSend => {
run_taker(&args.wallet_file_name);
Expand Down Expand Up @@ -691,11 +714,11 @@ mod test {
});

let maker1_thread = thread::spawn(|| {
run_maker(&PathBuf::from_str(MAKER1).unwrap(), 6102);
run_maker(&PathBuf::from_str(MAKER1).unwrap(), 6102, None);
});

let maker2_thread = thread::spawn(|| {
run_maker(&PathBuf::from_str(MAKER2).unwrap(), 16102);
run_maker(&PathBuf::from_str(MAKER2).unwrap(), 16102, None);
});

let taker_thread = thread::spawn(|| {
Expand Down
124 changes: 86 additions & 38 deletions src/maker_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,45 +32,67 @@ use crate::watchtower_client::{
ping_watchtowers, register_coinswap_with_watchtowers, ContractInfo,
};

//used to configure the maker do weird things for testing
#[derive(Debug, Clone, Copy)]
pub enum MakerBehavior {
Normal,
CloseOnSignSendersContractTx,
}

#[derive(Debug, Clone, Copy)]
pub struct MakerConfig {
pub port: u16,
pub rpc_ping_interval: u64,
pub watchtower_ping_interval: u64,
pub maker_behavior: MakerBehavior,
}

#[tokio::main]
pub async fn start_maker(rpc: Arc<Client>, wallet: Arc<RwLock<Wallet>>, config: MakerConfig) {
match run(rpc, wallet, config).await {
Ok(_o) => log::info!("maker ended without error"),
Err(e) => log::info!("maker ended with err {:?}", e),
};
}

// A structure denoting expectation of type of taker message.
// Used in the [ConnectionState] structure.
//
// If the recieved message doesn't match expected method,
// protocol error will be returned.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug)]
enum ExpectedMessage {
TakerHello,
NewlyConnectedTaker,
SignSendersContractTx,
ProofOfFunding,
SendersAndReceiversContractSigs,
ProofOfFundingORSendersAndReceiversContractSigs,
SignReceiversContractTx,
HashPreimage,
PrivateKeyHandover,
}

#[tokio::main]
pub async fn start_maker(rpc: Arc<Client>, wallet: Arc<RwLock<Wallet>>, port: u16) {
match run(rpc, wallet, port).await {
Ok(_o) => log::info!("maker ended without error"),
Err(e) => log::info!("maker ended with err {:?}", e),
};
}

struct ConnectionState {
allowed_message: ExpectedMessage,
incoming_swapcoins: Option<Vec<IncomingSwapCoin>>,
outgoing_swapcoins: Option<Vec<OutgoingSwapCoin>>,
pending_funding_txes: Option<Vec<Transaction>>,
}

async fn run(rpc: Arc<Client>, wallet: Arc<RwLock<Wallet>>, port: u16) -> Result<(), Error> {
async fn run(
rpc: Arc<Client>,
wallet: Arc<RwLock<Wallet>>,
config: MakerConfig,
) -> Result<(), Error> {
log::debug!(
"Running maker with special behavior = {:?}",
config.maker_behavior
);

log::info!("Pinging watchtowers. . .");
ping_watchtowers().await?;

//TODO port number in config file
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, port)).await?;
log::info!("Listening On Port {}", port);
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, config.port)).await?;
log::info!("Listening On Port {}", config.port);

let (server_loop_comms_tx, mut server_loop_comms_rx) = mpsc::channel::<Error>(100);
let mut accepting_clients = true;
Expand All @@ -91,10 +113,9 @@ async fn run(rpc: Arc<Client>, wallet: Arc<RwLock<Wallet>>, port: u16) -> Result
}
break Err(client_err.unwrap());
},
//TODO make a const for this magic number of how often to ping the rpc
_ = sleep(Duration::from_secs(10)) => {
_ = sleep(Duration::from_secs(config.rpc_ping_interval)) => {
let rpc_ping_success = rpc.get_best_block_hash().is_ok();
let watchtowers_ping_interval = Duration::from_secs(300); //TODO put in config file?
let watchtowers_ping_interval = Duration::from_secs(config.watchtower_ping_interval);
let (watchtowers_ping_success, debug_msg) = if Instant::now()
.saturating_duration_since(last_watchtowers_ping)
> watchtowers_ping_interval {
Expand Down Expand Up @@ -174,6 +195,7 @@ async fn run(rpc: Arc<Client>, wallet: Arc<RwLock<Wallet>>, port: u16) -> Result
Arc::clone(&client_rpc),
Arc::clone(&client_wallet),
addr,
config.maker_behavior,
)
.await;
match message_result {
Expand Down Expand Up @@ -222,6 +244,7 @@ async fn handle_message(
rpc: Arc<Client>,
wallet: Arc<RwLock<Wallet>>,
from_addrs: SocketAddr,
maker_behavior: MakerBehavior,
) -> Result<Option<MakerToTakerMessage>, Error> {
let request: TakerToMakerMessage = match serde_json::from_str(&line) {
Ok(r) => r,
Expand Down Expand Up @@ -261,7 +284,7 @@ async fn handle_message(
log::info!("===> [{}] | Sending SendersContractSig", from_addrs.port());
log::debug!("{:#?}", message);
connection_state.allowed_message = ExpectedMessage::ProofOfFunding;
handle_sign_senders_contract_tx(wallet, message)?
handle_sign_senders_contract_tx(wallet, message, maker_behavior)?
}
TakerToMakerMessage::ProofOfFunding(proof) => {
log::info!("<=== [{}] | Recieved ProofOfFunding", from_addrs.port());
Expand All @@ -270,7 +293,8 @@ async fn handle_message(
from_addrs.port()
);
log::debug!("{:#?}", proof);
connection_state.allowed_message = ExpectedMessage::SendersAndReceiversContractSigs;
connection_state.allowed_message =
ExpectedMessage::ProofOfFundingORSendersAndReceiversContractSigs;
handle_proof_of_funding(connection_state, rpc, wallet, &proof)?
}
TakerToMakerMessage::SignReceiversContractTx(message) => {
Expand Down Expand Up @@ -306,7 +330,7 @@ async fn handle_message(
log::info!("===> [{}] | Sending SendersContractSig", from_addrs.port());
log::debug!("{:#?}", message);
connection_state.allowed_message = ExpectedMessage::ProofOfFunding;
handle_sign_senders_contract_tx(wallet, message)?
handle_sign_senders_contract_tx(wallet, message, maker_behavior)?
} else {
return Err(Error::Protocol(
"Expected Sign sender's contract transaction message",
Expand All @@ -321,27 +345,47 @@ async fn handle_message(
from_addrs.port()
);
log::debug!("{:#?}", proof);
connection_state.allowed_message = ExpectedMessage::SendersAndReceiversContractSigs;
connection_state.allowed_message =
ExpectedMessage::ProofOfFundingORSendersAndReceiversContractSigs;
handle_proof_of_funding(connection_state, rpc, wallet, &proof)?
} else {
return Err(Error::Protocol("Expected proof of funding message"));
}
}
ExpectedMessage::SendersAndReceiversContractSigs => {
if let TakerToMakerMessage::SendersAndReceiversContractSigs(message) = request {
log::info!(
"<=== [{}] | Recieved SendersAndReceiversContractSigs",
from_addrs.port()
);
// Nothing to send. Maker now creates and broadcasts his funding Txs
log::debug!("{:#?}", message);
connection_state.allowed_message = ExpectedMessage::SignReceiversContractTx;
handle_senders_and_receivers_contract_sigs(connection_state, rpc, wallet, message)
ExpectedMessage::ProofOfFundingORSendersAndReceiversContractSigs => {
match request {
TakerToMakerMessage::ProofOfFunding(proof) => {
log::info!("<=== [{}] | Recieved ProofOfFunding", from_addrs.port());
log::info!(
"===> [{}] | Sending SignSendersAndReceiversContractTxes",
from_addrs.port()
);
log::debug!("{:#?}", proof);
connection_state.allowed_message =
ExpectedMessage::ProofOfFundingORSendersAndReceiversContractSigs;
handle_proof_of_funding(connection_state, rpc, wallet, &proof)?
}
TakerToMakerMessage::SendersAndReceiversContractSigs(message) => {
log::info!(
"<=== [{}] | Recieved SendersAndReceiversContractSigs",
from_addrs.port()
);
// Nothing to send. Maker now creates and broadcasts his funding Txs
log::debug!("{:#?}", message);
connection_state.allowed_message = ExpectedMessage::SignReceiversContractTx;
handle_senders_and_receivers_contract_sigs(
connection_state,
rpc,
wallet,
message,
)
.await?
} else {
return Err(Error::Protocol(
"Expected sender's and reciever's contract signatures",
));
}
_ => {
return Err(Error::Protocol(
"Expected proof of funding or sender's and reciever's contract signatures",
));
}
}
}
ExpectedMessage::SignReceiversContractTx => {
Expand Down Expand Up @@ -396,7 +440,13 @@ async fn handle_message(
fn handle_sign_senders_contract_tx(
wallet: Arc<RwLock<Wallet>>,
message: SignSendersContractTx,
maker_behavior: MakerBehavior,
) -> Result<Option<MakerToTakerMessage>, Error> {
if let MakerBehavior::CloseOnSignSendersContractTx = maker_behavior {
return Err(Error::Protocol(
"closing connection early due to special maker behavior",
));
}
let tweakable_privkey = wallet.read().unwrap().get_tweakable_keypair().0;
//TODO this for loop could be replaced with an iterator and map
//see that other example where Result<> inside an iterator is used
Expand Down Expand Up @@ -435,7 +485,6 @@ fn handle_proof_of_funding(
}
for funding_info in &proof.confirmed_funding_txes {
//check that the claimed multisig redeemscript is in the transaction

log::debug!(
"Proof of Funding: \ntx = {:#?}\nMultisig_Reedimscript = {:x}",
funding_info.funding_tx,
Expand All @@ -450,7 +499,6 @@ fn handle_proof_of_funding(
};
funding_output_indexes.push(funding_output_index);
funding_outputs.push(funding_output);

let verify_result = contracts::verify_proof_of_funding(
Arc::clone(&rpc),
&mut wallet.write().unwrap(),
Expand Down
3 changes: 2 additions & 1 deletion src/offerbook_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ pub struct OfferAddress {
pub address: String, //string for now when its "localhost:port"
}

const MAKER_HOSTS: [&str; 4] = [
const MAKER_HOSTS: [&str; 5] = [
"localhost:6102",
"localhost:16102",
"localhost:26102",
"localhost:36102",
"localhost:46102",
];

fn parse_message(line: &str) -> Option<MakerToTakerMessage> {
Expand Down
Loading

0 comments on commit 1f63310

Please sign in to comment.