Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composer V2 #2873

Merged
merged 67 commits into from
Jan 3, 2025
Merged
Changes from 1 commit
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
00a2fa3
Introduce composer.py
Ouziel Sep 27, 2024
b4f2300
Add unit tests
Ouziel Sep 30, 2024
c6e0438
Merge branch 'develop' into composer
Ouziel Oct 3, 2024
106405c
fix test
Ouziel Oct 3, 2024
32a168d
tweaks
Ouziel Oct 3, 2024
5a8ea0a
Migrate to 'bitcoinutils' which supports taproot addresses
Ouziel Oct 3, 2024
45fa9fc
migrate get vsize
Ouziel Oct 3, 2024
d3a9a70
Merge branch 'develop' into composer
Ouziel Oct 3, 2024
59ab8ab
more unit tests
Ouziel Oct 3, 2024
0289a65
Merge branch 'develop' into composer
Ouziel Dec 19, 2024
d577e98
fix pytest
Ouziel Dec 19, 2024
b9ec24e
Composer v2 progress
Ouziel Dec 23, 2024
9c1b06d
composer v2 progress
Ouziel Dec 23, 2024
3e0d203
Add utxo locks
Ouziel Dec 24, 2024
641bc8a
Add more_outputs params
Ouziel Dec 24, 2024
920a193
Tweaks
Ouziel Dec 24, 2024
9f55b5f
ensure unspent_list is complete
Ouziel Dec 24, 2024
ff04ff7
tweaks
Ouziel Dec 24, 2024
88a6953
Correctly set has_segwit parameter
Ouziel Dec 24, 2024
511e84b
backward compatibilities
Ouziel Dec 24, 2024
c14dd45
tweaks
Ouziel Dec 25, 2024
5ab194a
Merge branch 'develop' into composer
Ouziel Dec 25, 2024
466b77f
fixes
Ouziel Dec 25, 2024
3d3326a
More fixes; Regtest OK
Ouziel Dec 26, 2024
c74f83e
Merge branch 'develop' into composer
Ouziel Dec 26, 2024
23dfc8d
More fixes; property tests OK
Ouziel Dec 26, 2024
53f9500
Cleaning; no need to pass inputs_set and keys
Ouziel Dec 26, 2024
65cb56e
Delete old code; progress in fixing tests
Ouziel Dec 27, 2024
1a1e8dd
Merge branch 'develop' into composer
Ouziel Dec 27, 2024
327f762
Merge branch 'develop' into composer
Ouziel Dec 28, 2024
b654b2c
pytest fixing progress
Ouziel Dec 28, 2024
feda13e
fix typo; pytest fixing progress
Ouziel Dec 28, 2024
b8a0676
progress
Ouziel Dec 28, 2024
1fc79e6
pytest fixing progress
Ouziel Dec 29, 2024
15a44f6
fix existing pytest
Ouziel Dec 29, 2024
64f25d8
Add function to check transaction sanity
Ouziel Dec 29, 2024
15d2cd6
cleaning
Ouziel Dec 29, 2024
c4a120a
More fixes, more tweaks
Ouziel Dec 30, 2024
95cf95e
fixes; Clean construct_params; add warnings
Ouziel Dec 30, 2024
7e07197
restore pubkeys parameters
Ouziel Dec 30, 2024
4d8c3cf
restore and fix test with p2sh source
Ouziel Dec 30, 2024
5c014e7
clean debug
Ouziel Dec 30, 2024
5c84f45
fix get pubkeys
Ouziel Dec 30, 2024
3f7a66e
tweak error messges for invalid utxos
Ouziel Dec 30, 2024
58bb8da
fix typo
Ouziel Dec 31, 2024
586b87a
tweak with balance checking
Ouziel Dec 31, 2024
e2b3745
re-enable and fix COMPOSER_VECTOR; fix regtest; fixes and tweaks
Ouziel Dec 31, 2024
419fe65
tweaks
Ouziel Dec 31, 2024
16d874b
exclude silently utxo with balances when inputs_set is not provided; …
Ouziel Dec 31, 2024
05caf3e
fix missing params and name in compose result
Ouziel Dec 31, 2024
2abe695
Don't retry RPC calls on API requests
Ouziel Dec 31, 2024
44a6c2b
fix pytest
Ouziel Dec 31, 2024
15ff5ac
Merge branch 'develop' into composer
Ouziel Dec 31, 2024
405a506
more tests; more fixes
Ouziel Dec 31, 2024
0d30d8f
More tests
Ouziel Jan 1, 2025
92409e7
more tests
Ouziel Jan 1, 2025
574cb0c
last unit tests for composer.py
Ouziel Jan 2, 2025
2d527ae
update release notes
Ouziel Jan 2, 2025
b3aa413
fix pytest
Ouziel Jan 2, 2025
3f56246
Insert dummy signatures before needed fee calculation
Ouziel Jan 2, 2025
9c3fc90
Calculate fees with adjusted vsize
Ouziel Jan 3, 2025
59348a8
fix tests
Ouziel Jan 3, 2025
d1ed85d
Add utxo_value parameter for attach and move
Ouziel Jan 3, 2025
5854ac3
Tweak docs; more tests; fix regtest
Ouziel Jan 3, 2025
acfbf06
more sigops tests
Ouziel Jan 3, 2025
aa4fca3
fix typo
Ouziel Jan 3, 2025
35d1a46
fix typo
Ouziel Jan 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
More fixes; Regtest OK
Ouziel committed Dec 26, 2024
commit 3d3326a76bcd50d3a4a272d151988329fb2f77ba
5,289 changes: 2,715 additions & 2,574 deletions apiary.apib

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions counterparty-core/counterpartycore/lib/api/compose.py
Original file line number Diff line number Diff line change
@@ -153,6 +153,11 @@
False,
"Verbose mode",
),
"use_all_inputs_set": (
bool,
False,
"Use all inputs set",
),
}


