diff --git a/doc/API.md b/doc/API.md index 1764eded..f126fffb 100644 --- a/doc/API.md +++ b/doc/API.md @@ -109,3 +109,24 @@ This command will refuse to create any output worth less than 5k sats. | Field | Type | Description | | -------------- | --------- | ---------------------------------------------------- | | `psbt` | string | PSBT of the spending transaction, encoded as base64. | + + +### `updatespend` + +Store the PSBT of a Spend transaction in database, updating it if it already exists. + +Will merge the partial signatures for all inputs if a PSBT for a transaction with the same txid +exists in DB. + +#### Request + +| Field | Type | Description | +| --------- | ------ | ------------------------------------------- | +| `psbt` | string | Base64-encoded PSBT of a Spend transaction. | + +#### Response + +This command does not return anything for now. + +| Field | Type | Description | +| -------------- | --------- | ---------------------------------------------------- | diff --git a/src/jsonrpc/api.rs b/src/jsonrpc/api.rs index 9f861edc..90f224c6 100644 --- a/src/jsonrpc/api.rs +++ b/src/jsonrpc/api.rs @@ -5,7 +5,7 @@ use crate::{ use std::{collections::HashMap, convert::TryInto, str::FromStr}; -use miniscript::bitcoin; +use miniscript::bitcoin::{self, consensus, util::psbt::PartiallySignedTransaction as Psbt}; fn create_spend(control: &DaemonControl, params: Params) -> Result { let outpoints = params @@ -47,6 +47,19 @@ fn create_spend(control: &DaemonControl, params: Params) -> Result Result { + let psbt: Psbt = params + .get(0, "psbt") + .ok_or(Error::invalid_params("Missing 'psbt' parameter."))? + .as_str() + .and_then(|s| base64::decode(&s).ok()) + .and_then(|bytes| consensus::deserialize(&bytes).ok()) + .ok_or(Error::invalid_params("Invalid 'feerate' parameter."))?; + control.update_spend(psbt)?; + + Ok(serde_json::json!({})) +} + /// Handle an incoming JSONRPC2 request. pub fn handle_request(control: &DaemonControl, req: Request) -> Result { let result = match req.method.as_str() { @@ -60,6 +73,12 @@ pub fn handle_request(control: &DaemonControl, req: Request) -> Result serde_json::json!(&control.get_new_address()), "listcoins" => serde_json::json!(&control.list_coins()), "stop" => serde_json::json!({}), + "updatespend" => { + let params = req + .params + .ok_or(Error::invalid_params("Missing 'psbt' parameter."))?; + update_spend(control, params)? + } _ => { return Err(Error::method_not_found()); } diff --git a/tests/test_rpc.py b/tests/test_rpc.py index e56f5cc2..84e077fa 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -93,3 +93,22 @@ def test_create_spend(minisafed, bitcoind): # We can sign it and broadcast it. signed_tx_hex = minisafed.sign_psbt(spend_psbt) bitcoind.rpc.sendrawtransaction(signed_tx_hex) + + +def test_update_spend(minisafed, bitcoind): + # Start by creating a Spend PSBT + addr = minisafed.rpc.getnewaddress()["address"] + bitcoind.rpc.sendtoaddress(addr, 0.2567) + wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) > 0) + outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]] + destinations = { + bitcoind.rpc.getnewaddress(): 200_000, + } + res = minisafed.rpc.createspend(outpoints, destinations, 6) + assert "psbt" in res + + # Now update it + minisafed.rpc.updatespend(res["psbt"]) + + # TODO: check it's stored once we implement 'listspendtxs' + # TODO: check with added signatures once we implement 'listspendtxs'