@ -1,5 +1,6 @@
import abc
import abc
import ast
import random
import random
import sys
import sys
import time
import time
@ -25,12 +26,11 @@ class BlockchainInterface(object):
def __init__ ( self ) :
def __init__ ( self ) :
pass
pass
@abc . abstractmethod
def is_address_imported ( self , addr ) :
def is_address_imported ( self , addr ) :
try :
""" checks that address is already imported """
return self . rpc ( ' getaccount ' , [ addr ] ) != ' '
except JsonRpcError :
return len ( self . rpc ( ' getaddressinfo ' , [ addr ] ) [ ' labels ' ] ) > 0
@abc . abstractmethod
def is_address_labeled ( self , utxo , walletname ) :
def is_address_labeled ( self , utxo , walletname ) :
""" checks that UTXO belongs to the JM wallet """
""" checks that UTXO belongs to the JM wallet """
@ -68,6 +68,8 @@ class BlockchainInterface(object):
else :
else :
return False
return False
# ElectrumWalletInterface is currently broken
class ElectrumWalletInterface ( BlockchainInterface ) : #pragma: no cover
class ElectrumWalletInterface ( BlockchainInterface ) : #pragma: no cover
""" A pseudo-blockchain interface using the existing
""" A pseudo-blockchain interface using the existing
Electrum server connection in an Electrum wallet .
Electrum server connection in an Electrum wallet .
@ -159,17 +161,18 @@ class ElectrumWalletInterface(BlockchainInterface): #pragma: no cover
log . info ( " Got fee: " + btc . fee_per_kb_to_str ( fee_per_kb_sat ) )
log . info ( " Got fee: " + btc . fee_per_kb_to_str ( fee_per_kb_sat ) )
return fee_per_kb_sat
return fee_per_kb_sat
class BitcoinCoreInterface ( BlockchainInterface ) :
class BitcoinCoreInterface ( BlockchainInterface ) :
def __init__ ( self , jsonRpc , network ) :
def __init__ ( self , jsonRpc , network ) :
super ( ) . __init__ ( )
super ( ) . __init__ ( )
self . jsonRpc = jsonRpc
self . jsonRpc = jsonRpc
blockchainInfo = self . rpc ( " getblockchaininfo " , [ ] )
blockchainInfo = self . _ rpc( " getblockchaininfo " , [ ] )
if not blockchainInfo :
if not blockchainInfo :
# see note in BitcoinCoreInterface.rpc - here
# see note in BitcoinCoreInterface.rpc - here
# we have to create this object before reactor start,
# we have to create this object before reactor start,
# so reactor is not stopped, so we override the 'swallowing'
# so reactor is not stopped, so we override the 'swallowing'
# of the Exception that happened in self.rpc():
# of the Exception that happened in self._ rpc():
raise JsonRpcConnectionError ( " RPC connection to Bitcoin Core "
raise JsonRpcConnectionError ( " RPC connection to Bitcoin Core "
" was not established successfully. " )
" was not established successfully. " )
actualNet = blockchainInfo [ ' chain ' ]
actualNet = blockchainInfo [ ' chain ' ]
@ -180,21 +183,28 @@ class BitcoinCoreInterface(BlockchainInterface):
#special case of regtest and testnet having the same addr format
#special case of regtest and testnet having the same addr format
raise Exception ( ' wrong network configured ' )
raise Exception ( ' wrong network configured ' )
def is_address_imported ( self , addr ) :
try :
return self . _rpc ( ' getaccount ' , [ addr ] ) != ' '
except JsonRpcError :
return len ( self . _rpc ( ' getaddressinfo ' , [ addr ] ) [ ' labels ' ] ) > 0
def get_block ( self , blockheight ) :
def get_block ( self , blockheight ) :
""" Returns full serialized block at a given height.
""" Returns full serialized block at a given height.
"""
"""
block_hash = self . rpc ( ' getblockhash ' , [ blockheight ] )
block_hash = self . _ rpc( ' getblockhash ' , [ blockheight ] )
block = self . rpc ( ' getblock ' , [ block_hash , False ] )
block = self . _ rpc( ' getblock ' , [ block_hash , False ] )
if not block :
if not block :
return False
return False
return block
return block
def rpc ( self , method , args ) :
def _ rpc( self , method , args ) :
""" Returns the result of an rpc call to the Bitcoin Core RPC API.
""" Returns the result of an rpc call to the Bitcoin Core RPC API.
If the connection is permanently or unrecognizably broken , None
If the connection is permanently or unrecognizably broken , None
is returned * and the reactor is shutdown * ( because we consider this
is returned * and the reactor is shutdown * ( because we consider this
condition unsafe - TODO possibly create a " freeze " mode that could
condition unsafe - TODO possibly create a " freeze " mode that could
restart when the connection is healed , but that is tricky ) .
restart when the connection is healed , but that is tricky ) .
Should not be called directly from outside code .
"""
"""
if method not in [ ' importaddress ' , ' walletpassphrase ' , ' getaccount ' ,
if method not in [ ' importaddress ' , ' walletpassphrase ' , ' getaccount ' ,
' gettransaction ' , ' getrawtransaction ' , ' gettxout ' ,
' gettransaction ' , ' getrawtransaction ' , ' gettxout ' ,
@ -243,7 +253,7 @@ class BitcoinCoreInterface(BlockchainInterface):
" watchonly " : True
" watchonly " : True
} )
} )
result = self . rpc ( ' importmulti ' , [ requests , { " rescan " : False } ] )
result = self . _ rpc( ' importmulti ' , [ requests , { " rescan " : False } ] )
num_failed = 0
num_failed = 0
for row in result :
for row in result :
@ -266,11 +276,11 @@ class BitcoinCoreInterface(BlockchainInterface):
def import_addresses_if_needed ( self , addresses , wallet_name ) :
def import_addresses_if_needed ( self , addresses , wallet_name ) :
try :
try :
imported_addresses = set ( self . rpc ( ' getaddressesbyaccount ' ,
imported_addresses = set ( self . _ rpc( ' getaddressesbyaccount ' ,
[ wallet_name ] ) )
[ wallet_name ] ) )
except JsonRpcError :
except JsonRpcError :
if wallet_name in self . rpc ( ' listlabels ' , [ ] ) :
if wallet_name in self . _ rpc( ' listlabels ' , [ ] ) :
imported_addresses = set ( self . rpc ( ' getaddressesbylabel ' ,
imported_addresses = set ( self . _ rpc( ' getaddressesbylabel ' ,
[ wallet_name ] ) . keys ( ) )
[ wallet_name ] ) . keys ( ) )
else :
else :
imported_addresses = set ( )
imported_addresses = set ( )
@ -283,7 +293,7 @@ class BitcoinCoreInterface(BlockchainInterface):
batch_size = 1000
batch_size = 1000
iteration = 0
iteration = 0
while True :
while True :
new = self . rpc (
new = self . _ rpc(
' listtransactions ' ,
' listtransactions ' ,
[ " * " , batch_size , iteration * batch_size , True ] )
[ " * " , batch_size , iteration * batch_size , True ] )
for tx in new :
for tx in new :
@ -307,7 +317,7 @@ class BitcoinCoreInterface(BlockchainInterface):
in the wallet ( under any label / account ) , optionally
in the wallet ( under any label / account ) , optionally
skipping some .
skipping some .
"""
"""
return self . rpc ( " listtransactions " , [ " * " , num , skip , True ] )
return self . _ rpc( " listtransactions " , [ " * " , num , skip , True ] )
def get_transaction ( self , txid ) :
def get_transaction ( self , txid ) :
""" Argument txid is passed in binary.
""" Argument txid is passed in binary.
@ -319,10 +329,10 @@ class BitcoinCoreInterface(BlockchainInterface):
htxid = bintohex ( txid )
htxid = bintohex ( txid )
#changed syntax in 0.14.0; allow both syntaxes
#changed syntax in 0.14.0; allow both syntaxes
try :
try :
res = self . rpc ( " gettransaction " , [ htxid , True ] )
res = self . _ rpc( " gettransaction " , [ htxid , True ] )
except Exception as e :
except Exception as e :
try :
try :
res = self . rpc ( " gettransaction " , [ htxid , 1 ] )
res = self . _ rpc( " gettransaction " , [ htxid , 1 ] )
except JsonRpcError as e :
except JsonRpcError as e :
#This should never happen (gettransaction is a wallet rpc).
#This should never happen (gettransaction is a wallet rpc).
log . warn ( " Failed gettransaction call; JsonRpcError: " + repr ( e ) )
log . warn ( " Failed gettransaction call; JsonRpcError: " + repr ( e ) )
@ -342,7 +352,7 @@ class BitcoinCoreInterface(BlockchainInterface):
"""
"""
txhex = bintohex ( txbin )
txhex = bintohex ( txbin )
try :
try :
txid = self . rpc ( ' sendrawtransaction ' , [ txhex ] )
txid = self . _ rpc( ' sendrawtransaction ' , [ txhex ] )
except JsonRpcConnectionError as e :
except JsonRpcConnectionError as e :
log . warning ( ' error pushing = ' + repr ( e ) )
log . warning ( ' error pushing = ' + repr ( e ) )
return False
return False
@ -378,7 +388,7 @@ class BitcoinCoreInterface(BlockchainInterface):
log . warn ( " Invalid utxo format, ignoring: {} " . format ( txo ) )
log . warn ( " Invalid utxo format, ignoring: {} " . format ( txo ) )
result . append ( None )
result . append ( None )
continue
continue
ret = self . rpc ( ' gettxout ' , [ txo_hex , txo_idx , includeunconf ] )
ret = self . _ rpc( ' gettxout ' , [ txo_hex , txo_idx , includeunconf ] )
if ret is None :
if ret is None :
result . append ( None )
result . append ( None )
else :
else :
@ -399,7 +409,7 @@ class BitcoinCoreInterface(BlockchainInterface):
if super ( ) . fee_per_kb_has_been_manually_set ( N ) :
if super ( ) . fee_per_kb_has_been_manually_set ( N ) :
# use the local bitcoin core relay fee as floor to avoid relay problems
# use the local bitcoin core relay fee as floor to avoid relay problems
btc_relayfee = - 1
btc_relayfee = - 1
rpc_result = self . rpc ( ' getnetworkinfo ' , None )
rpc_result = self . _ rpc( ' getnetworkinfo ' , None )
btc_relayfee = rpc_result . get ( ' relayfee ' , btc_relayfee )
btc_relayfee = rpc_result . get ( ' relayfee ' , btc_relayfee )
if btc_relayfee > 0 :
if btc_relayfee > 0 :
relayfee_in_sat = int ( Decimal ( 1e8 ) * Decimal ( btc_relayfee ) )
relayfee_in_sat = int ( Decimal ( 1e8 ) * Decimal ( btc_relayfee ) )
@ -419,7 +429,7 @@ class BitcoinCoreInterface(BlockchainInterface):
estimate = - 1
estimate = - 1
retval = - 1
retval = - 1
for i in range ( tries ) :
for i in range ( tries ) :
rpc_result = self . rpc ( ' estimatesmartfee ' , [ N + i ] )
rpc_result = self . _ rpc( ' estimatesmartfee ' , [ N + i ] )
estimate = rpc_result . get ( ' feerate ' , estimate )
estimate = rpc_result . get ( ' feerate ' , estimate )
if estimate > 0 :
if estimate > 0 :
break
break
@ -433,7 +443,7 @@ class BitcoinCoreInterface(BlockchainInterface):
def get_current_block_height ( self ) :
def get_current_block_height ( self ) :
try :
try :
res = self . rpc ( " getblockcount " , [ ] )
res = self . _ rpc( " getblockcount " , [ ] )
except JsonRpcError as e :
except JsonRpcError as e :
log . error ( " Getblockcount RPC failed with: %i , %s " % (
log . error ( " Getblockcount RPC failed with: %i , %s " % (
e . code , e . message ) )
e . code , e . message ) )
@ -441,23 +451,32 @@ class BitcoinCoreInterface(BlockchainInterface):
return res
return res
def get_best_block_hash ( self ) :
def get_best_block_hash ( self ) :
return self . rpc ( ' getbestblockhash ' , [ ] )
return self . _ rpc( ' getbestblockhash ' , [ ] )
def get_block_time ( self , blockhash ) :
def get_best_block_median_time ( self ) :
return self . _rpc ( ' getblockchaininfo ' , [ ] ) [ ' mediantime ' ]
def _get_block_header_data ( self , blockhash , key ) :
try :
try :
# works with pruning enabled, but only after v0.12
# works with pruning enabled, but only after v0.12
return self . rpc ( ' getblockheader ' , [ blockhash ] ) [ ' time ' ]
return self . _ rpc( ' getblockheader ' , [ blockhash ] ) [ key ]
except JsonRpcError :
except JsonRpcError :
return self . rpc ( ' getblock ' , [ blockhash ] ) [ ' time ' ]
return self . _rpc ( ' getblock ' , [ blockhash ] ) [ key ]
def get_block_height ( self , blockhash ) :
return self . _get_block_header_data ( blockhash , ' height ' )
def get_block_time ( self , blockhash ) :
return self . _get_block_header_data ( blockhash , ' time ' )
def get_tx_merkle_branch ( self , txid , blockhash = None ) :
def get_tx_merkle_branch ( self , txid , blockhash = None ) :
if not blockhash :
if not blockhash :
tx = self . rpc ( " gettransaction " , [ txid ] )
tx = self . _ rpc( " gettransaction " , [ txid ] )
if tx [ " confirmations " ] < 1 :
if tx [ " confirmations " ] < 1 :
raise ValueError ( " Transaction not in block " )
raise ValueError ( " Transaction not in block " )
blockhash = tx [ " blockhash " ]
blockhash = tx [ " blockhash " ]
try :
try :
core_proof = self . rpc ( " gettxoutproof " , [ [ txid ] , blockhash ] )
core_proof = self . _ rpc( " gettxoutproof " , [ [ txid ] , blockhash ] )
except JsonRpcError :
except JsonRpcError :
raise ValueError ( " Block containing transaction is pruned " )
raise ValueError ( " Block containing transaction is pruned " )
return self . core_proof_to_merkle_branch ( core_proof )
return self . core_proof_to_merkle_branch ( core_proof )
@ -469,12 +488,28 @@ class BitcoinCoreInterface(BlockchainInterface):
return core_proof [ 80 : ]
return core_proof [ 80 : ]
def verify_tx_merkle_branch ( self , txid , block_height , merkle_branch ) :
def verify_tx_merkle_branch ( self , txid , block_height , merkle_branch ) :
block_hash = self . rpc ( " getblockhash " , [ block_height ] )
block_hash = self . _ rpc( " getblockhash " , [ block_height ] )
core_proof = self . rpc ( " getblockheader " , [ block_hash , False ] ) + \
core_proof = self . _ rpc( " getblockheader " , [ block_hash , False ] ) + \
binascii . hexlify ( merkle_branch ) . decode ( )
binascii . hexlify ( merkle_branch ) . decode ( )
ret = self . rpc ( " verifytxoutproof " , [ core_proof ] )
ret = self . _ rpc( " verifytxoutproof " , [ core_proof ] )
return len ( ret ) == 1 and ret [ 0 ] == txid
return len ( ret ) == 1 and ret [ 0 ] == txid
def listaddressgroupings ( self ) :
return self . _rpc ( ' listaddressgroupings ' , [ ] )
def listunspent ( self , minconf = None ) :
listunspent_args = [ ]
if ' listunspent_args ' in jm_single ( ) . config . options ( ' POLICY ' ) :
listunspent_args = ast . literal_eval ( jm_single ( ) . config . get (
' POLICY ' , ' listunspent_args ' ) )
if minconf is not None :
listunspent_args [ 0 ] = minconf
return self . _rpc ( ' listunspent ' , listunspent_args )
def testmempoolaccept ( self , rawtx ) :
return self . _rpc ( ' testmempoolaccept ' , [ [ rawtx ] ] )
class RegtestBitcoinCoreMixin ( ) :
class RegtestBitcoinCoreMixin ( ) :
"""
"""
This Mixin provides helper functions that are used in Interface classes
This Mixin provides helper functions that are used in Interface classes
@ -486,7 +521,7 @@ class RegtestBitcoinCoreMixin():
instruct to mine n blocks .
instruct to mine n blocks .
"""
"""
try :
try :
self . rpc ( ' generatetoaddress ' , [ n , self . destn_addr ] )
self . _ rpc( ' generatetoaddress ' , [ n , self . destn_addr ] )
except JsonRpcConnectionError :
except JsonRpcConnectionError :
#can happen if the blockchain is shut down
#can happen if the blockchain is shut down
#automatically at the end of tests; this shouldn't
#automatically at the end of tests; this shouldn't
@ -510,17 +545,18 @@ class RegtestBitcoinCoreMixin():
#mine enough to get to the reqd amt
#mine enough to get to the reqd amt
reqd = int ( amt - self . current_balance )
reqd = int ( amt - self . current_balance )
reqd_blocks = int ( reqd / 50 ) + 1
reqd_blocks = int ( reqd / 50 ) + 1
if self . rpc ( ' setgenerate ' , [ True , reqd_blocks ] ) :
if self . _ rpc( ' setgenerate ' , [ True , reqd_blocks ] ) :
raise Exception ( " Something went wrong " )
raise Exception ( " Something went wrong " )
"""
"""
# now we do a custom create transaction and push to the receiver
# now we do a custom create transaction and push to the receiver
txid = self . rpc ( ' sendtoaddress ' , [ receiving_addr , amt ] )
txid = self . _ rpc( ' sendtoaddress ' , [ receiving_addr , amt ] )
if not txid :
if not txid :
raise Exception ( " Failed to broadcast transaction " )
raise Exception ( " Failed to broadcast transaction " )
# confirm
# confirm
self . tick_forward_chain ( 1 )
self . tick_forward_chain ( 1 )
return txid
return txid
class BitcoinCoreNoHistoryInterface ( BitcoinCoreInterface , RegtestBitcoinCoreMixin ) :
class BitcoinCoreNoHistoryInterface ( BitcoinCoreInterface , RegtestBitcoinCoreMixin ) :
def __init__ ( self , jsonRpc , network ) :
def __init__ ( self , jsonRpc , network ) :
@ -537,8 +573,8 @@ class BitcoinCoreNoHistoryInterface(BitcoinCoreInterface, RegtestBitcoinCoreMixi
log . debug ( " Starting scan of UTXO set " )
log . debug ( " Starting scan of UTXO set " )
st = time . time ( )
st = time . time ( )
try :
try :
self . rpc ( " scantxoutset " , [ " abort " , [ ] ] )
self . _ rpc( " scantxoutset " , [ " abort " , [ ] ] )
self . scan_result = self . rpc ( " scantxoutset " , [ " start " ,
self . scan_result = self . _ rpc( " scantxoutset " , [ " start " ,
addr_list ] )
addr_list ] )
except JsonRpcError as e :
except JsonRpcError as e :
raise RuntimeError ( " Bitcoin Core 0.17.0 or higher required "
raise RuntimeError ( " Bitcoin Core 0.17.0 or higher required "
@ -567,23 +603,19 @@ class BitcoinCoreNoHistoryInterface(BitcoinCoreInterface, RegtestBitcoinCoreMixi
def list_transactions ( self , num ) :
def list_transactions ( self , num ) :
return [ ]
return [ ]
def rpc ( self , method , args ) :
def listaddressgroupings ( self ) :
if method == " listaddressgroupings " :
raise RuntimeError ( " default sync not supported by bitcoin-rpc-nohistory, use --recoversync " )
raise RuntimeError ( " default sync not supported by bitcoin-rpc-nohistory, use --recoversync " )
elif method == " listunspent " :
def listunspent ( self ) :
minconf = 0 if len ( args ) < 1 else args [ 0 ]
return [ {
maxconf = 9999999 if len ( args ) < 2 else args [ 1 ]
" address " : self . _get_addr_from_desc ( u [ " desc " ] ) ,
return [ {
" label " : self . wallet_name ,
" address " : self . _get_addr_from_desc ( u [ " desc " ] ) ,
" height " : u [ " height " ] ,
" label " : self . wallet_name ,
" txid " : u [ " txid " ] ,
" height " : u [ " height " ] ,
" vout " : u [ " vout " ] ,
" txid " : u [ " txid " ] ,
" scriptPubKey " : u [ " scriptPubKey " ] ,
" vout " : u [ " vout " ] ,
" amount " : u [ " amount " ]
" scriptPubKey " : u [ " scriptPubKey " ] ,
} for u in self . scan_result [ " unspents " ] ]
" amount " : u [ " amount " ]
} for u in self . scan_result [ " unspents " ] ]
else :
return super ( ) . rpc ( method , args )
def set_wallet_no_history ( self , wallet ) :
def set_wallet_no_history ( self , wallet ) :
#make wallet-tool not display any new addresses
#make wallet-tool not display any new addresses
@ -595,9 +627,10 @@ class BitcoinCoreNoHistoryInterface(BitcoinCoreInterface, RegtestBitcoinCoreMixi
wallet . disable_new_scripts = True
wallet . disable_new_scripts = True
def tick_forward_chain ( self , n ) :
def tick_forward_chain ( self , n ) :
self . destn_addr = self . rpc ( " getnewaddress " , [ ] )
self . destn_addr = self . _ rpc( " getnewaddress " , [ ] )
super ( ) . tick_forward_chain ( n )
super ( ) . tick_forward_chain ( n )
# class for regtest chain access
# class for regtest chain access
# running on local daemon. Only
# running on local daemon. Only
# to be instantiated after network is up
# to be instantiated after network is up
@ -611,7 +644,7 @@ class RegtestBitcoinCoreInterface(BitcoinCoreInterface, RegtestBitcoinCoreMixin)
self . absurd_fees = False
self . absurd_fees = False
self . simulating = False
self . simulating = False
self . shutdown_signal = False
self . shutdown_signal = False
self . destn_addr = self . rpc ( " getnewaddress " , [ ] )
self . destn_addr = self . _ rpc( " getnewaddress " , [ ] )
def estimate_fee_per_kb ( self , N ) :
def estimate_fee_per_kb ( self , N ) :
if not self . absurd_fees :
if not self . absurd_fees :
@ -655,8 +688,8 @@ class RegtestBitcoinCoreInterface(BitcoinCoreInterface, RegtestBitcoinCoreMixin)
# in the wallet
# in the wallet
res = [ ]
res = [ ]
for address in addresses :
for address in addresses :
#self.rpc('importaddress', [address, 'watchonly'])
#self._ rpc('importaddress', [address, 'watchonly'])
res . append ( { ' address ' : address ,
res . append ( { ' address ' : address ,
' balance ' : int ( Decimal ( 1e8 ) * self . rpc (
' balance ' : int ( Decimal ( 1e8 ) * self . _ rpc(
' getreceivedbyaddress ' , [ address , 0 ] ) ) } )
' getreceivedbyaddress ' , [ address , 0 ] ) ) } )
return { ' data ' : res }
return { ' data ' : res }