47 changes: 35 additions & 12 deletions counterparty-core/counterpartycore/lib/composer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import binascii
import hashlib
import inspect
import logging
import string
import sys
import time
@@ -24,6 +25,8 @@

MAX_INPUTS_SET = 100

logger = logging.getLogger(config.LOGGER_NAME)


def get_script(address):
try:
@@ -36,7 +39,7 @@
raise exceptions.ComposeError(f"Invalid address: {address}") from e


def get_output_type(script_pub_key):
asm = script.script_to_asm(script_pub_key)
if asm[0] == opcodes.OP_RETURN:
return "OP_RETURN"
@@ -97,7 +100,7 @@
encoding = "opreturn"
else:
encoding = "multisig"
if encoding not in ("multisig", "opreturn"):
raise exceptions.TransactionError(f"Not supported encoding: {encoding}")
return encoding

@@ -119,7 +122,7 @@
try:
PublicKey.from_hex(pubkey).get_address(compressed=True).to_string()
return True
except Exception:
return False


@@ -168,7 +171,7 @@
the ECDSA curve). Find the correct bytes by guessing randomly until the check
passes. (In parsing, these two bytes are ignored.)
"""
assert type(pubkey_start) == bytes # noqa: E721
assert len(pubkey_start) == 31 # One sign byte and one nonce byte required (for 33 bytes).

random_bytes = hashlib.sha256(
@@ -196,8 +199,9 @@
chunk_size = (33 * 2) - 1 - len(config.PREFIX) - 2 - 2
data_array = util.chunkify(data, chunk_size)
pubkey_pairs = []
for data_chunk in data_array:
for data_part in data_array:
# Get data (fake) public key.
data_chunk = config.PREFIX + data_part
pad_length = (33 * 2) - 1 - 2 - 2 - len(data_chunk)
assert pad_length >= 0
output_data = bytes([len(data_chunk)]) + data_chunk + (pad_length * b"\x00") # noqa: PLW2901
@@ -247,7 +251,7 @@
# if hex string we assume it is a script
if all(c in string.hexdigits for c in address_or_script):
has_segwit = is_segwit_output(address_or_script)
script = Script.from_raw(address_or_script, has_segwit=has_segwit)
else:
script = get_script(address_or_script)
except Exception as e:
@@ -299,7 +303,7 @@
return [utxo for utxo in unspent_list if not self.locked(f"{utxo['txid']}:{utxo['vout']}")]

def lock_inputs(self, inputs):
for input in inputs:
self.lock(f"{input.txid}:{input.txout_index}")


@@ -447,11 +451,22 @@


def utxo_to_address(db, utxo):
# first try with the database
sql = "SELECT utxo_address FROM balances WHERE utxo = ? LIMIT 1"
balance = db.execute(sql, (utxo,)).fetchone()
if balance:
return balance["utxo_address"]
raise exceptions.ComposeError(f"the address corresponding to {utxo} not found in the database")
# then try with Bitcoin Core
txid, vout = utxo.split(":")
try:
tx = backend.bitcoind.getrawtransaction(txid, verbose=True)
vout = int(vout)
address = tx["vout"][vout]["scriptPubKey"]["address"]
return address
except Exception as e:
raise exceptions.ComposeError(
f"the address corresponding to {utxo} not found in the database"
) from e


def ensure_utxo_is_first(utxo, unspent_list):
@@ -494,7 +509,7 @@
new_unspent_list.append(utxo)
continue
utxo_balances = ledger.get_utxo_balances(db, str_input)
with_balances = len(utxo_balances) > 0 and any(
[balance["quantity"] > 0 for balance in utxo_balances]
)
if exclude_utxos_with_balances and with_balances:
@@ -502,6 +517,7 @@
if with_balances:
raise exceptions.ComposeError(f"UTXO {str_input} has balances")
new_unspent_list.append(utxo)
return new_unspent_list


def prepare_unspent_list(db, source, construct_params):
@@ -539,11 +555,7 @@
unspent_list = UTXOLocks().filter_unspent_list(unspent_list)

# exclude utxos with balances if needed
filter_utxos_with_balances(db, source, unspent_list, construct_params)

# if source is an utxo, ensure it is first in the unspent list
if util.is_utxo_format(source):
unspent_list = ensure_utxo_is_first(source, unspent_list)
unspent_list = filter_utxos_with_balances(db, source, unspent_list, construct_params)

if len(unspent_list) == 0:
raise exceptions.ComposeError(
@@ -556,6 +568,10 @@
# sort unspent list by value
unspent_list = sorted(unspent_list, key=lambda x: x["value"], reverse=True)

# if source is an utxo, ensure it is first in the unspent list
if util.is_utxo_format(source):
unspent_list = ensure_utxo_is_first(source, unspent_list)

return unspent_list


@@ -568,14 +584,16 @@
if total_amount >= target_amount:
break
if total_amount < target_amount:
raise exceptions.ComposeError(f"Insufficient funds for the target amount: {target_amount}")
raise exceptions.ComposeError(
f"Insufficient funds for the target amount: {total_amount} < {target_amount}"
)
return selected_utxos


def utxos_to_txins(utxos: list):
inputs = []
for utxo in utxos:
input = TxInput(utxo["txid"], utxo["vout"])
inputs.append(input)
return inputs

@@ -627,11 +645,15 @@
outputs_total = sum(output.amount for output in outputs)

change_outputs = []
btc_in = 0
# try with one input and increase until the change is enough for the fee
input_count = 1
use_all_inputs_set = construct_params.get("use_all_inputs_set", False)
input_count = len(unspent_list) if use_all_inputs_set else 1
while True:
if input_count > len(unspent_list):
raise exceptions.ComposeError("Insufficient funds for the target amount")
raise exceptions.ComposeError(
f"Insufficient funds for the target amount: {btc_in} < {outputs_total}"
)

selected_utxos = unspent_list[:input_count]
inputs = utxos_to_txins(selected_utxos)
@@ -649,7 +671,7 @@

# if change is enough for exact_fee, add change output and break
if exact_fee is not None:
change_amount = change_amount - exact_fee
change_amount = int(change_amount - exact_fee)
if change_amount > regular_dust_size(construct_params):
change_outputs.append(TxOutput(change_amount, get_script(change_address)))
break
@@ -676,7 +698,7 @@
return inputs, btc_in, change_outputs


"""
# UNCHANGED

