2020-08-25 14:56:24 +02:00
#!/usr/bin/env python3
2022-12-24 23:49:50 +00:00
# Copyright (c) 2020-2022 The Bitcoin Core developers
2020-08-25 14:56:24 +02:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" A limited-functionality wallet, which may replace a real wallet in tests """
2021-08-05 14:01:51 +01:00
from copy import deepcopy
2020-08-25 14:56:24 +02:00
from decimal import Decimal
2021-05-24 15:54:52 +02:00
from enum import Enum
2022-03-17 20:07:47 +01:00
from typing import (
Any ,
Optional ,
)
2021-12-25 21:31:49 +01:00
from test_framework . address import (
2023-03-28 16:58:16 +01:00
address_to_scriptpubkey ,
2021-12-25 21:31:49 +01:00
create_deterministic_address_bcrt1_p2tr_op_true ,
key_to_p2pkh ,
key_to_p2sh_p2wpkh ,
key_to_p2wpkh ,
2022-06-07 01:47:54 +02:00
output_key_to_p2tr ,
2021-12-25 21:31:49 +01:00
)
2023-05-23 23:38:31 +02:00
from test_framework . blocktools import COINBASE_MATURITY
2021-10-06 14:55:00 +02:00
from test_framework . descriptors import descsum_create
2022-06-07 01:47:54 +02:00
from test_framework . key import (
ECKey ,
compute_xonly_pubkey ,
)
2020-08-25 14:56:24 +02:00
from test_framework . messages import (
COIN ,
COutPoint ,
CTransaction ,
CTxIn ,
CTxInWitness ,
CTxOut ,
2024-04-16 01:32:44 +02:00
hash256 ,
2024-05-23 18:20:58 +02:00
ser_compact_size ,
2020-08-25 14:56:24 +02:00
)
from test_framework . script import (
CScript ,
2024-05-23 18:20:58 +02:00
OP_1 ,
2021-04-22 18:04:57 +02:00
OP_NOP ,
2022-08-03 12:10:52 +02:00
OP_RETURN ,
2021-10-27 11:18:57 +02:00
OP_TRUE ,
2023-07-03 02:05:13 +02:00
sign_input_legacy ,
2022-06-07 01:47:54 +02:00
taproot_construct ,
2020-08-25 14:56:24 +02:00
)
2021-09-28 13:37:46 +02:00
from test_framework . script_util import (
key_to_p2pk_script ,
2021-12-25 21:31:49 +01:00
key_to_p2pkh_script ,
key_to_p2sh_p2wpkh_script ,
2021-09-28 13:37:46 +02:00
key_to_p2wpkh_script ,
)
2020-08-25 14:56:24 +02:00
from test_framework . util import (
assert_equal ,
2021-08-05 14:01:51 +01:00
assert_greater_than_or_equal ,
2024-05-23 19:38:32 +02:00
get_fee ,
2020-08-25 14:56:24 +02:00
)
2023-05-23 23:38:31 +02:00
from test_framework . wallet_util import generate_keypair
2020-08-25 14:56:24 +02:00
2021-08-05 15:30:25 +01:00
DEFAULT_FEE = Decimal ( " 0.0001 " )
2020-08-25 14:56:24 +02:00
2021-05-24 15:54:52 +02:00
class MiniWalletMode ( Enum ) :
""" Determines the transaction type the MiniWallet is creating and spending.
For most purposes , the default mode ADDRESS_OP_TRUE should be sufficient ;
2021-10-27 11:18:57 +02:00
it simply uses a fixed bech32m P2TR address whose coins are spent with a
2021-05-24 15:54:52 +02:00
witness stack of OP_TRUE , i . e . following an anyone - can - spend policy .
However , if the transactions need to be modified by the user ( e . g . prepending
scriptSig for testing opcodes that are activated by a soft - fork ) , or the txs
should contain an actual signature , the raw modes RAW_OP_TRUE and RAW_P2PK
2024-04-16 01:32:44 +02:00
can be useful . In order to avoid mixing of UTXOs between different MiniWallet
instances , a tag name can be passed to the default mode , to create different
output scripts . Note that the UTXOs from the pre - generated test chain can
only be spent if no tag is passed . Summary of modes :
2021-05-24 15:54:52 +02:00
| output | | tx is | can modify | needs
mode | description | address | standard | scriptSig | signing
- - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - -
2021-10-27 11:18:57 +02:00
ADDRESS_OP_TRUE | anyone - can - spend | bech32m | yes | no | no
2021-05-24 15:54:52 +02:00
RAW_OP_TRUE | anyone - can - spend | - ( raw ) | no | yes | no
RAW_P2PK | pay - to - public - key | - ( raw ) | yes | yes | yes
"""
ADDRESS_OP_TRUE = 1
RAW_OP_TRUE = 2
RAW_P2PK = 3
2020-08-25 14:56:24 +02:00
class MiniWallet :
2024-04-16 01:32:44 +02:00
def __init__ ( self , test_node , * , mode = MiniWalletMode . ADDRESS_OP_TRUE , tag_name = None ) :
2020-08-25 14:56:24 +02:00
self . _test_node = test_node
self . _utxos = [ ]
2022-06-20 17:34:55 +02:00
self . _mode = mode
2021-05-13 18:56:38 +02:00
2021-05-24 15:54:52 +02:00
assert isinstance ( mode , MiniWalletMode )
if mode == MiniWalletMode . RAW_OP_TRUE :
2024-04-16 01:32:44 +02:00
assert tag_name is None
2021-04-22 18:04:57 +02:00
self . _scriptPubKey = bytes ( CScript ( [ OP_TRUE ] ) )
2021-05-24 15:54:52 +02:00
elif mode == MiniWalletMode . RAW_P2PK :
2021-05-13 18:56:38 +02:00
# use simple deterministic private key (k=1)
2024-04-16 01:32:44 +02:00
assert tag_name is None
2021-05-13 18:56:38 +02:00
self . _priv_key = ECKey ( )
self . _priv_key . set ( ( 1 ) . to_bytes ( 32 , ' big ' ) , True )
pub_key = self . _priv_key . get_pubkey ( )
2021-09-28 13:37:46 +02:00
self . _scriptPubKey = key_to_p2pk_script ( pub_key . get_bytes ( ) )
2021-05-24 15:54:52 +02:00
elif mode == MiniWalletMode . ADDRESS_OP_TRUE :
2024-06-11 02:13:18 +02:00
internal_key = None if tag_name is None else compute_xonly_pubkey ( hash256 ( tag_name . encode ( ) ) ) [ 0 ]
2024-05-09 18:52:27 +02:00
self . _address , self . _taproot_info = create_deterministic_address_bcrt1_p2tr_op_true ( internal_key )
2023-03-28 16:58:16 +01:00
self . _scriptPubKey = address_to_scriptpubkey ( self . _address )
2020-08-25 14:56:24 +02:00
2023-01-13 11:28:39 +02:00
# When the pre-mined test framework chain is used, it contains coinbase
# outputs to the MiniWallet's default address in blocks 76-100
# (see method BitcoinTestFramework._initialize_chain())
# The MiniWallet needs to rescan_utxos() in order to account
# for those mature UTXOs, so that all txs spend confirmed coins
self . rescan_utxos ( )
2022-12-15 22:50:39 +08:00
def _create_utxo ( self , * , txid , vout , value , height , coinbase , confirmations ) :
return { " txid " : txid , " vout " : vout , " value " : value , " height " : height , " coinbase " : coinbase , " confirmations " : confirmations }
2022-06-22 10:50:47 +02:00
2024-08-26 19:35:00 +02:00
def _bulk_tx ( self , tx , target_vsize ) :
""" Pad a transaction with extra outputs until it reaches a target vsize.
2022-06-15 15:43:56 +03:00
returns the tx
"""
2024-10-01 23:21:29 +01:00
if target_vsize < tx . get_vsize ( ) :
raise RuntimeError ( f " target_vsize { target_vsize } is less than transaction virtual size { tx . get_vsize ( ) } " )
2024-05-23 18:20:58 +02:00
tx . vout . append ( CTxOut ( nValue = 0 , scriptPubKey = CScript ( [ OP_RETURN ] ) ) )
2024-08-26 19:35:00 +02:00
# determine number of needed padding bytes
dummy_vbytes = target_vsize - tx . get_vsize ( )
2024-05-23 18:20:58 +02:00
# compensate for the increase of the compact-size encoded script length
# (note that the length encoding of the unpadded output script needs one byte)
dummy_vbytes - = len ( ser_compact_size ( dummy_vbytes ) ) - 1
tx . vout [ - 1 ] . scriptPubKey = CScript ( [ OP_RETURN ] + [ OP_1 ] * dummy_vbytes )
2024-08-26 19:35:00 +02:00
assert_equal ( tx . get_vsize ( ) , target_vsize )
2022-06-15 15:43:56 +03:00
2022-03-22 14:17:51 +05:30
def get_balance ( self ) :
return sum ( u [ ' value ' ] for u in self . _utxos )
2022-12-15 22:50:51 +08:00
def rescan_utxos ( self , * , include_mempool = True ) :
2021-09-12 11:03:56 +02:00
""" Drop all utxos and rescan the utxo set """
self . _utxos = [ ]
2021-09-20 15:22:15 +02:00
res = self . _test_node . scantxoutset ( action = " start " , scanobjects = [ self . get_descriptor ( ) ] )
2021-09-12 11:03:56 +02:00
assert_equal ( True , res [ ' success ' ] )
for utxo in res [ ' unspents ' ] :
2022-12-15 22:50:39 +08:00
self . _utxos . append (
self . _create_utxo ( txid = utxo [ " txid " ] ,
vout = utxo [ " vout " ] ,
value = utxo [ " amount " ] ,
height = utxo [ " height " ] ,
coinbase = utxo [ " coinbase " ] ,
confirmations = res [ " height " ] - utxo [ " height " ] + 1 ) )
2022-12-15 22:50:51 +08:00
if include_mempool :
mempool = self . _test_node . getrawmempool ( verbose = True )
# Sort tx by ancestor count. See BlockAssembler::SortForBlock in src/node/miner.cpp
sorted_mempool = sorted ( mempool . items ( ) , key = lambda item : ( item [ 1 ] [ " ancestorcount " ] , int ( item [ 0 ] , 16 ) ) )
for txid , _ in sorted_mempool :
self . scan_tx ( self . _test_node . getrawtransaction ( txid = txid , verbose = True ) )
2021-09-12 11:03:56 +02:00
2021-04-22 13:34:26 +02:00
def scan_tx ( self , tx ) :
2022-06-23 12:24:51 +02:00
""" Scan the tx and adjust the internal list of owned utxos """
for spent in tx [ " vin " ] :
# Mark spent. This may happen when the caller has ownership of a
# utxo that remained in this wallet. For example, by passing
# mark_as_spent=False to get_utxo or by using an utxo returned by a
# create_self_transfer* call.
try :
self . get_utxo ( txid = spent [ " txid " ] , vout = spent [ " vout " ] )
except StopIteration :
pass
2021-04-22 13:34:26 +02:00
for out in tx [ ' vout ' ] :
if out [ ' scriptPubKey ' ] [ ' hex ' ] == self . _scriptPubKey . hex ( ) :
2022-12-15 22:50:39 +08:00
self . _utxos . append ( self . _create_utxo ( txid = tx [ " txid " ] , vout = out [ " n " ] , value = out [ " value " ] , height = 0 , coinbase = False , confirmations = 0 ) )
2021-02-16 17:48:01 +01:00
2022-12-06 22:03:24 -05:00
def scan_txs ( self , txs ) :
for tx in txs :
self . scan_tx ( tx )
2021-05-27 18:09:07 +02:00
def sign_tx ( self , tx , fixed_length = True ) :
2022-12-12 18:22:28 +08:00
if self . _mode == MiniWalletMode . RAW_P2PK :
# for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
# 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)
2023-07-03 02:05:13 +02:00
# with the DER header/skeleton data of 6 bytes added, plus 2 bytes scriptSig overhead
# (OP_PUSHn and SIGHASH_ALL), this leads to a scriptSig target size of 73 bytes
tx . vin [ 0 ] . scriptSig = b ' '
while not len ( tx . vin [ 0 ] . scriptSig ) == 73 :
tx . vin [ 0 ] . scriptSig = b ' '
sign_input_legacy ( tx , 0 , self . _scriptPubKey , self . _priv_key )
2022-12-12 18:22:28 +08:00
if not fixed_length :
break
elif self . _mode == MiniWalletMode . RAW_OP_TRUE :
2023-01-03 13:20:04 +01:00
for i in tx . vin :
i . scriptSig = CScript ( [ OP_NOP ] * 43 ) # pad to identical size
2022-12-12 18:22:28 +08:00
elif self . _mode == MiniWalletMode . ADDRESS_OP_TRUE :
tx . wit . vtxinwit = [ CTxInWitness ( ) ] * len ( tx . vin )
2023-01-03 13:20:04 +01:00
for i in tx . wit . vtxinwit :
2024-05-09 18:52:27 +02:00
assert_equal ( len ( self . _taproot_info . leaves ) , 1 )
leaf_info = list ( self . _taproot_info . leaves . values ( ) ) [ 0 ]
i . scriptWitness . stack = [
leaf_info . script ,
2024-05-09 19:17:57 +02:00
bytes ( [ leaf_info . version | self . _taproot_info . negflag ] ) + self . _taproot_info . internal_pubkey ,
2024-05-09 18:52:27 +02:00
]
2022-12-12 18:22:28 +08:00
else :
assert False
2021-05-13 18:56:38 +02:00
2021-07-27 13:59:55 +02:00
def generate ( self , num_blocks , * * kwargs ) :
2022-06-23 12:23:42 +02:00
""" Generate blocks with coinbase outputs to the internal address, and call rescan_utxos """
2021-07-27 13:59:55 +02:00
blocks = self . _test_node . generatetodescriptor ( num_blocks , self . get_descriptor ( ) , * * kwargs )
2022-06-23 12:23:42 +02:00
# Calling rescan_utxos here makes sure that after a generate the utxo
# set is in a clean state. For example, the wallet will update
# - if the caller consumed utxos, but never used them
# - if the caller sent a transaction that is not mined or got rbf'd
# - after block re-orgs
# - the utxo height for mined mempool txs
# - However, the wallet will not consider remaining mempool txs
self . rescan_utxos ( )
2020-08-25 14:56:24 +02:00
return blocks
2022-03-17 19:54:45 +01:00
def get_scriptPubKey ( self ) :
return self . _scriptPubKey
2021-09-20 15:22:15 +02:00
def get_descriptor ( self ) :
2021-10-06 14:55:00 +02:00
return descsum_create ( f ' raw( { self . _scriptPubKey . hex ( ) } ) ' )
2021-09-20 15:22:15 +02:00
2021-02-23 00:16:50 +01:00
def get_address ( self ) :
2022-06-20 17:34:55 +02:00
assert_equal ( self . _mode , MiniWalletMode . ADDRESS_OP_TRUE )
2021-02-23 00:16:50 +01:00
return self . _address
2023-08-14 10:10:06 +01:00
def get_utxo ( self , * , txid : str = ' ' , vout : Optional [ int ] = None , mark_as_spent = True , confirmed_only = False ) - > dict :
2020-11-13 16:55:20 -06:00
"""
Returns a utxo and marks it as spent ( pops it from the internal list )
Args :
2021-05-28 13:21:14 +08:00
txid : get the first utxo we find from a specific transaction
2020-11-13 16:55:20 -06:00
"""
2021-11-15 13:19:58 +01:00
self . _utxos = sorted ( self . _utxos , key = lambda k : ( k [ ' value ' ] , - k [ ' height ' ] ) ) # Put the largest utxo last
2023-02-28 09:58:06 -03:00
blocks_height = self . _test_node . getblockchaininfo ( ) [ ' blocks ' ]
mature_coins = list ( filter ( lambda utxo : not utxo [ ' coinbase ' ] or COINBASE_MATURITY - 1 < = blocks_height - utxo [ ' height ' ] , self . _utxos ) )
2020-11-13 16:55:20 -06:00
if txid :
2022-03-17 20:07:47 +01:00
utxo_filter : Any = filter ( lambda utxo : txid == utxo [ ' txid ' ] , self . _utxos )
else :
2023-02-28 09:58:06 -03:00
utxo_filter = reversed ( mature_coins ) # By default the largest utxo
2022-03-17 19:54:45 +01:00
if vout is not None :
utxo_filter = filter ( lambda utxo : vout == utxo [ ' vout ' ] , utxo_filter )
2023-08-14 10:10:06 +01:00
if confirmed_only :
utxo_filter = filter ( lambda utxo : utxo [ ' confirmations ' ] > 0 , utxo_filter )
2022-03-17 20:07:47 +01:00
index = self . _utxos . index ( next ( utxo_filter ) )
2021-05-09 23:22:27 +02:00
if mark_as_spent :
return self . _utxos . pop ( index )
else :
return self . _utxos [ index ]
2020-09-09 10:38:19 +02:00
2023-08-14 10:10:06 +01:00
def get_utxos ( self , * , include_immature_coinbase = False , mark_as_spent = True , confirmed_only = False ) :
2022-05-09 00:38:17 +02:00
""" Returns the list of all utxos and optionally mark them as spent """
2022-12-15 22:50:39 +08:00
if not include_immature_coinbase :
2023-05-18 11:17:36 -03:00
blocks_height = self . _test_node . getblockchaininfo ( ) [ ' blocks ' ]
utxo_filter = filter ( lambda utxo : not utxo [ ' coinbase ' ] or COINBASE_MATURITY - 1 < = blocks_height - utxo [ ' height ' ] , self . _utxos )
2022-12-15 22:50:39 +08:00
else :
utxo_filter = self . _utxos
2023-08-14 10:10:06 +01:00
if confirmed_only :
utxo_filter = filter ( lambda utxo : utxo [ ' confirmations ' ] > 0 , utxo_filter )
2022-12-15 22:50:39 +08:00
utxos = deepcopy ( list ( utxo_filter ) )
2022-05-09 00:38:17 +02:00
if mark_as_spent :
self . _utxos = [ ]
return utxos
2022-06-21 11:25:54 +02:00
def send_self_transfer ( self , * , from_node , * * kwargs ) :
2022-07-01 12:07:55 +02:00
""" Call create_self_transfer and send the transaction. """
2021-06-10 12:57:15 +02:00
tx = self . create_self_transfer ( * * kwargs )
2022-06-21 11:25:54 +02:00
self . sendrawtransaction ( from_node = from_node , tx_hex = tx [ ' hex ' ] )
2021-04-22 13:44:05 +02:00
return tx
2021-09-24 01:01:52 +02:00
def send_to ( self , * , from_node , scriptPubKey , amount , fee = 1000 ) :
"""
Create and send a tx with an output to a given scriptPubKey / amount ,
plus a change output to our internal address . To keep things simple , a
fixed fee given in Satoshi is used .
Note that this method fails if there is no single internal utxo
available that can cover the cost for the amount and the fixed fee
( the utxo with the largest value is taken ) .
"""
2022-06-21 11:25:54 +02:00
tx = self . create_self_transfer ( fee_rate = 0 ) [ " tx " ]
2021-09-24 01:01:52 +02:00
assert_greater_than_or_equal ( tx . vout [ 0 ] . nValue , amount + fee )
tx . vout [ 0 ] . nValue - = ( amount + fee ) # change output -> MiniWallet
tx . vout . append ( CTxOut ( amount , scriptPubKey ) ) # arbitrary output -> to be returned
txid = self . sendrawtransaction ( from_node = from_node , tx_hex = tx . serialize ( ) . hex ( ) )
2023-05-12 15:27:05 +02:00
return {
" sent_vout " : 1 ,
" txid " : txid ,
" wtxid " : tx . getwtxid ( ) ,
" hex " : tx . serialize ( ) . hex ( ) ,
" tx " : tx ,
}
2021-09-24 01:01:52 +02:00
2022-06-21 11:25:54 +02:00
def send_self_transfer_multi ( self , * , from_node , * * kwargs ) :
2022-06-23 10:50:29 +02:00
""" Call create_self_transfer_multi and send the transaction. """
2022-03-22 00:34:55 +01:00
tx = self . create_self_transfer_multi ( * * kwargs )
2022-06-23 10:50:29 +02:00
self . sendrawtransaction ( from_node = from_node , tx_hex = tx [ " hex " ] )
return tx
2022-03-22 00:34:55 +01:00
2022-05-27 13:40:06 -04:00
def create_self_transfer_multi (
2022-06-21 11:25:54 +02:00
self ,
* ,
2023-10-25 00:55:17 +02:00
utxos_to_spend : Optional [ list [ dict ] ] = None ,
2022-06-21 11:25:54 +02:00
num_outputs = 1 ,
2022-07-01 12:09:56 +02:00
amount_per_output = 0 ,
2023-11-30 15:00:05 +01:00
version = 2 ,
2022-10-28 16:19:55 +03:00
locktime = 0 ,
2022-06-21 11:25:54 +02:00
sequence = 0 ,
fee_per_output = 1000 ,
2024-08-26 19:35:00 +02:00
target_vsize = 0 ,
2023-11-30 15:00:05 +01:00
confirmed_only = False ,
2022-06-21 11:25:54 +02:00
) :
2022-03-22 00:34:55 +01:00
"""
Create and return a transaction that spends the given UTXOs and creates a
2022-07-01 12:09:56 +02:00
certain number of outputs with equal amounts . The output amounts can be
set by amount_per_output or automatically calculated with a fee_per_output .
2022-03-22 00:34:55 +01:00
"""
2023-08-14 10:10:06 +01:00
utxos_to_spend = utxos_to_spend or [ self . get_utxo ( confirmed_only = confirmed_only ) ]
2022-07-01 12:27:59 +02:00
sequence = [ sequence ] * len ( utxos_to_spend ) if type ( sequence ) is int else sequence
assert_equal ( len ( utxos_to_spend ) , len ( sequence ) )
2022-10-28 16:19:55 +03:00
# calculate output amount
2022-03-22 00:34:55 +01:00
inputs_value_total = sum ( [ int ( COIN * utxo [ ' value ' ] ) for utxo in utxos_to_spend ] )
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
2022-10-28 16:19:55 +03:00
amount_per_output = amount_per_output or ( outputs_value_total / / num_outputs )
2023-01-03 13:00:00 +01:00
assert amount_per_output > 0
outputs_value_total = amount_per_output * num_outputs
fee = Decimal ( inputs_value_total - outputs_value_total ) / COIN
2022-10-28 16:19:55 +03:00
# create tx
tx = CTransaction ( )
2023-01-03 13:20:04 +01:00
tx . vin = [ CTxIn ( COutPoint ( int ( utxo_to_spend [ ' txid ' ] , 16 ) , utxo_to_spend [ ' vout ' ] ) , nSequence = seq ) for utxo_to_spend , seq in zip ( utxos_to_spend , sequence ) ]
2022-10-28 16:19:55 +03:00
tx . vout = [ CTxOut ( amount_per_output , bytearray ( self . _scriptPubKey ) ) for _ in range ( num_outputs ) ]
2024-01-26 15:27:13 -05:00
tx . version = version
2022-10-28 16:19:55 +03:00
tx . nLockTime = locktime
2022-12-12 18:22:28 +08:00
self . sign_tx ( tx )
2022-06-15 15:43:56 +03:00
2024-08-26 19:35:00 +02:00
if target_vsize :
self . _bulk_tx ( tx , target_vsize )
2022-06-15 15:43:56 +03:00
2022-06-23 10:50:29 +02:00
txid = tx . rehash ( )
return {
" new_utxos " : [ self . _create_utxo (
txid = txid ,
vout = i ,
value = Decimal ( tx . vout [ i ] . nValue ) / COIN ,
height = 0 ,
2022-12-15 22:50:39 +08:00
coinbase = False ,
confirmations = 0 ,
2022-06-23 10:50:29 +02:00
) for i in range ( len ( tx . vout ) ) ] ,
2023-01-03 13:00:00 +01:00
" fee " : fee ,
2022-06-23 10:50:29 +02:00
" txid " : txid ,
2023-01-03 13:19:19 +01:00
" wtxid " : tx . getwtxid ( ) ,
2022-06-23 10:50:29 +02:00
" hex " : tx . serialize ( ) . hex ( ) ,
" tx " : tx ,
}
2022-03-22 00:34:55 +01:00
2023-11-30 15:00:05 +01:00
def create_self_transfer (
self ,
* ,
2023-08-14 10:10:06 +01:00
fee_rate = Decimal ( " 0.003 " ) ,
fee = Decimal ( " 0 " ) ,
utxo_to_spend = None ,
2024-08-26 19:35:00 +02:00
target_vsize = 0 ,
2023-11-30 15:00:05 +01:00
confirmed_only = False ,
* * kwargs ,
2023-08-14 10:10:06 +01:00
) :
2022-07-01 12:07:55 +02:00
""" Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed. """
2023-08-14 10:10:06 +01:00
utxo_to_spend = utxo_to_spend or self . get_utxo ( confirmed_only = confirmed_only )
2022-07-01 12:07:55 +02:00
assert fee_rate > = 0
assert fee > = 0
2022-10-28 16:19:55 +03:00
# calculate fee
2022-06-20 17:34:55 +02:00
if self . _mode in ( MiniWalletMode . RAW_OP_TRUE , MiniWalletMode . ADDRESS_OP_TRUE ) :
2021-10-27 11:18:57 +02:00
vsize = Decimal ( 104 ) # anyone-can-spend
2022-06-20 17:34:55 +02:00
elif self . _mode == MiniWalletMode . RAW_P2PK :
2021-05-27 20:15:02 +02:00
vsize = Decimal ( 168 ) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
2022-06-20 17:34:55 +02:00
else :
assert False
2024-08-26 19:35:00 +02:00
if target_vsize and not fee : # respect fee_rate if target vsize is passed
fee = get_fee ( target_vsize , fee_rate )
2022-07-01 12:07:55 +02:00
send_value = utxo_to_spend [ " value " ] - ( fee or ( fee_rate * vsize / 1000 ) )
2024-06-22 14:13:54 +01:00
if send_value < = 0 :
raise RuntimeError ( f " UTXO value { utxo_to_spend [ ' value ' ] } is too small to cover fees { ( fee or ( fee_rate * vsize / 1000 ) ) } " )
2022-10-28 16:19:55 +03:00
# create tx
2023-11-30 15:00:05 +01:00
tx = self . create_self_transfer_multi (
utxos_to_spend = [ utxo_to_spend ] ,
amount_per_output = int ( COIN * send_value ) ,
2024-08-26 19:35:00 +02:00
target_vsize = target_vsize ,
2023-11-30 15:00:05 +01:00
* * kwargs ,
)
2024-08-26 19:35:00 +02:00
if not target_vsize :
2022-10-28 16:19:55 +03:00
assert_equal ( tx [ " tx " ] . get_vsize ( ) , vsize )
2023-01-03 13:19:19 +01:00
tx [ " new_utxo " ] = tx . pop ( " new_utxos " ) [ 0 ]
2022-04-22 02:48:38 +02:00
2023-01-03 13:19:19 +01:00
return tx
2021-04-22 13:34:26 +02:00
2022-06-01 17:05:13 +02:00
def sendrawtransaction ( self , * , from_node , tx_hex , maxfeerate = 0 , * * kwargs ) :
txid = from_node . sendrawtransaction ( hexstring = tx_hex , maxfeerate = maxfeerate , * * kwargs )
2021-04-22 13:34:26 +02:00
self . scan_tx ( from_node . decoderawtransaction ( tx_hex ) )
2021-09-16 14:42:50 +02:00
return txid
2023-01-03 12:57:56 +01:00
def create_self_transfer_chain ( self , * , chain_length , utxo_to_spend = None ) :
2022-09-02 12:54:26 -03:00
"""
Create a " chain " of chain_length transactions . The nth transaction in
the chain is a child of the n - 1 th transaction and parent of the n + 1 th transaction .
"""
2023-01-03 12:57:56 +01:00
chaintip_utxo = utxo_to_spend or self . get_utxo ( )
chain = [ ]
2022-09-02 12:54:26 -03:00
for _ in range ( chain_length ) :
tx = self . create_self_transfer ( utxo_to_spend = chaintip_utxo )
chaintip_utxo = tx [ " new_utxo " ]
2023-01-03 12:57:56 +01:00
chain . append ( tx )
2022-09-02 12:54:26 -03:00
2023-01-03 12:57:56 +01:00
return chain
2022-09-02 12:54:26 -03:00
2023-01-03 12:57:56 +01:00
def send_self_transfer_chain ( self , * , from_node , * * kwargs ) :
2022-06-28 14:23:45 +03:00
""" Create and send a " chain " of chain_length transactions. The nth transaction in
the chain is a child of the n - 1 th transaction and parent of the n + 1 th transaction .
2023-01-03 12:57:56 +01:00
Returns a list of objects for each tx ( see create_self_transfer_multi ) .
2022-06-28 14:23:45 +03:00
"""
2023-01-03 12:57:56 +01:00
chain = self . create_self_transfer_chain ( * * kwargs )
for t in chain :
self . sendrawtransaction ( from_node = from_node , tx_hex = t [ " hex " ] )
return chain
2022-06-28 14:23:45 +03:00
2022-06-07 01:47:54 +02:00
def getnewdestination ( address_type = ' bech32m ' ) :
2021-12-25 21:31:49 +01:00
""" Generate a random destination of the specified type and return the
corresponding public key , scriptPubKey and address . Supported types are
2022-06-07 01:47:54 +02:00
' legacy ' , ' p2sh-segwit ' , ' bech32 ' and ' bech32m ' . Can be used when a random
2021-12-25 21:31:49 +01:00
destination is needed , but no compiled wallet is available ( e . g . as
replacement to the getnewaddress / getaddressinfo RPCs ) . """
2023-05-23 23:38:31 +02:00
key , pubkey = generate_keypair ( )
2021-12-25 21:31:49 +01:00
if address_type == ' legacy ' :
scriptpubkey = key_to_p2pkh_script ( pubkey )
address = key_to_p2pkh ( pubkey )
elif address_type == ' p2sh-segwit ' :
scriptpubkey = key_to_p2sh_p2wpkh_script ( pubkey )
address = key_to_p2sh_p2wpkh ( pubkey )
elif address_type == ' bech32 ' :
scriptpubkey = key_to_p2wpkh_script ( pubkey )
address = key_to_p2wpkh ( pubkey )
2022-06-07 01:47:54 +02:00
elif address_type == ' bech32m ' :
tap = taproot_construct ( compute_xonly_pubkey ( key . get_bytes ( ) ) [ 0 ] )
pubkey = tap . output_pubkey
scriptpubkey = tap . scriptPubKey
address = output_key_to_p2tr ( pubkey )
2021-12-25 21:31:49 +01:00
else :
assert False
return pubkey , scriptpubkey , address