Skip to content

Commit

Permalink
Merge pull request #419 from 1200wd/v7-bugfixes
Browse files Browse the repository at this point in the history
V7 bugfixes
  • Loading branch information
mccwdev authored Nov 2, 2024
2 parents c006e75 + 97f30cb commit f7acb81
Show file tree
Hide file tree
Showing 22 changed files with 200 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unittests-mysql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
architecture: 'x64'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittests-noscrypt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
architecture: 'x64'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittests-postgresql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
architecture: 'x64'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
architecture: 'x64'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittests_windows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
architecture: 'x64'
Expand Down
2 changes: 2 additions & 0 deletions bitcoinlib/config/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ def _set_opcodes():
opcodenames = _set_opcodes()

OP_N_CODES = range(op.op_1, op.op_16)

opcodeints = dict((v,k) for k,v in opcodenames.items())
33 changes: 22 additions & 11 deletions bitcoinlib/data/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,17 +307,6 @@
"denominator": 1,
"network_overrides": null
},
"blockcypher.testnet": {
"provider": "blockcypher",
"network": "testnet",
"client_class": "BlockCypher",
"provider_coin_id": "",
"url": "https://api.blockcypher.com/v1/btc/test3/",
"api_key": "",
"priority": 10,
"denominator": 1,
"network_overrides": null
},
"mempool": {
"provider": "mempool",
"network": "bitcoin",
Expand All @@ -339,5 +328,27 @@
"priority": 10,
"denominator": 1,
"network_overrides": null
},
"mempool.litecoin": {
"provider": "mempool",
"network": "litecoin_testnet",
"client_class": "MempoolClient",
"provider_coin_id": "",
"url": "https://litecoinspace.org/api/",
"api_key": "",
"priority": 11,
"denominator": 1,
"network_overrides": null
},
"mempool.litecoin_testnet": {
"provider": "mempool",
"network": "litecoin_testnet",
"client_class": "MempoolClient",
"provider_coin_id": "",
"url": "https://litecoinspace.org/testnet/api/",
"api_key": "",
"priority": 11,
"denominator": 1,
"network_overrides": null
}
}
4 changes: 3 additions & 1 deletion bitcoinlib/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def deserialize_address(address, encoding=None, network=None):
If more networks and or script types are found you can find these in the 'networks' field.
>>> deserialize_address('1Khyc5eUddbhYZ8bEZi9wiN8TrmQ8uND4j')
{'address': '1Khyc5eUddbhYZ8bEZi9wiN8TrmQ8uND4j', 'encoding': 'base58', 'public_key_hash': 'cd322766c02e7c37c3e3f9b825cd41ffbdcd17d7', 'public_key_hash_bytes': b"\\xcd2'f\\xc0.|7\\xc3\\xe3\\xf9\\xb8%\\xcdA\\xff\\xbd\\xcd\\x17\\xd7", 'prefix': b'\\x00', 'network': 'bitcoin', 'script_type': 'p2pkh', 'witness_type': 'legacy', 'networks': ['bitcoin', 'regtest'], 'checksum': b'\xcf\xa4I0', 'witver': None}
{'address': '1Khyc5eUddbhYZ8bEZi9wiN8TrmQ8uND4j', 'encoding': 'base58', 'public_key_hash': 'cd322766c02e7c37c3e3f9b825cd41ffbdcd17d7', 'public_key_hash_bytes': b"\\xcd2'f\\xc0.|7\\xc3\\xe3\\xf9\\xb8%\\xcdA\\xff\\xbd\\xcd\\x17\\xd7", 'prefix': b'\\x00', 'network': 'bitcoin', 'script_type': 'p2pkh', 'witness_type': 'legacy', 'networks': ['bitcoin', 'regtest'], 'checksum': b'\\xcf\\xa4I0', 'witver': None, 'raw': b"\\x00\\xcd2'f\\xc0.|7\\xc3\\xe3\\xf9\\xb8%\\xcdA\\xff\\xbd\\xcd\\x17\\xd7\\xcf\\xa4I0"}
:param address: A base58 or bech32 encoded address
:type address: str
Expand Down Expand Up @@ -297,6 +297,7 @@ def deserialize_address(address, encoding=None, network=None):
'networks': networks,
'checksum': checksum,
'witver': None,
'raw': address_bytes,
}
if encoding == 'bech32' or encoding is None:
try:
Expand All @@ -322,6 +323,7 @@ def deserialize_address(address, encoding=None, network=None):
'networks': networks,
'checksum': addr_bech32_checksum(address),
'witver': witver,
'raw': pkh_incl,
}
except EncodingError as err:
raise EncodingError("Invalid address %s: %s" % (address, err))
Expand Down
23 changes: 20 additions & 3 deletions bitcoinlib/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def __init__(self, commands=None, message=None, script_types='', is_locking=True
:param sigs_required: Nubmer of signatures required to create multisig script
:type sigs_required: int
:param redeemscript: Provide redeemscript to create a new (multisig) script
:type redeemscript: bytes
:type redeemscript: bytes, Script
:param hash_type: Specific script hash type, default is SIGHASH_ALL
:type hash_type: int
"""
Expand All @@ -207,7 +207,12 @@ def __init__(self, commands=None, message=None, script_types='', is_locking=True
self._blueprint = blueprint if blueprint else []
self.env_data = {} if not env_data else env_data
self.sigs_required = sigs_required if sigs_required else len(self.keys) if len(self.keys) else 1
self.redeemscript = redeemscript
self.redeemscript_obj = None
if isinstance(redeemscript, Script):
self.redeemscript_obj = redeemscript
self.redeemscript = redeemscript.as_bytes()
else:
self.redeemscript = redeemscript
self.public_hash = public_hash
self.hash_type = hash_type

Expand Down Expand Up @@ -312,6 +317,7 @@ def parse_bytesio(cls, script, message=None, env_data=None, data_length=0, is_lo
keys = []
blueprint = []
redeemscript = b''
redeemscript_obj = None
sigs_required = None
# hash_type = SIGHASH_ALL # todo: check
hash_type = None
Expand Down Expand Up @@ -373,7 +379,8 @@ def parse_bytesio(cls, script, message=None, env_data=None, data_length=0, is_lo
blueprint += [s2.blueprint]
keys += s2.keys
signatures += s2.signatures
redeemscript = s2.redeemscript
redeemscript_obj = s2
redeemscript = s2.as_bytes()
sigs_required = s2.sigs_required
except (ScriptError, IndexError):
blueprint.append('data-%d' % len(data))
Expand Down Expand Up @@ -419,6 +426,7 @@ def parse_bytesio(cls, script, message=None, env_data=None, data_length=0, is_lo
# Extract extra information from script data
for st in s.script_types[:1]:
if st == 'multisig':
redeemscript_obj = s
s.redeemscript = s.as_bytes()
s.sigs_required = s.commands[0] - 80
if s.sigs_required > len(keys):
Expand All @@ -434,6 +442,7 @@ def parse_bytesio(cls, script, message=None, env_data=None, data_length=0, is_lo
elif st == 'p2pkh' and len(s.commands) > 2:
s.public_hash = s.commands[2]
s.redeemscript = redeemscript if redeemscript else s.redeemscript
s.redeemscript_obj = redeemscript_obj
if s.redeemscript and 'redeemscript' not in s.env_data:
s.env_data['redeemscript'] = s.redeemscript

Expand Down Expand Up @@ -1011,6 +1020,14 @@ def op_0notequal(self):
return True

def op_add(self):
"""
Add the top 2 numbers of the stack and appends the result on the top of the stack.
Fails if the top 2 items are not arithmetic or if there are not enough items on the stack.
:return bool: Operation succeeded
"""

if not self.is_arithmetic(2):
return False
self.append(encode_num(self.pop_as_number() + self.pop_as_number()))
Expand Down
2 changes: 1 addition & 1 deletion bitcoinlib/services/bitaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def _parse_transaction(self, tx):
for n, ti in tx['vIn'].items():
if t.coinbase:
t.add_input(prev_txid=ti['txId'], output_n=ti['vOut'], unlocking_script=ti['scriptSig'],
sequence=ti['sequence'], index_n=int(n), value=0)
sequence=ti['sequence'], index_n=int(n), value=0, witness_type=witness_type)
else:
t.add_input(prev_txid=ti['txId'], output_n=ti['vOut'], unlocking_script=ti['scriptSig'],
locking_script=ti['scriptPubKey'], witnesses=ti.get('txInWitness', []),
Expand Down
4 changes: 2 additions & 2 deletions bitcoinlib/services/bitgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#

import logging
from datetime import datetime
from datetime import datetime, timezone
from bitcoinlib.main import MAX_TRANSACTIONS
from bitcoinlib.services.baseclient import BaseClient, ClientError
from bitcoinlib.transactions import Transaction
Expand Down Expand Up @@ -72,7 +72,7 @@ def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
'size': 0,
'value': int(round(utxo['value'] * self.units, 0)),
'script': utxo['script'],
'date': datetime.strptime(utxo['date'], "%Y-%m-%dT%H:%M:%S.%fZ")
'date': datetime.strptime(utxo['date'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
}
)
total = res['total']
Expand Down
4 changes: 2 additions & 2 deletions bitcoinlib/services/blockchair.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
'size': 0,
'value': utxo['value'],
'script': utxo['script_hex'],
'date': datetime.strptime(utxo['time'], "%Y-%m-%d %H:%M:%S")
'date': datetime.strptime(utxo['time'], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
})
if not len(res['data']) or len(res['data']) < REQUEST_LIMIT:
break
Expand All @@ -114,7 +114,7 @@ def gettransaction(self, tx_id):
input_total = tx['input_total']
t = Transaction(locktime=tx['lock_time'], version=tx['version'], network=self.network,
fee=tx['fee'], size=tx['size'], txid=tx['hash'],
date=None if not confirmations else datetime.strptime(tx['time'], "%Y-%m-%d %H:%M:%S"),
date=None if not confirmations else datetime.strptime(tx['time'], "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc),
confirmations=confirmations, block_height=tx['block_id'] if tx['block_id'] > 0 else None,
status=status, input_total=input_total, coinbase=tx['is_coinbase'],
output_total=tx['output_total'], witness_type=witness_type)
Expand Down
6 changes: 3 additions & 3 deletions bitcoinlib/services/blockcypher.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
tdate = None
if 'confirmed' in tx:
try:
tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%SZ")
tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
except ValueError:
tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%S.%fZ")
tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
transactions.append({
'address': address.address_orig,
'txid': tx['tx_hash'],
Expand All @@ -94,7 +94,7 @@ def gettransaction(self, txid):
t = Transaction.parse_hex(tx['hex'], strict=self.strict, network=self.network)
if tx['confirmations']:
t.status = 'confirmed'
t.date = datetime.strptime(tx['confirmed'][:19], "%Y-%m-%dT%H:%M:%S")
t.date = datetime.strptime(tx['confirmed'][:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
else:
t.status = 'unconfirmed'
t.confirmations = tx['confirmations']
Expand Down
12 changes: 4 additions & 8 deletions bitcoinlib/services/blocksmurfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def getbalance(self, addresslist):
balance += res['balance']
return balance

# TODO: fix blocksmurfer api
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
res = self.compose_request('utxos', address, variables={'after_txid': after_txid})
self.latest_block = self.blockcount() if not self.latest_block else self.latest_block
Expand All @@ -75,7 +74,7 @@ def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS):
'size': u['size'],
'value': u['value'],
'script': u['script'],
'date': datetime.strptime(u['date'][:19], "%Y-%m-%dT%H:%M:%S")
'date': datetime.strptime(u['date'][:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
})
return utxos[:limit]

Expand All @@ -85,10 +84,8 @@ def _parse_transaction(self, tx, block_height=None):
if block_height and not confirmations and tx['status'] == 'confirmed':
self.latest_block = self.blockcount() if not self.latest_block else self.latest_block
confirmations = self.latest_block - block_height
# FIXME: Blocksmurfer returns 'date' or 'time', should be consistent
tx_date = None if not tx.get('date') else datetime.strptime(tx['date'], "%Y-%m-%dT%H:%M:%S")
if not tx_date and 'time' in tx:
tx_date = datetime.fromtimestamp(tx['time'], timezone.utc)
tx_date = None if not tx.get('date') else (
datetime.strptime(tx['date'][:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc))
t = Transaction(locktime=tx['locktime'], version=tx['version'], network=self.network,
fee=tx['fee'], size=tx['size'], txid=tx['txid'], date=tx_date, input_total=tx['input_total'],
output_total=tx['output_total'], confirmations=confirmations, block_height=block_height,
Expand All @@ -100,8 +97,7 @@ def _parse_transaction(self, tx, block_height=None):
public_hash=bytes.fromhex(ti['public_hash']), address=ti['address'],
witness_type=ti['witness_type'], locktime_cltv=ti['locktime_cltv'],
locktime_csv=ti['locktime_csv'], signatures=ti['signatures'], compressed=ti['compressed'],
encoding=ti['encoding'], locking_script=ti['script_code'],
sigs_required=ti['sigs_required'], sequence=ti['sequence'],
locking_script=ti['locking_script'], sigs_required=ti['sigs_required'], sequence=ti['sequence'],
witnesses=[bytes.fromhex(w) for w in ti['witnesses']], script_type=ti['script_type'],
strict=self.strict)
for to in tx['outputs']:
Expand Down
14 changes: 11 additions & 3 deletions bitcoinlib/services/blockstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,20 @@ def _parse_transaction(self, tx):
if tx['status']['confirmed']:
status = 'confirmed'
fee = None if 'fee' not in tx else tx['fee']
witness_type = 'legacy'
if tx['size'] * 4 > tx['weight']:
witness_type = 'segwit'

t = Transaction(locktime=tx['locktime'], version=tx['version'], network=self.network,
fee=fee, size=tx['size'], txid=tx['txid'],
date=None if 'block_time' not in tx['status'] else
datetime.fromtimestamp(tx['status']['block_time'], timezone.utc),
confirmations=confirmations, block_height=block_height, status=status,
coinbase=tx['vin'][0]['is_coinbase'])
coinbase=tx['vin'][0]['is_coinbase'], witness_type=witness_type)
index_n = 0
for ti in tx['vin']:
if tx['vin'][0]['is_coinbase']:
t.add_input(prev_txid=ti['txid'], output_n=ti['vout'], index_n=index_n,
t.add_input(prev_txid=ti['txid'], output_n=ti['vout'], index_n=index_n, witness_type=witness_type,
unlocking_script=ti['scriptsig'], value=0, sequence=ti['sequence'], strict=self.strict)
else:
witnesses = []
Expand Down Expand Up @@ -157,7 +161,11 @@ def gettransactions(self, address, after_txid='', limit=MAX_TRANSACTIONS):
if len(prtxs) > limit:
break
txs = []
for tx in prtxs[::-1]:
if len(set([x['status'].get('block_height', '-1') for x in prtxs])) > 1:
prtxs.sort(key=lambda x: x['status'].get('block_height', '-1'))
else:
prtxs = prtxs[::-1]
for tx in prtxs:
t = self._parse_transaction(tx)
if t:
txs.append(t)
Expand Down
9 changes: 5 additions & 4 deletions bitcoinlib/services/mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ def _parse_transaction(self, tx):
confirmations = self.latest_block - block_height + 1
tx_date = datetime.fromtimestamp(tx['status']['block_time'], timezone.utc)
status = 'confirmed'
witness_type = 'legacy'
if tx['size'] * 4 > tx['weight']:
witness_type = 'segwit'

t = Transaction(locktime=tx['locktime'], version=tx['version'], network=self.network, block_height=block_height,
fee=tx['fee'], size=tx['size'], txid=tx['txid'], date=tx_date, confirmations=confirmations,
status=status, coinbase=tx['vin'][0]['is_coinbase'])
status=status, coinbase=tx['vin'][0]['is_coinbase'], witness_type=witness_type)
for ti in tx['vin']:
if ti['is_coinbase']:
t.add_input(prev_txid=ti['txid'], output_n=ti['vout'], unlocking_script=ti['scriptsig'], value=0,
sequence=ti['sequence'], strict=self.strict)
sequence=ti['sequence'], strict=self.strict, witness_type=witness_type)
else:
t.add_input(prev_txid=ti['txid'], output_n=ti['vout'],
unlocking_script=ti['scriptsig'], value=ti['prevout']['value'],
Expand All @@ -118,8 +121,6 @@ def _parse_transaction(self, tx):
for to in tx['vout']:
t.add_output(value=to['value'], address=to.get('scriptpubkey_address', ''), spent=None,
lock_script=to['scriptpubkey'], strict=self.strict)
if 'segwit' in [i.witness_type for i in t.inputs] or 'p2sh-segwit' in [i.witness_type for i in t.inputs]:
t.witness_type = 'segwit'
t.update_totals()
return t

Expand Down
Loading

0 comments on commit f7acb81

Please sign in to comment.