Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 30 additions & 6 deletions counterparty-core/counterpartycore/lib/messages/sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,34 @@
FLAGS_ALL = FLAG_BINARY_MEMO | FLAG_BALANCES | FLAG_OWNERSHIP


def get_total_fee(db, source, block_index):
def get_sweep_balances(db, source, block_index):
balances = ledger.balances.get_address_balances(db, source)
if protocol.enabled("sweep_skip_zero_balances", block_index):
return [balance for balance in balances if balance["quantity"] > 0]
return balances


def get_total_fee(db, source, block_index, flags=None):
total_fee = ANTISPAM_FEE
antispamfee = protocol.get_value_by_block_index("sweep_antispam_fee", block_index) * config.UNIT
if antispamfee > 0:
balances_count = ledger.balances.get_balances_count(db, source)[0]["cnt"]
issuances_count = ledger.issuances.get_issuances_count(db, source)
if protocol.enabled("sweep_skip_zero_balances", block_index):
if (
not isinstance(flags, int)
or isinstance(flags, bool)
or flags > FLAGS_ALL
or not flags & (FLAG_BALANCES | FLAG_OWNERSHIP)
):
flags = FLAGS_ALL
balances_count = len(get_sweep_balances(db, source, block_index))
if not flags & FLAG_BALANCES:
balances_count = 0
issuances_count = ledger.issuances.get_issuances_count(db, source)
if not flags & FLAG_OWNERSHIP:
issuances_count = 0
else:
balances_count = ledger.balances.get_balances_count(db, source)[0]["cnt"]
issuances_count = ledger.issuances.get_issuances_count(db, source)
total_fee = int(balances_count * antispamfee * 2 + issuances_count * antispamfee * 4)
return total_fee

Expand All @@ -48,7 +70,7 @@ def validate(db, source, destination, flags, memo, block_index):

result = ledger.balances.get_balance(db, source, "XCP")

total_fee = get_total_fee(db, source, block_index)
total_fee = get_total_fee(db, source, block_index, flags)

if result < total_fee:
problems.append(
Expand Down Expand Up @@ -219,7 +241,9 @@ def parse(db, tx, message):
* config.UNIT
)

if antispamfee > 0:
if antispamfee > 0 and (
total_fee > 0 or not protocol.enabled("sweep_skip_zero_balances", tx["block_index"])
):
ledger.events.debit(
db,
tx["source"],
Expand All @@ -244,7 +268,7 @@ def parse(db, tx, message):
status = "invalid: insufficient balance for antispam fee for sweep"

if status == "valid":
balances = ledger.balances.get_address_balances(db, tx["source"])
balances = get_sweep_balances(db, tx["source"], tx["block_index"])

if flags & FLAG_BALANCES:
for balance in balances:
Expand Down
9 changes: 9 additions & 0 deletions counterparty-core/counterpartycore/protocol_changes.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,15 @@
"testnet4_block_index": 0,
"signet_block_index": 0
},
"sweep_skip_zero_balances": {
"minimum_version_major": 11,
"minimum_version_minor": 1,
"minimum_version_revision": 0,
"block_index": 952800,
"testnet3_block_index": 4961300,
"testnet4_block_index": 136300,
"signet_block_index": 310300
},
"dispenser_origin_permission_extended": {
"minimum_version_major": 9,
"minimum_version_minor": 59,
Expand Down
114 changes: 103 additions & 11 deletions counterparty-core/counterpartycore/test/units/messages/sweep_test.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import cbor2
import pytest
from counterpartycore.lib import config, exceptions
from counterpartycore.lib import config, exceptions, ledger
from counterpartycore.lib.messages import sweep
from counterpartycore.test.mocks.counterpartydbs import ProtocolChangesDisabled


def test_validate(ledger_db, defaults, monkeypatch):
assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 1, None, config.BURN_START
) == ([], 800000)
) == ([], 400000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 2, None, config.BURN_START
) == ([], 800000)
) == ([], 400000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 3, None, config.BURN_START
) == ([], 800000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 1, "test", config.BURN_START
) == ([], 800000)
) == ([], 400000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 1, b"test", config.BURN_START
) == ([], 800000)
) == ([], 400000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 0, None, config.BURN_START
) == (["must specify which kind of transfer in flags"], 800000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][6], 1, None, config.BURN_START
) == (["destination cannot be the same as source"], 800000)
) == (["destination cannot be the same as source"], 400000)

assert sweep.validate(
ledger_db, defaults["addresses"][6], defaults["addresses"][5], 8, None, config.BURN_START
Expand All @@ -45,11 +45,11 @@ def test_validate(ledger_db, defaults, monkeypatch):
1,
"012345678900123456789001234567890012345",
config.BURN_START,
) == (["memo too long"], 800000)
) == (["memo too long"], 400000)

monkeypatch.setattr(
"counterpartycore.lib.messages.sweep.get_total_fee",
lambda db, source, block_index: 1000000000000,
lambda db, source, block_index, flags=None: 1000000000000,
)