+encoding
@@ -698,6 +720,7 @@
+mutlisig_pubkey
+change_address
+more_outputs
+use_all_inputs_set

# DEPRECATED BUT STILL SUPPORTED

@@ -757,7 +780,7 @@
setup(config.NETWORK_NAME)

# prepare data
skip_validation = construct_params.get("validate", True)
skip_validation = not construct_params.get("validate", True)
source, destinations, data = compose_data(db, name, params, skip_validation=skip_validation)

if construct_params.get("return_only_data", False):
5 changes: 2 additions & 3 deletions counterparty-core/counterpartycore/lib/messages/dispense.py
Original file line number Diff line number Diff line change
@@ -49,7 +49,6 @@ def validate_compose(db, source, destination, quantity):
else:
try:
must_give = get_must_give(db, dispenser, quantity) * dispenser["give_quantity"]
logger.debug("must_give: %s", must_give)
if must_give > dispenser["give_remaining"]:
dispenser_problems.append(
f"dispenser for {dispenser['asset']} doesn't have enough asset to give"
@@ -60,6 +59,7 @@ def validate_compose(db, source, destination, quantity):
)
except exceptions.NoPriceError as e:
dispenser_problems.append(str(e))
logger.warning("dispenser_problems: %s", dispenser_problems)
# no error if at least one dispenser is valid
if len(dispenser_problems) == 0 and util.enabled("accept_only_one_valid_dispenser"):
return []
@@ -69,11 +69,10 @@ def validate_compose(db, source, destination, quantity):

def compose(db, source, destination, quantity, skip_validation: bool = False):
problems = validate_compose(db, source, destination, quantity)

if problems and not skip_validation:
raise exceptions.ComposeError(problems)

# not in validate() to not risk a protocol change

# create data
data = struct.pack(config.SHORT_TXTYPE_FORMAT, dispenser_module.DISPENSE_ID)
data += b"\x00"
Loading

Unchanged files with check annotations Beta

return rpc("decoderawtransaction", [rawtx])
def search_pubkey_in_transactions(pubkeyhash, tx_hashes):

Check warning

Code scanning / pylint

Too many branches (17/12). Warning

Too many branches (17/12).
for tx_hash in tx_hashes:

Check warning

Code scanning / pylint

Too many nested blocks (6/5). Warning

Too many nested blocks (6/5).

Check warning

Code scanning / pylint

Too many nested blocks (6/5). Warning

Too many nested blocks (6/5).
tx = getrawtransaction(tx_hash, True)
for vin in tx["vin"]:
if "txinwitness" in vin:
return unspents
def pubkey_from_tx(tx, pubkeyhash):

Check warning

Code scanning / pylint

Too many branches (15/12). Warning

Too many branches (15/12).
for vin in tx["vin"]:
if "witness" in vin:
if len(vin["witness"]) >= 2: