commands: add a 'spend_info' field to the 'listcoins' entries
This commit is contained in:
parent
57add1d86b
commit
99ab0d7add
19
doc/API.md
19
doc/API.md
@ -81,11 +81,20 @@ This command does not take any parameter for now.
|
||||
|
||||
#### Response
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------- | ------------- | ---------------------------------------------------------------- |
|
||||
| `amount` | int | Value of the TxO in satoshis |
|
||||
| `outpoint` | string | Transaction id and output index of this coin |
|
||||
| `block_height` | int or null | Blockheight the transaction was confirmed at, or `null` |
|
||||
| Field | Type | Description |
|
||||
| -------------- | ------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `amount` | int | Value of the TxO in satoshis. |
|
||||
| `outpoint` | string | Transaction id and output index of this coin. |
|
||||
| `block_height` | int or null | Blockheight the transaction was confirmed at, or `null`. |
|
||||
| `spend_info` | object | Information about the transaction spending this coin. See [Spending transaction info](#spending_transaction_info). |
|
||||
|
||||
|
||||
##### Spending transaction info
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------- | ----------- | -------------------------------------------------------------- |
|
||||
| `txid` | str | Spending transaction's id. |
|
||||
| `height` | int or null | Block height the spending tx was included at, if confirmed. |
|
||||
|
||||
|
||||
### `createspend`
|
||||
|
||||
@ -169,10 +169,7 @@ impl DaemonControl {
|
||||
pub fn get_info(&self) -> GetInfoResult {
|
||||
let mut db_conn = self.db.connection();
|
||||
|
||||
let blockheight = db_conn
|
||||
.chain_tip()
|
||||
.map(|tip| tip.height)
|
||||
.unwrap_or(0);
|
||||
let blockheight = db_conn.chain_tip().map(|tip| tip.height).unwrap_or(0);
|
||||
GetInfoResult {
|
||||
version: VERSION.to_string(),
|
||||
network: self.config.bitcoin_config.network,
|
||||
@ -211,12 +208,19 @@ impl DaemonControl {
|
||||
amount,
|
||||
outpoint,
|
||||
block_height,
|
||||
spend_txid,
|
||||
spend_block,
|
||||
..
|
||||
} = coin;
|
||||
let spend_info = spend_txid.map(|txid| LCSpendInfo {
|
||||
txid,
|
||||
height: spend_block.map(|b| b.height),
|
||||
});
|
||||
ListCoinsEntry {
|
||||
amount,
|
||||
outpoint,
|
||||
block_height,
|
||||
spend_info,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -464,7 +468,14 @@ pub struct GetAddressResult {
|
||||
pub address: bitcoin::Address,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct LCSpendInfo {
|
||||
pub txid: bitcoin::Txid,
|
||||
/// The block height this spending transaction was confirmed at.
|
||||
pub height: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct ListCoinsEntry {
|
||||
#[serde(
|
||||
serialize_with = "ser_amount",
|
||||
@ -473,6 +484,8 @@ pub struct ListCoinsEntry {
|
||||
pub amount: bitcoin::Amount,
|
||||
pub outpoint: bitcoin::OutPoint,
|
||||
pub block_height: Option<i32>,
|
||||
/// Information about the transaction spending this coin.
|
||||
pub spend_info: Option<LCSpendInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@ -8,6 +8,9 @@ import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from io import BytesIO
|
||||
from .serializations import CTransaction, PSBT
|
||||
|
||||
TIMEOUT = int(os.getenv("TIMEOUT", 20))
|
||||
EXECUTOR_WORKERS = int(os.getenv("EXECUTOR_WORKERS", 20))
|
||||
VERBOSE = os.getenv("VERBOSE", "0") == "1"
|
||||
@ -43,6 +46,35 @@ def wait_for(success, timeout=TIMEOUT, debug_fn=None):
|
||||
raise ValueError("Error waiting for {}", success)
|
||||
|
||||
|
||||
def get_txid(hex_tx):
|
||||
"""Get the txid (as hex) of the given (as hex) transaction."""
|
||||
tx = CTransaction()
|
||||
tx.deserialize(BytesIO(bytes.fromhex(hex_tx)))
|
||||
return tx.txid().hex()
|
||||
|
||||
|
||||
def spend_coins(minisafed, bitcoind, coins):
|
||||
"""Spend these coins, no matter how.
|
||||
This will create a single transaction spending them all at once at the minimum
|
||||
feerate. This will broadcast but not confirm the transaction.
|
||||
|
||||
:param coins: a list of dict as returned by listcoins. The coins must all exist.
|
||||
:returns: the broadcasted transaction, as hex.
|
||||
"""
|
||||
total_value = sum(c["amount"] for c in coins)
|
||||
destinations = {
|
||||
bitcoind.rpc.getnewaddress(): total_value - 11 - 31 - 300 * len(coins)
|
||||
}
|
||||
res = minisafed.rpc.createspend([c["outpoint"] for c in coins], destinations, 1)
|
||||
|
||||
psbt = PSBT()
|
||||
psbt.deserialize(res["psbt"])
|
||||
tx = minisafed.sign_psbt(psbt)
|
||||
bitcoind.rpc.sendrawtransaction(tx)
|
||||
|
||||
return tx
|
||||
|
||||
|
||||
class RpcError(ValueError):
|
||||
def __init__(self, method: str, params: dict, error: str):
|
||||
super(ValueError, self).__init__(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from fixtures import *
|
||||
from test_framework.serializations import PSBT
|
||||
from test_framework.utils import wait_for, COIN
|
||||
from test_framework.utils import wait_for, COIN, get_txid, spend_coins
|
||||
|
||||
|
||||
def test_getinfo(minisafed):
|
||||
@ -34,6 +34,7 @@ def test_listcoins(minisafed, bitcoind):
|
||||
assert txid == res[0]["outpoint"][:64]
|
||||
assert res[0]["amount"] == 1 * COIN
|
||||
assert res[0]["block_height"] is None
|
||||
assert res[0]["spend_info"] is None
|
||||
|
||||
# If the coin gets confirmed, it'll be marked as such.
|
||||
bitcoind.generate_block(1, wait_for_mempool=txid)
|
||||
@ -42,6 +43,22 @@ def test_listcoins(minisafed, bitcoind):
|
||||
lambda: minisafed.rpc.listcoins()["coins"][0]["block_height"] == block_height
|
||||
)
|
||||
|
||||
# Same if the coin gets spent.
|
||||
spend_tx = spend_coins(minisafed, bitcoind, (res[0],))
|
||||
spend_txid = get_txid(spend_tx)
|
||||
wait_for(lambda: minisafed.rpc.listcoins()["coins"][0]["spend_info"] is not None)
|
||||
spend_info = minisafed.rpc.listcoins()["coins"][0]["spend_info"]
|
||||
assert spend_info["txid"] == spend_txid
|
||||
assert spend_info["height"] is None
|
||||
|
||||
# And if this spending tx gets confirmed.
|
||||
bitcoind.generate_block(1, wait_for_mempool=spend_txid)
|
||||
curr_height = bitcoind.rpc.getblockcount()
|
||||
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == curr_height)
|
||||
spend_info = minisafed.rpc.listcoins()["coins"][0]["spend_info"]
|
||||
assert spend_info["txid"] == spend_txid
|
||||
assert spend_info["height"] == curr_height
|
||||
|
||||
|
||||
def test_jsonrpc_server(minisafed, bitcoind):
|
||||
"""Test passing parameters as a list or a mapping."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user