assert sweep.validate(
Expand Down Expand Up @@ -111,7 +111,7 @@ def test_compose(ledger_db, defaults, monkeypatch):

monkeypatch.setattr(
"counterpartycore.lib.messages.sweep.get_total_fee",
lambda db, source, block_index: 1000000000000,
lambda db, source, block_index, flags=None: 1000000000000,
)

with pytest.raises(
Expand Down Expand Up @@ -232,7 +232,7 @@ def test_parse_flag_1(ledger_db, blockchain_mock, defaults, test_helpers, curren
"block_index": current_block_index,
"calling_function": "sweep",
"event": tx["tx_hash"],
"quantity": 91391199693,
"quantity": 91397199693,
},
},
{
Expand All @@ -243,7 +243,7 @@ def test_parse_flag_1(ledger_db, blockchain_mock, defaults, test_helpers, curren
"asset": "XCP",
"block_index": current_block_index,
"event": tx["tx_hash"],
"quantity": 91391199693,
"quantity": 91397199693,
},
},
{
Expand Down Expand Up @@ -279,6 +279,98 @@ def test_parse_flag_1(ledger_db, blockchain_mock, defaults, test_helpers, curren
)


def add_zero_balance(ledger_db, address, asset, tx_index, event):
ledger.events.credit(ledger_db, address, asset, 1, tx_index, action="test setup", event=event)
ledger.events.debit(ledger_db, address, asset, 1, tx_index, action="test setup", event=event)


def test_parse_skips_zero_quantity_balances(ledger_db, blockchain_mock, defaults):
source = defaults["addresses"][0]
destination = defaults["addresses"][1]
tx = blockchain_mock.dummy_tx(ledger_db, source)
add_zero_balance(ledger_db, source, "ZEROASSET", tx["tx_index"], "zero-balance-setup")

message = b"\x83U\x01\x8dj\xe8\xa3\xb3\x81f1\x18\xb4\xe1\xef\xf4\xcf\xc7\xd0\x95M\xd6\xec\x01@"
sweep.parse(ledger_db, tx, message)

zero_sweep_records = ledger_db.execute(
"""
SELECT COUNT(*) AS count
FROM (
SELECT quantity FROM credits
WHERE address = ? AND asset = ? AND calling_function = ? AND event = ?
UNION ALL
SELECT quantity FROM debits
WHERE address = ? AND asset = ? AND action = ? AND event = ?
)
""",
(
destination,
"ZEROASSET",
"sweep",
tx["tx_hash"],
source,
"ZEROASSET",
"sweep",
tx["tx_hash"],
),
).fetchone()["count"]

assert zero_sweep_records == 0


def test_parse_zero_quantity_balances_legacy_path(ledger_db, blockchain_mock, defaults):
source = defaults["addresses"][0]
destination = defaults["addresses"][1]
tx = blockchain_mock.dummy_tx(ledger_db, source)
add_zero_balance(ledger_db, source, "ZEROASSET", tx["tx_index"], "zero-balance-setup")

message = b"\x83U\x01\x8dj\xe8\xa3\xb3\x81f1\x18\xb4\xe1\xef\xf4\xcf\xc7\xd0\x95M\xd6\xec\x01@"
with ProtocolChangesDisabled(["sweep_skip_zero_balances"]):
sweep.parse(ledger_db, tx, message)

zero_sweep_records = ledger_db.execute(
"""
SELECT COUNT(*) AS count
FROM (
SELECT quantity FROM credits
WHERE address = ? AND asset = ? AND calling_function = ? AND event = ?
UNION ALL
SELECT quantity FROM debits
WHERE address = ? AND asset = ? AND action = ? AND event = ?
)
""",
(
destination,
"ZEROASSET",
"sweep",
tx["tx_hash"],
source,
"ZEROASSET",
"sweep",
tx["tx_hash"],
),
).fetchone()["count"]

assert zero_sweep_records == 2


def test_total_fee_ignores_zero_quantity_balances(ledger_db, blockchain_mock, defaults):
source = defaults["addresses"][0]
tx = blockchain_mock.dummy_tx(ledger_db, source)
fee_before = sweep.get_total_fee(ledger_db, source, tx["block_index"], sweep.FLAG_BALANCES)

add_zero_balance(ledger_db, source, "ZEROASSET", tx["tx_index"], "zero-balance-setup")

fee_after = sweep.get_total_fee(ledger_db, source, tx["block_index"], sweep.FLAG_BALANCES)
assert fee_after == fee_before

with ProtocolChangesDisabled(["sweep_skip_zero_balances"]):
legacy_fee = sweep.get_total_fee(ledger_db, source, tx["block_index"], sweep.FLAG_BALANCES)

assert legacy_fee > fee_after


def test_parse_flag_2(ledger_db, blockchain_mock, defaults, test_helpers):
with ProtocolChangesDisabled("taproot_support"):
tx = blockchain_mock.dummy_tx(ledger_db, defaults["addresses"][6])
Expand Down