claim takeovers
This commit is contained in:
parent
6aa124592d
commit
e678df86e0
6 changed files with 855 additions and 776 deletions
|
@ -4,23 +4,24 @@ import typing
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
from concurrent.futures.thread import ThreadPoolExecutor
|
from concurrent.futures.thread import ThreadPoolExecutor
|
||||||
from typing import Optional, List, Tuple
|
from typing import Optional, List, Tuple, Set, DefaultDict, Dict
|
||||||
from prometheus_client import Gauge, Histogram
|
from prometheus_client import Gauge, Histogram
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import lbry
|
import lbry
|
||||||
from lbry.schema.claim import Claim
|
from lbry.schema.claim import Claim
|
||||||
from lbry.wallet.transaction import OutputScript, Output
|
from lbry.wallet.transaction import OutputScript, Output
|
||||||
from lbry.wallet.server.tx import Tx
|
from lbry.wallet.server.tx import Tx, TxOutput, TxInput
|
||||||
from lbry.wallet.server.daemon import DaemonError
|
from lbry.wallet.server.daemon import DaemonError
|
||||||
from lbry.wallet.server.hash import hash_to_hex_str, HASHX_LEN
|
from lbry.wallet.server.hash import hash_to_hex_str, HASHX_LEN
|
||||||
from lbry.wallet.server.util import chunks, class_logger
|
from lbry.wallet.server.util import chunks, class_logger
|
||||||
from lbry.crypto.hash import hash160
|
from lbry.crypto.hash import hash160
|
||||||
from lbry.wallet.server.leveldb import FlushData
|
from lbry.wallet.server.leveldb import FlushData
|
||||||
from lbry.wallet.server.db import DB_PREFIXES
|
from lbry.wallet.server.db import DB_PREFIXES
|
||||||
from lbry.wallet.server.db.claimtrie import StagedClaimtrieItem, StagedClaimtrieSupport, get_expiration_height
|
from lbry.wallet.server.db.claimtrie import StagedClaimtrieItem, StagedClaimtrieSupport
|
||||||
from lbry.wallet.server.db.claimtrie import get_takeover_name_ops, get_force_activate_ops, get_delay_for_name
|
from lbry.wallet.server.db.claimtrie import get_takeover_name_ops, StagedActivation
|
||||||
from lbry.wallet.server.db.prefixes import PendingClaimActivationPrefixRow, Prefixes
|
from lbry.wallet.server.db.claimtrie import get_remove_name_ops
|
||||||
from lbry.wallet.server.db.revertable import RevertablePut
|
from lbry.wallet.server.db.prefixes import ACTIVATED_SUPPORT_TXO_TYPE, ACTIVATED_CLAIM_TXO_TYPE
|
||||||
|
from lbry.wallet.server.db.prefixes import PendingActivationKey, PendingActivationValue
|
||||||
from lbry.wallet.server.udp import StatusServer
|
from lbry.wallet.server.udp import StatusServer
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from lbry.wallet.server.leveldb import LevelDB
|
from lbry.wallet.server.leveldb import LevelDB
|
||||||
|
@ -204,13 +205,19 @@ class BlockProcessor:
|
||||||
self.search_cache = {}
|
self.search_cache = {}
|
||||||
self.history_cache = {}
|
self.history_cache = {}
|
||||||
self.status_server = StatusServer()
|
self.status_server = StatusServer()
|
||||||
self.effective_amount_changes = defaultdict(list)
|
|
||||||
self.pending_claims: typing.Dict[Tuple[int, int], StagedClaimtrieItem] = {}
|
self.pending_claims: typing.Dict[Tuple[int, int], StagedClaimtrieItem] = {}
|
||||||
self.pending_claim_txos: typing.Dict[bytes, Tuple[int, int]] = {}
|
self.pending_claim_txos: typing.Dict[bytes, Tuple[int, int]] = {}
|
||||||
self.pending_supports = defaultdict(set)
|
self.pending_supports = defaultdict(list)
|
||||||
|
|
||||||
self.pending_support_txos = {}
|
self.pending_support_txos = {}
|
||||||
self.pending_abandon = set()
|
|
||||||
self.staged_pending_abandoned = {}
|
self.pending_removed_support = defaultdict(lambda: defaultdict(list))
|
||||||
|
self.staged_pending_abandoned: Dict[bytes, StagedClaimtrieItem] = {}
|
||||||
|
self.removed_active_support = defaultdict(list)
|
||||||
|
self.staged_activated_support = defaultdict(list)
|
||||||
|
self.staged_activated_claim = {}
|
||||||
|
self.pending_activated = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
async def run_in_thread_with_lock(self, func, *args):
|
async def run_in_thread_with_lock(self, func, *args):
|
||||||
# Run in a thread to prevent blocking. Shielded so that
|
# Run in a thread to prevent blocking. Shielded so that
|
||||||
|
@ -241,6 +248,7 @@ class BlockProcessor:
|
||||||
try:
|
try:
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
await self.run_in_thread_with_lock(self.advance_block, block)
|
await self.run_in_thread_with_lock(self.advance_block, block)
|
||||||
|
print("******************\n")
|
||||||
except:
|
except:
|
||||||
self.logger.exception("advance blocks failed")
|
self.logger.exception("advance blocks failed")
|
||||||
raise
|
raise
|
||||||
|
@ -363,7 +371,6 @@ class BlockProcessor:
|
||||||
|
|
||||||
return start, count
|
return start, count
|
||||||
|
|
||||||
|
|
||||||
# - Flushing
|
# - Flushing
|
||||||
def flush_data(self):
|
def flush_data(self):
|
||||||
"""The data for a flush. The lock must be taken."""
|
"""The data for a flush. The lock must be taken."""
|
||||||
|
@ -386,461 +393,448 @@ class BlockProcessor:
|
||||||
await self.flush(True)
|
await self.flush(True)
|
||||||
self.next_cache_check = time.perf_counter() + 30
|
self.next_cache_check = time.perf_counter() + 30
|
||||||
|
|
||||||
def check_cache_size(self):
|
def _add_claim_or_update(self, height: int, txo: 'Output', tx_hash: bytes, tx_num: int, nout: int,
|
||||||
"""Flush a cache if it gets too big."""
|
spent_claims: typing.Dict[bytes, typing.Tuple[int, int, str]]) -> List['RevertableOp']:
|
||||||
# Good average estimates based on traversal of subobjects and
|
|
||||||
# requesting size from Python (see deep_getsizeof).
|
|
||||||
one_MB = 1000*1000
|
|
||||||
utxo_cache_size = len(self.utxo_cache) * 205
|
|
||||||
db_deletes_size = len(self.db_deletes) * 57
|
|
||||||
hist_cache_size = len(self.db.hist_unflushed) * 180 + self.db.hist_unflushed_count * 4
|
|
||||||
# Roughly ntxs * 32 + nblocks * 42
|
|
||||||
tx_hash_size = ((self.tx_count - self.db.fs_tx_count) * 32
|
|
||||||
+ (self.height - self.db.fs_height) * 42)
|
|
||||||
utxo_MB = (db_deletes_size + utxo_cache_size) // one_MB
|
|
||||||
hist_MB = (hist_cache_size + tx_hash_size) // one_MB
|
|
||||||
|
|
||||||
self.logger.info('our height: {:,d} daemon: {:,d} '
|
|
||||||
'UTXOs {:,d}MB hist {:,d}MB'
|
|
||||||
.format(self.height, self.daemon.cached_height(),
|
|
||||||
utxo_MB, hist_MB))
|
|
||||||
|
|
||||||
# Flush history if it takes up over 20% of cache memory.
|
|
||||||
# Flush UTXOs once they take up 80% of cache memory.
|
|
||||||
cache_MB = self.env.cache_MB
|
|
||||||
if utxo_MB + hist_MB >= cache_MB or hist_MB >= cache_MB // 5:
|
|
||||||
return utxo_MB >= cache_MB * 4 // 5
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _add_claim_or_update(self, height: int, txo, script, tx_hash: bytes, idx: int, tx_count: int, txout,
|
|
||||||
spent_claims: typing.Dict[bytes, typing.Tuple[int, int, str]],
|
|
||||||
zero_delay_claims: typing.Dict[Tuple[str, bytes], Tuple[int, int]]) -> List['RevertableOp']:
|
|
||||||
try:
|
try:
|
||||||
claim_name = txo.normalized_name
|
claim_name = txo.normalized_name
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
claim_name = ''.join(chr(c) for c in txo.script.values['claim_name'])
|
claim_name = ''.join(chr(c) for c in txo.script.values['claim_name'])
|
||||||
if script.is_claim_name:
|
if txo.script.is_claim_name:
|
||||||
claim_hash = hash160(tx_hash + pack('>I', idx))[::-1]
|
claim_hash = hash160(tx_hash + pack('>I', nout))[::-1]
|
||||||
# print(f"\tnew lbry://{claim_name}#{claim_hash.hex()} ({tx_count} {txout.value})")
|
print(f"\tnew lbry://{claim_name}#{claim_hash.hex()} ({tx_num} {txo.amount})")
|
||||||
else:
|
else:
|
||||||
claim_hash = txo.claim_hash[::-1]
|
claim_hash = txo.claim_hash[::-1]
|
||||||
|
print(f"\tupdate lbry://{claim_name}#{claim_hash.hex()} ({tx_num} {txo.amount})")
|
||||||
signing_channel_hash = None
|
|
||||||
channel_claims_count = 0
|
|
||||||
activation_delay = self.db.get_activation_delay(claim_hash, claim_name)
|
|
||||||
if activation_delay == 0:
|
|
||||||
zero_delay_claims[(claim_name, claim_hash)] = tx_count, idx
|
|
||||||
# else:
|
|
||||||
# print("delay activation ", claim_name, activation_delay, height)
|
|
||||||
|
|
||||||
activation_height = activation_delay + height
|
|
||||||
try:
|
try:
|
||||||
signable = txo.signable
|
signable = txo.signable
|
||||||
except: # google.protobuf.message.DecodeError: Could not parse JSON.
|
except: # google.protobuf.message.DecodeError: Could not parse JSON.
|
||||||
signable = None
|
signable = None
|
||||||
|
|
||||||
|
ops = []
|
||||||
|
signing_channel_hash = None
|
||||||
if signable and signable.signing_channel_hash:
|
if signable and signable.signing_channel_hash:
|
||||||
signing_channel_hash = txo.signable.signing_channel_hash[::-1]
|
signing_channel_hash = txo.signable.signing_channel_hash[::-1]
|
||||||
# if signing_channel_hash in self.pending_claim_txos:
|
if txo.script.is_claim_name:
|
||||||
# pending_channel = self.pending_claims[self.pending_claim_txos[signing_channel_hash]]
|
root_tx_num, root_idx = tx_num, nout
|
||||||
# channel_claims_count = pending_channel.
|
|
||||||
|
|
||||||
channel_claims_count = self.db.get_claims_in_channel_count(signing_channel_hash) + 1
|
|
||||||
if script.is_claim_name:
|
|
||||||
support_amount = 0
|
|
||||||
root_tx_num, root_idx = tx_count, idx
|
|
||||||
else:
|
else:
|
||||||
if claim_hash not in spent_claims:
|
if claim_hash not in spent_claims:
|
||||||
print(f"\tthis is a wonky tx, contains unlinked claim update {claim_hash.hex()}")
|
print(f"\tthis is a wonky tx, contains unlinked claim update {claim_hash.hex()}")
|
||||||
return []
|
return []
|
||||||
support_amount = self.db.get_support_amount(claim_hash)
|
|
||||||
(prev_tx_num, prev_idx, _) = spent_claims.pop(claim_hash)
|
(prev_tx_num, prev_idx, _) = spent_claims.pop(claim_hash)
|
||||||
# print(f"\tupdate lbry://{claim_name}#{claim_hash.hex()} {tx_hash[::-1].hex()} {txout.value}")
|
print(f"\tupdate lbry://{claim_name}#{claim_hash.hex()} {tx_hash[::-1].hex()} {txo.amount}")
|
||||||
|
|
||||||
if (prev_tx_num, prev_idx) in self.pending_claims:
|
if (prev_tx_num, prev_idx) in self.pending_claims:
|
||||||
previous_claim = self.pending_claims.pop((prev_tx_num, prev_idx))
|
previous_claim = self.pending_claims.pop((prev_tx_num, prev_idx))
|
||||||
root_tx_num = previous_claim.root_claim_tx_num
|
root_tx_num, root_idx = previous_claim.root_claim_tx_num, previous_claim.root_claim_tx_position
|
||||||
root_idx = previous_claim.root_claim_tx_position
|
|
||||||
# prev_amount = previous_claim.amount
|
|
||||||
else:
|
else:
|
||||||
k, v = self.db.get_root_claim_txo_and_current_amount(
|
k, v = self.db.get_claim_txo(
|
||||||
claim_hash
|
claim_hash
|
||||||
)
|
)
|
||||||
root_tx_num = v.root_tx_num
|
root_tx_num, root_idx = v.root_tx_num, v.root_position
|
||||||
root_idx = v.root_position
|
activation = self.db.get_activation(prev_tx_num, prev_idx)
|
||||||
prev_amount = v.amount
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE, claim_hash, prev_tx_num, prev_idx, activation, claim_name, v.amount
|
||||||
|
).get_remove_activate_ops()
|
||||||
|
)
|
||||||
pending = StagedClaimtrieItem(
|
pending = StagedClaimtrieItem(
|
||||||
claim_name, claim_hash, txout.value, support_amount + txout.value,
|
claim_name, claim_hash, txo.amount, self.coin.get_expiration_height(height), tx_num, nout, root_tx_num,
|
||||||
activation_height, get_expiration_height(height), tx_count, idx, root_tx_num, root_idx,
|
root_idx, signing_channel_hash
|
||||||
signing_channel_hash, channel_claims_count
|
|
||||||
)
|
)
|
||||||
|
self.pending_claims[(tx_num, nout)] = pending
|
||||||
self.pending_claims[(tx_count, idx)] = pending
|
self.pending_claim_txos[claim_hash] = (tx_num, nout)
|
||||||
self.pending_claim_txos[claim_hash] = (tx_count, idx)
|
ops.extend(pending.get_add_claim_utxo_ops())
|
||||||
self.effective_amount_changes[claim_hash].append(txout.value)
|
|
||||||
return pending.get_add_claim_utxo_ops()
|
|
||||||
|
|
||||||
def _add_support(self, height, txo, txout, idx, tx_count,
|
|
||||||
zero_delay_claims: typing.Dict[Tuple[str, bytes], Tuple[int, int]]) -> List['RevertableOp']:
|
|
||||||
supported_claim_hash = txo.claim_hash[::-1]
|
|
||||||
|
|
||||||
claim_info = self.db.get_root_claim_txo_and_current_amount(
|
|
||||||
supported_claim_hash
|
|
||||||
)
|
|
||||||
controlling_claim = None
|
|
||||||
supported_tx_num = supported_position = supported_activation_height = supported_name = None
|
|
||||||
if claim_info:
|
|
||||||
k, v = claim_info
|
|
||||||
supported_name = v.name
|
|
||||||
supported_tx_num = k.tx_num
|
|
||||||
supported_position = k.position
|
|
||||||
supported_activation_height = v.activation
|
|
||||||
controlling_claim = self.db.get_controlling_claim(v.name)
|
|
||||||
|
|
||||||
if supported_claim_hash in self.effective_amount_changes:
|
|
||||||
# print(f"\tsupport claim {supported_claim_hash.hex()} {txout.value}")
|
|
||||||
self.effective_amount_changes[supported_claim_hash].append(txout.value)
|
|
||||||
self.pending_supports[supported_claim_hash].add((tx_count, idx))
|
|
||||||
self.pending_support_txos[(tx_count, idx)] = supported_claim_hash, txout.value
|
|
||||||
return StagedClaimtrieSupport(
|
|
||||||
supported_claim_hash, tx_count, idx, txout.value
|
|
||||||
).get_add_support_utxo_ops()
|
|
||||||
elif supported_claim_hash not in self.pending_claims and supported_claim_hash not in self.pending_abandon:
|
|
||||||
# print(f"\tsupport claim {supported_claim_hash.hex()} {txout.value}")
|
|
||||||
ops = []
|
|
||||||
if claim_info:
|
|
||||||
starting_amount = self.db.get_effective_amount(supported_claim_hash)
|
|
||||||
|
|
||||||
if supported_claim_hash not in self.effective_amount_changes:
|
|
||||||
self.effective_amount_changes[supported_claim_hash].append(starting_amount)
|
|
||||||
self.effective_amount_changes[supported_claim_hash].append(txout.value)
|
|
||||||
supported_amount = self._get_pending_effective_amount(supported_claim_hash)
|
|
||||||
|
|
||||||
if controlling_claim and supported_claim_hash != controlling_claim.claim_hash:
|
|
||||||
if supported_amount + txo.amount > self._get_pending_effective_amount(controlling_claim.claim_hash):
|
|
||||||
# takeover could happen
|
|
||||||
if (supported_name, supported_claim_hash) not in zero_delay_claims:
|
|
||||||
takeover_delay = get_delay_for_name(height - supported_activation_height)
|
|
||||||
if takeover_delay == 0:
|
|
||||||
zero_delay_claims[(supported_name, supported_claim_hash)] = (
|
|
||||||
supported_tx_num, supported_position
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ops.append(
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.pending_activation.pack_item(
|
|
||||||
height + takeover_delay, supported_tx_num, supported_position,
|
|
||||||
supported_claim_hash, supported_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.pending_supports[supported_claim_hash].add((tx_count, idx))
|
|
||||||
self.pending_support_txos[(tx_count, idx)] = supported_claim_hash, txout.value
|
|
||||||
# print(f"\tsupport claim {supported_claim_hash.hex()} {starting_amount}+{txout.value}={starting_amount + txout.value}")
|
|
||||||
ops.extend(StagedClaimtrieSupport(
|
|
||||||
supported_claim_hash, tx_count, idx, txout.value
|
|
||||||
).get_add_support_utxo_ops())
|
|
||||||
return ops
|
return ops
|
||||||
else:
|
|
||||||
print(f"\tthis is a wonky tx, contains unlinked support for non existent {supported_claim_hash.hex()}")
|
def _add_support(self, txo: 'Output', tx_num: int, nout: int) -> List['RevertableOp']:
|
||||||
|
supported_claim_hash = txo.claim_hash[::-1]
|
||||||
|
self.pending_supports[supported_claim_hash].append((tx_num, nout))
|
||||||
|
self.pending_support_txos[(tx_num, nout)] = supported_claim_hash, txo.amount
|
||||||
|
print(f"\tsupport claim {supported_claim_hash.hex()} +{txo.amount}")
|
||||||
|
return StagedClaimtrieSupport(
|
||||||
|
supported_claim_hash, tx_num, nout, txo.amount
|
||||||
|
).get_add_support_utxo_ops()
|
||||||
|
|
||||||
|
def _add_claim_or_support(self, height: int, tx_hash: bytes, tx_num: int, nout: int, txo: 'Output',
|
||||||
|
spent_claims: typing.Dict[bytes, Tuple[int, int, str]]) -> List['RevertableOp']:
|
||||||
|
if txo.script.is_claim_name or txo.script.is_update_claim:
|
||||||
|
return self._add_claim_or_update(height, txo, tx_hash, tx_num, nout, spent_claims)
|
||||||
|
elif txo.script.is_support_claim or txo.script.is_support_claim_data:
|
||||||
|
return self._add_support(txo, tx_num, nout)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _add_claim_or_support(self, height: int, tx_hash: bytes, tx_count: int, idx: int, txo, txout, script,
|
def _spend_support_txo(self, txin):
|
||||||
spent_claims: typing.Dict[bytes, Tuple[int, int, str]],
|
|
||||||
zero_delay_claims: typing.Dict[Tuple[str, bytes], Tuple[int, int]]) -> List['RevertableOp']:
|
|
||||||
if script.is_claim_name or script.is_update_claim:
|
|
||||||
return self._add_claim_or_update(height, txo, script, tx_hash, idx, tx_count, txout, spent_claims,
|
|
||||||
zero_delay_claims)
|
|
||||||
elif script.is_support_claim or script.is_support_claim_data:
|
|
||||||
return self._add_support(height, txo, txout, idx, tx_count, zero_delay_claims)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _remove_support(self, txin, zero_delay_claims):
|
|
||||||
txin_num = self.db.transaction_num_mapping[txin.prev_hash]
|
txin_num = self.db.transaction_num_mapping[txin.prev_hash]
|
||||||
supported_name = None
|
|
||||||
if (txin_num, txin.prev_idx) in self.pending_support_txos:
|
if (txin_num, txin.prev_idx) in self.pending_support_txos:
|
||||||
spent_support, support_amount = self.pending_support_txos.pop((txin_num, txin.prev_idx))
|
spent_support, support_amount = self.pending_support_txos.pop((txin_num, txin.prev_idx))
|
||||||
supported_name = self._get_pending_claim_name(spent_support)
|
|
||||||
self.pending_supports[spent_support].remove((txin_num, txin.prev_idx))
|
self.pending_supports[spent_support].remove((txin_num, txin.prev_idx))
|
||||||
else:
|
|
||||||
spent_support, support_amount = self.db.get_supported_claim_from_txo(txin_num, txin.prev_idx)
|
|
||||||
if spent_support:
|
|
||||||
supported_name = self._get_pending_claim_name(spent_support)
|
supported_name = self._get_pending_claim_name(spent_support)
|
||||||
|
print(f"\tspent support for lbry://{supported_name}#{spent_support.hex()}")
|
||||||
if spent_support and support_amount is not None and spent_support not in self.pending_abandon:
|
self.pending_removed_support[supported_name][spent_support].append((txin_num, txin.prev_idx))
|
||||||
controlling = self.db.get_controlling_claim(supported_name)
|
|
||||||
if controlling:
|
|
||||||
bid_queue = {
|
|
||||||
claim_hash: self._get_pending_effective_amount(claim_hash)
|
|
||||||
for claim_hash in self.db.get_claims_for_name(supported_name)
|
|
||||||
if claim_hash not in self.pending_abandon
|
|
||||||
}
|
|
||||||
bid_queue[spent_support] -= support_amount
|
|
||||||
sorted_claims = sorted(
|
|
||||||
list(bid_queue.keys()), key=lambda claim_hash: bid_queue[claim_hash], reverse=True
|
|
||||||
)
|
|
||||||
if controlling.claim_hash == spent_support and sorted_claims.index(controlling.claim_hash) > 0:
|
|
||||||
print("takeover due to abandoned support")
|
|
||||||
|
|
||||||
# print(f"\tspent support for {spent_support.hex()} -{support_amount} ({txin_num}, {txin.prev_idx}) {supported_name}")
|
|
||||||
if spent_support not in self.effective_amount_changes:
|
|
||||||
assert spent_support not in self.pending_claims
|
|
||||||
prev_effective_amount = self.db.get_effective_amount(spent_support)
|
|
||||||
self.effective_amount_changes[spent_support].append(prev_effective_amount)
|
|
||||||
self.effective_amount_changes[spent_support].append(-support_amount)
|
|
||||||
return StagedClaimtrieSupport(
|
return StagedClaimtrieSupport(
|
||||||
spent_support, txin_num, txin.prev_idx, support_amount
|
spent_support, txin_num, txin.prev_idx, support_amount
|
||||||
).get_spend_support_txo_ops()
|
).get_spend_support_txo_ops()
|
||||||
|
spent_support, support_amount = self.db.get_supported_claim_from_txo(txin_num, txin.prev_idx)
|
||||||
|
if spent_support:
|
||||||
|
supported_name = self._get_pending_claim_name(spent_support)
|
||||||
|
self.pending_removed_support[supported_name][spent_support].append((txin_num, txin.prev_idx))
|
||||||
|
activation = self.db.get_activation(txin_num, txin.prev_idx, is_support=True)
|
||||||
|
self.removed_active_support[spent_support].append(support_amount)
|
||||||
|
print(f"\tspent support for lbry://{supported_name}#{spent_support.hex()} activation:{activation} {support_amount}")
|
||||||
|
return StagedClaimtrieSupport(
|
||||||
|
spent_support, txin_num, txin.prev_idx, support_amount
|
||||||
|
).get_spend_support_txo_ops() + StagedActivation(
|
||||||
|
ACTIVATED_SUPPORT_TXO_TYPE, spent_support, txin_num, txin.prev_idx, activation, supported_name,
|
||||||
|
support_amount
|
||||||
|
).get_remove_activate_ops()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _remove_claim(self, txin, spent_claims, zero_delay_claims):
|
def _spend_claim_txo(self, txin: TxInput, spent_claims: Dict[bytes, Tuple[int, int, str]]):
|
||||||
txin_num = self.db.transaction_num_mapping[txin.prev_hash]
|
txin_num = self.db.transaction_num_mapping[txin.prev_hash]
|
||||||
if (txin_num, txin.prev_idx) in self.pending_claims:
|
if (txin_num, txin.prev_idx) in self.pending_claims:
|
||||||
spent = self.pending_claims[(txin_num, txin.prev_idx)]
|
spent = self.pending_claims[(txin_num, txin.prev_idx)]
|
||||||
name = spent.name
|
|
||||||
spent_claims[spent.claim_hash] = (txin_num, txin.prev_idx, name)
|
|
||||||
# print(f"spend lbry://{name}#{spent.claim_hash.hex()}")
|
|
||||||
else:
|
else:
|
||||||
spent_claim_hash_and_name = self.db.claim_hash_and_name_from_txo(
|
spent_claim_hash_and_name = self.db.get_claim_from_txo(
|
||||||
txin_num, txin.prev_idx
|
txin_num, txin.prev_idx
|
||||||
)
|
)
|
||||||
if not spent_claim_hash_and_name: # txo is not a claim
|
if not spent_claim_hash_and_name: # txo is not a claim
|
||||||
return []
|
return []
|
||||||
prev_claim_hash = spent_claim_hash_and_name.claim_hash
|
claim_hash = spent_claim_hash_and_name.claim_hash
|
||||||
|
signing_hash = self.db.get_channel_for_claim(claim_hash)
|
||||||
prev_signing_hash = self.db.get_channel_for_claim(prev_claim_hash)
|
k, v = self.db.get_claim_txo(claim_hash)
|
||||||
prev_claims_in_channel_count = None
|
|
||||||
if prev_signing_hash:
|
|
||||||
prev_claims_in_channel_count = self.db.get_claims_in_channel_count(
|
|
||||||
prev_signing_hash
|
|
||||||
)
|
|
||||||
prev_effective_amount = self.db.get_effective_amount(
|
|
||||||
prev_claim_hash
|
|
||||||
)
|
|
||||||
k, v = self.db.get_root_claim_txo_and_current_amount(prev_claim_hash)
|
|
||||||
claim_root_tx_num = v.root_tx_num
|
|
||||||
claim_root_idx = v.root_position
|
|
||||||
prev_amount = v.amount
|
|
||||||
name = v.name
|
|
||||||
tx_num = k.tx_num
|
|
||||||
position = k.position
|
|
||||||
activation_height = v.activation
|
|
||||||
height = bisect_right(self.db.tx_counts, tx_num)
|
|
||||||
spent = StagedClaimtrieItem(
|
spent = StagedClaimtrieItem(
|
||||||
name, prev_claim_hash, prev_amount, prev_effective_amount,
|
v.name, claim_hash, v.amount,
|
||||||
activation_height, get_expiration_height(height), txin_num, txin.prev_idx, claim_root_tx_num,
|
self.coin.get_expiration_height(bisect_right(self.db.tx_counts, txin_num)),
|
||||||
claim_root_idx, prev_signing_hash, prev_claims_in_channel_count
|
txin_num, txin.prev_idx, v.root_tx_num, v.root_position, signing_hash
|
||||||
)
|
)
|
||||||
spent_claims[prev_claim_hash] = (txin_num, txin.prev_idx, name)
|
spent_claims[spent.claim_hash] = (spent.tx_num, spent.position, spent.name)
|
||||||
# print(f"spend lbry://{spent_claims[prev_claim_hash][2]}#{prev_claim_hash.hex()}")
|
print(f"\tspend lbry://{spent.name}#{spent.claim_hash.hex()}")
|
||||||
if spent.claim_hash not in self.effective_amount_changes:
|
|
||||||
self.effective_amount_changes[spent.claim_hash].append(spent.effective_amount)
|
|
||||||
self.effective_amount_changes[spent.claim_hash].append(-spent.amount)
|
|
||||||
if (name, spent.claim_hash) in zero_delay_claims:
|
|
||||||
zero_delay_claims.pop((name, spent.claim_hash))
|
|
||||||
return spent.get_spend_claim_txo_ops()
|
return spent.get_spend_claim_txo_ops()
|
||||||
|
|
||||||
def _remove_claim_or_support(self, txin, spent_claims, zero_delay_claims):
|
def _spend_claim_or_support_txo(self, txin, spent_claims):
|
||||||
spend_claim_ops = self._remove_claim(txin, spent_claims, zero_delay_claims)
|
spend_claim_ops = self._spend_claim_txo(txin, spent_claims)
|
||||||
if spend_claim_ops:
|
if spend_claim_ops:
|
||||||
return spend_claim_ops
|
return spend_claim_ops
|
||||||
return self._remove_support(txin, zero_delay_claims)
|
return self._spend_support_txo(txin)
|
||||||
|
|
||||||
def _abandon(self, spent_claims) -> typing.Tuple[List['RevertableOp'], typing.Set[str]]:
|
def _abandon_claim(self, claim_hash, tx_num, nout, name) -> List['RevertableOp']:
|
||||||
|
if (tx_num, nout) in self.pending_claims:
|
||||||
|
pending = self.pending_claims.pop((tx_num, nout))
|
||||||
|
self.staged_pending_abandoned[pending.claim_hash] = pending
|
||||||
|
claim_root_tx_num, claim_root_idx = pending.root_claim_tx_num, pending.root_claim_tx_position
|
||||||
|
prev_amount, prev_signing_hash = pending.amount, pending.signing_hash
|
||||||
|
expiration = self.coin.get_expiration_height(self.height)
|
||||||
|
else:
|
||||||
|
k, v = self.db.get_claim_txo(
|
||||||
|
claim_hash
|
||||||
|
)
|
||||||
|
claim_root_tx_num, claim_root_idx, prev_amount = v.root_tx_num, v.root_position, v.amount
|
||||||
|
prev_signing_hash = self.db.get_channel_for_claim(claim_hash)
|
||||||
|
expiration = self.coin.get_expiration_height(bisect_right(self.db.tx_counts, tx_num))
|
||||||
|
self.staged_pending_abandoned[claim_hash] = staged = StagedClaimtrieItem(
|
||||||
|
name, claim_hash, prev_amount, expiration, tx_num, nout, claim_root_tx_num,
|
||||||
|
claim_root_idx, prev_signing_hash
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pending_supports[claim_hash].clear()
|
||||||
|
self.pending_supports.pop(claim_hash)
|
||||||
|
|
||||||
|
return staged.get_abandon_ops(self.db.db)
|
||||||
|
|
||||||
|
def _abandon(self, spent_claims) -> List['RevertableOp']:
|
||||||
# Handle abandoned claims
|
# Handle abandoned claims
|
||||||
ops = []
|
ops = []
|
||||||
|
|
||||||
controlling_claims = {}
|
for abandoned_claim_hash, (tx_num, nout, name) in spent_claims.items():
|
||||||
need_takeover = set()
|
print(f"\tabandon lbry://{name}#{abandoned_claim_hash.hex()} {tx_num} {nout}")
|
||||||
|
ops.extend(self._abandon_claim(abandoned_claim_hash, tx_num, nout, name))
|
||||||
|
return ops
|
||||||
|
|
||||||
for abandoned_claim_hash, (prev_tx_num, prev_idx, name) in spent_claims.items():
|
def _expire_claims(self, height: int):
|
||||||
# print(f"\tabandon lbry://{name}#{abandoned_claim_hash.hex()} {prev_tx_num} {prev_idx}")
|
|
||||||
|
|
||||||
if (prev_tx_num, prev_idx) in self.pending_claims:
|
|
||||||
pending = self.pending_claims.pop((prev_tx_num, prev_idx))
|
|
||||||
self.staged_pending_abandoned[pending.claim_hash] = pending
|
|
||||||
claim_root_tx_num = pending.root_claim_tx_num
|
|
||||||
claim_root_idx = pending.root_claim_tx_position
|
|
||||||
prev_amount = pending.amount
|
|
||||||
prev_signing_hash = pending.signing_hash
|
|
||||||
prev_effective_amount = pending.effective_amount
|
|
||||||
prev_claims_in_channel_count = pending.claims_in_channel_count
|
|
||||||
else:
|
|
||||||
k, v = self.db.get_root_claim_txo_and_current_amount(
|
|
||||||
abandoned_claim_hash
|
|
||||||
)
|
|
||||||
claim_root_tx_num = v.root_tx_num
|
|
||||||
claim_root_idx = v.root_position
|
|
||||||
prev_amount = v.amount
|
|
||||||
prev_signing_hash = self.db.get_channel_for_claim(abandoned_claim_hash)
|
|
||||||
prev_claims_in_channel_count = None
|
|
||||||
if prev_signing_hash:
|
|
||||||
prev_claims_in_channel_count = self.db.get_claims_in_channel_count(
|
|
||||||
prev_signing_hash
|
|
||||||
)
|
|
||||||
prev_effective_amount = self.db.get_effective_amount(
|
|
||||||
abandoned_claim_hash
|
|
||||||
)
|
|
||||||
|
|
||||||
if name not in controlling_claims:
|
|
||||||
controlling_claims[name] = self.db.get_controlling_claim(name)
|
|
||||||
controlling = controlling_claims[name]
|
|
||||||
if controlling and controlling.claim_hash == abandoned_claim_hash:
|
|
||||||
need_takeover.add(name)
|
|
||||||
# print("needs takeover")
|
|
||||||
|
|
||||||
for (support_tx_num, support_tx_idx) in self.pending_supports[abandoned_claim_hash]:
|
|
||||||
_, support_amount = self.pending_support_txos.pop((support_tx_num, support_tx_idx))
|
|
||||||
ops.extend(
|
|
||||||
StagedClaimtrieSupport(
|
|
||||||
abandoned_claim_hash, support_tx_num, support_tx_idx, support_amount
|
|
||||||
).get_spend_support_txo_ops()
|
|
||||||
)
|
|
||||||
# print(f"\tremove pending support for abandoned lbry://{name}#{abandoned_claim_hash.hex()} {support_tx_num} {support_tx_idx}")
|
|
||||||
self.pending_supports[abandoned_claim_hash].clear()
|
|
||||||
self.pending_supports.pop(abandoned_claim_hash)
|
|
||||||
|
|
||||||
for (support_tx_num, support_tx_idx, support_amount) in self.db.get_supports(abandoned_claim_hash):
|
|
||||||
ops.extend(
|
|
||||||
StagedClaimtrieSupport(
|
|
||||||
abandoned_claim_hash, support_tx_num, support_tx_idx, support_amount
|
|
||||||
).get_spend_support_txo_ops()
|
|
||||||
)
|
|
||||||
# print(f"\tremove support for abandoned lbry://{name}#{abandoned_claim_hash.hex()} {support_tx_num} {support_tx_idx}")
|
|
||||||
|
|
||||||
height = bisect_right(self.db.tx_counts, prev_tx_num)
|
|
||||||
activation_height = 0
|
|
||||||
|
|
||||||
if abandoned_claim_hash in self.effective_amount_changes:
|
|
||||||
# print("pop")
|
|
||||||
self.effective_amount_changes.pop(abandoned_claim_hash)
|
|
||||||
self.pending_abandon.add(abandoned_claim_hash)
|
|
||||||
|
|
||||||
# print(f"\tabandoned lbry://{name}#{abandoned_claim_hash.hex()}, {len(need_takeover)} names need takeovers")
|
|
||||||
ops.extend(
|
|
||||||
StagedClaimtrieItem(
|
|
||||||
name, abandoned_claim_hash, prev_amount, prev_effective_amount,
|
|
||||||
activation_height, get_expiration_height(height), prev_tx_num, prev_idx, claim_root_tx_num,
|
|
||||||
claim_root_idx, prev_signing_hash, prev_claims_in_channel_count
|
|
||||||
).get_abandon_ops(self.db.db)
|
|
||||||
)
|
|
||||||
return ops, need_takeover
|
|
||||||
|
|
||||||
def _expire_claims(self, height: int, zero_delay_claims):
|
|
||||||
expired = self.db.get_expired_by_height(height)
|
expired = self.db.get_expired_by_height(height)
|
||||||
spent_claims = {}
|
spent_claims = {}
|
||||||
ops = []
|
ops = []
|
||||||
names_needing_takeover = set()
|
|
||||||
for expired_claim_hash, (tx_num, position, name, txi) in expired.items():
|
for expired_claim_hash, (tx_num, position, name, txi) in expired.items():
|
||||||
if (tx_num, position) not in self.pending_claims:
|
if (tx_num, position) not in self.pending_claims:
|
||||||
ops.extend(self._remove_claim(txi, spent_claims, zero_delay_claims))
|
ops.extend(self._spend_claim_txo(txi, spent_claims))
|
||||||
if expired:
|
if expired:
|
||||||
# do this to follow the same content claim removing pathway as if a claim (possible channel) was abandoned
|
# do this to follow the same content claim removing pathway as if a claim (possible channel) was abandoned
|
||||||
abandon_ops, _names_needing_takeover = self._abandon(spent_claims)
|
|
||||||
if abandon_ops:
|
|
||||||
ops.extend(abandon_ops)
|
|
||||||
names_needing_takeover.update(_names_needing_takeover)
|
|
||||||
ops.extend(self._abandon(spent_claims))
|
ops.extend(self._abandon(spent_claims))
|
||||||
return ops, names_needing_takeover
|
return ops
|
||||||
|
|
||||||
def _get_pending_claim_amount(self, claim_hash: bytes) -> int:
|
def _get_pending_claim_amount(self, name: str, claim_hash: bytes) -> int:
|
||||||
if claim_hash in self.pending_claim_txos:
|
if (name, claim_hash) in self.staged_activated_claim:
|
||||||
return self.pending_claims[self.pending_claim_txos[claim_hash]].amount
|
return self.staged_activated_claim[(name, claim_hash)]
|
||||||
return self.db.get_claim_amount(claim_hash)
|
return self.db._get_active_amount(claim_hash, ACTIVATED_CLAIM_TXO_TYPE, self.height + 1)
|
||||||
|
|
||||||
def _get_pending_claim_name(self, claim_hash: bytes) -> str:
|
def _get_pending_claim_name(self, claim_hash: bytes) -> Optional[str]:
|
||||||
assert claim_hash is not None
|
assert claim_hash is not None
|
||||||
if claim_hash in self.pending_claims:
|
if claim_hash in self.pending_claims:
|
||||||
return self.pending_claims[claim_hash].name
|
return self.pending_claims[claim_hash].name
|
||||||
claim = self.db.get_claim_from_txo(claim_hash)
|
claim_info = self.db.get_claim_txo(claim_hash)
|
||||||
return claim.name
|
if claim_info:
|
||||||
|
return claim_info[1].name
|
||||||
|
|
||||||
def _get_pending_effective_amount(self, claim_hash: bytes) -> int:
|
def _get_pending_supported_amount(self, claim_hash: bytes) -> int:
|
||||||
claim_amount = self._get_pending_claim_amount(claim_hash) or 0
|
support_amount = self.db._get_active_amount(claim_hash, ACTIVATED_SUPPORT_TXO_TYPE, self.height + 1) or 0
|
||||||
support_amount = self.db.get_support_amount(claim_hash) or 0
|
amount = support_amount + sum(
|
||||||
return claim_amount + support_amount + sum(
|
self.staged_activated_support.get(claim_hash, [])
|
||||||
self.pending_support_txos[support_txnum, support_n][1]
|
)
|
||||||
for (support_txnum, support_n) in self.pending_supports.get(claim_hash, [])
|
if claim_hash in self.removed_active_support:
|
||||||
) # TODO: subtract pending spend supports
|
return amount - sum(self.removed_active_support[claim_hash])
|
||||||
|
return amount
|
||||||
|
|
||||||
def _get_name_takeover_ops(self, height: int, name: str,
|
def _get_pending_effective_amount(self, name: str, claim_hash: bytes) -> int:
|
||||||
activated_claims: typing.Set[bytes]) -> List['RevertableOp']:
|
claim_amount = self._get_pending_claim_amount(name, claim_hash)
|
||||||
controlling = self.db.get_controlling_claim(name)
|
support_amount = self._get_pending_supported_amount(claim_hash)
|
||||||
if not controlling or controlling.claim_hash in self.pending_abandon:
|
return claim_amount + support_amount
|
||||||
# print("no controlling claim for ", name)
|
|
||||||
bid_queue = {
|
def _get_takeover_ops(self, height: int) -> List['RevertableOp']:
|
||||||
claim_hash: self._get_pending_effective_amount(claim_hash) for claim_hash in activated_claims
|
|
||||||
}
|
|
||||||
winning_claim = max(bid_queue, key=lambda k: bid_queue[k])
|
|
||||||
if winning_claim in self.pending_claim_txos:
|
|
||||||
s = self.pending_claims[self.pending_claim_txos[winning_claim]]
|
|
||||||
else:
|
|
||||||
s = self.db.make_staged_claim_item(winning_claim)
|
|
||||||
ops = []
|
ops = []
|
||||||
if s.activation_height > height:
|
|
||||||
ops.extend(get_force_activate_ops(
|
|
||||||
name, s.tx_num, s.position, s.claim_hash, s.root_claim_tx_num, s.root_claim_tx_position,
|
|
||||||
s.amount, s.effective_amount, s.activation_height, height
|
|
||||||
))
|
|
||||||
ops.extend(get_takeover_name_ops(name, winning_claim, height))
|
|
||||||
return ops
|
|
||||||
else:
|
|
||||||
# print(f"current controlling claim for {name}#{controlling.claim_hash.hex()}")
|
|
||||||
controlling_effective_amount = self._get_pending_effective_amount(controlling.claim_hash)
|
|
||||||
bid_queue = {
|
|
||||||
claim_hash: self._get_pending_effective_amount(claim_hash) for claim_hash in activated_claims
|
|
||||||
}
|
|
||||||
highest_newly_activated = max(bid_queue, key=lambda k: bid_queue[k])
|
|
||||||
if bid_queue[highest_newly_activated] > controlling_effective_amount:
|
|
||||||
# print(f"takeover controlling claim for {name}#{controlling.claim_hash.hex()}")
|
|
||||||
return get_takeover_name_ops(name, highest_newly_activated, height, controlling)
|
|
||||||
print(bid_queue[highest_newly_activated], controlling_effective_amount)
|
|
||||||
# print("no takeover")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _get_takeover_ops(self, height: int, zero_delay_claims) -> List['RevertableOp']:
|
|
||||||
ops = []
|
|
||||||
pending = defaultdict(set)
|
|
||||||
|
|
||||||
# get non delayed takeovers for new names
|
|
||||||
for (name, claim_hash) in zero_delay_claims:
|
|
||||||
if claim_hash not in self.pending_abandon:
|
|
||||||
pending[name].add(claim_hash)
|
|
||||||
# print("zero delay activate", name, claim_hash.hex())
|
|
||||||
|
|
||||||
# get takeovers from claims activated at this block
|
# get takeovers from claims activated at this block
|
||||||
for activated in self.db.get_activated_claims_at_height(height):
|
activated_at_height = self.db.get_activated_at_height(height)
|
||||||
if activated.claim_hash not in self.pending_abandon:
|
controlling_claims = {}
|
||||||
pending[activated.name].add(activated.claim_hash)
|
abandoned_need_takeover = []
|
||||||
# print("delayed activate")
|
abandoned_support_check_need_takeover = defaultdict(list)
|
||||||
|
|
||||||
# get takeovers from supports for controlling claims being abandoned
|
def get_controlling(_name):
|
||||||
for abandoned_claim_hash in self.pending_abandon:
|
if _name not in controlling_claims:
|
||||||
if abandoned_claim_hash in self.staged_pending_abandoned:
|
_controlling = self.db.get_controlling_claim(_name)
|
||||||
abandoned = self.staged_pending_abandoned[abandoned_claim_hash]
|
controlling_claims[_name] = _controlling
|
||||||
controlling = self.db.get_controlling_claim(abandoned.name)
|
|
||||||
if controlling and controlling.claim_hash == abandoned_claim_hash and abandoned.name not in pending:
|
|
||||||
pending[abandoned.name].update(self.db.get_claims_for_name(abandoned.name))
|
|
||||||
else:
|
else:
|
||||||
k, v = self.db.get_root_claim_txo_and_current_amount(abandoned_claim_hash)
|
_controlling = controlling_claims[_name]
|
||||||
controlling_claim = self.db.get_controlling_claim(v.name)
|
return _controlling
|
||||||
if controlling_claim and abandoned_claim_hash == controlling_claim.claim_hash and v.name not in pending:
|
|
||||||
pending[v.name].update(self.db.get_claims_for_name(v.name))
|
|
||||||
# print("check abandoned winning")
|
|
||||||
|
|
||||||
|
# determine names needing takeover/deletion due to controlling claims being abandoned
|
||||||
|
# and add ops to deactivate abandoned claims
|
||||||
|
for claim_hash, staged in self.staged_pending_abandoned.items():
|
||||||
|
controlling = get_controlling(staged.name)
|
||||||
|
if controlling and controlling.claim_hash == claim_hash:
|
||||||
|
abandoned_need_takeover.append(staged.name)
|
||||||
|
print(f"\t{staged.name} needs takeover")
|
||||||
|
activation = self.db.get_activation(staged.tx_num, staged.position)
|
||||||
|
if activation > 0:
|
||||||
|
# removed queued future activation from the db
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE, staged.claim_hash, staged.tx_num, staged.position,
|
||||||
|
activation, staged.name, staged.amount
|
||||||
|
).get_remove_activate_ops()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# it hadn't yet been activated, db returns -1 for non-existent txos
|
||||||
|
pass
|
||||||
|
|
||||||
|
# build set of controlling claims that had activated supports spent to check them for takeovers later
|
||||||
|
for claim_hash, amounts in self.removed_active_support.items():
|
||||||
|
name = self._get_pending_claim_name(claim_hash)
|
||||||
|
controlling = get_controlling(name)
|
||||||
|
if controlling and controlling.claim_hash == claim_hash and name not in abandoned_need_takeover:
|
||||||
|
abandoned_support_check_need_takeover[(name, claim_hash)].extend(amounts)
|
||||||
|
|
||||||
# get takeovers from controlling claims being abandoned
|
# prepare to activate or delay activation of the pending claims being added this block
|
||||||
|
for (tx_num, nout), staged in self.pending_claims.items():
|
||||||
|
controlling = get_controlling(staged.name)
|
||||||
|
delay = 0
|
||||||
|
if not controlling or staged.claim_hash == controlling.claim_hash or \
|
||||||
|
controlling.claim_hash in abandoned_need_takeover:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
controlling_effective_amount = self._get_pending_effective_amount(staged.name, controlling.claim_hash)
|
||||||
|
amount = self._get_pending_effective_amount(staged.name, staged.claim_hash)
|
||||||
|
delay = 0
|
||||||
|
# if this is an OP_CLAIM or the amount appears to trigger a takeover, delay
|
||||||
|
if not staged.is_update or (amount > controlling_effective_amount):
|
||||||
|
delay = self.coin.get_delay_for_name(height - controlling.height)
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE, staged.claim_hash, staged.tx_num, staged.position,
|
||||||
|
height + delay, staged.name, staged.amount
|
||||||
|
).get_activate_ops()
|
||||||
|
)
|
||||||
|
if delay == 0: # if delay was 0 it needs to be considered for takeovers
|
||||||
|
activated_at_height[PendingActivationValue(staged.claim_hash, staged.name)].append(
|
||||||
|
PendingActivationKey(height, ACTIVATED_CLAIM_TXO_TYPE, tx_num, nout)
|
||||||
|
)
|
||||||
|
|
||||||
|
# and the supports
|
||||||
|
for (tx_num, nout), (claim_hash, amount) in self.pending_support_txos.items():
|
||||||
|
if claim_hash in self.staged_pending_abandoned:
|
||||||
|
continue
|
||||||
|
elif claim_hash in self.pending_claim_txos:
|
||||||
|
name = self.pending_claims[self.pending_claim_txos[claim_hash]].name
|
||||||
|
is_update = self.pending_claims[self.pending_claim_txos[claim_hash]].is_update
|
||||||
|
else:
|
||||||
|
k, v = self.db.get_claim_txo(claim_hash)
|
||||||
|
name = v.name
|
||||||
|
is_update = (v.root_tx_num, v.root_position) != (k.tx_num, k.position)
|
||||||
|
|
||||||
|
controlling = get_controlling(name)
|
||||||
|
delay = 0
|
||||||
|
if not controlling or claim_hash == controlling.claim_hash:
|
||||||
|
pass
|
||||||
|
elif not is_update or self._get_pending_effective_amount(staged.name,
|
||||||
|
claim_hash) > self._get_pending_effective_amount(staged.name, controlling.claim_hash):
|
||||||
|
delay = self.coin.get_delay_for_name(height - controlling.height)
|
||||||
|
if delay == 0:
|
||||||
|
activated_at_height[PendingActivationValue(claim_hash, name)].append(
|
||||||
|
PendingActivationKey(height + delay, ACTIVATED_SUPPORT_TXO_TYPE, tx_num, nout)
|
||||||
|
)
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_SUPPORT_TXO_TYPE, claim_hash, tx_num, nout,
|
||||||
|
height + delay, name, amount
|
||||||
|
).get_activate_ops()
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the activation/delayed-activation ops
|
||||||
|
for activated, activated_txos in activated_at_height.items():
|
||||||
|
controlling = get_controlling(activated.name)
|
||||||
|
|
||||||
|
if activated.claim_hash in self.staged_pending_abandoned:
|
||||||
|
continue
|
||||||
|
reactivate = False
|
||||||
|
if not controlling or controlling.claim_hash == activated.claim_hash:
|
||||||
|
# there is no delay for claims to a name without a controlling value or to the controlling value
|
||||||
|
reactivate = True
|
||||||
|
for activated_txo in activated_txos:
|
||||||
|
if activated_txo.is_support and (activated_txo.tx_num, activated_txo.position) in \
|
||||||
|
self.pending_removed_support[activated.name][activated.claim_hash]:
|
||||||
|
print("\tskip activate support for pending abandoned claim")
|
||||||
|
continue
|
||||||
|
if activated_txo.is_claim:
|
||||||
|
txo_type = ACTIVATED_CLAIM_TXO_TYPE
|
||||||
|
txo_tup = (activated_txo.tx_num, activated_txo.position)
|
||||||
|
if txo_tup in self.pending_claims:
|
||||||
|
amount = self.pending_claims[txo_tup].amount
|
||||||
|
else:
|
||||||
|
amount = self.db.get_claim_txo_amount(
|
||||||
|
activated.claim_hash, activated_txo.tx_num, activated_txo.position
|
||||||
|
)
|
||||||
|
self.staged_activated_claim[(activated.name, activated.claim_hash)] = amount
|
||||||
|
else:
|
||||||
|
txo_type = ACTIVATED_SUPPORT_TXO_TYPE
|
||||||
|
txo_tup = (activated_txo.tx_num, activated_txo.position)
|
||||||
|
if txo_tup in self.pending_support_txos:
|
||||||
|
amount = self.pending_support_txos[txo_tup][1]
|
||||||
|
else:
|
||||||
|
amount = self.db.get_support_txo_amount(
|
||||||
|
activated.claim_hash, activated_txo.tx_num, activated_txo.position
|
||||||
|
)
|
||||||
|
self.staged_activated_support[activated.claim_hash].append(amount)
|
||||||
|
self.pending_activated[activated.name][activated.claim_hash].append((activated_txo, amount))
|
||||||
|
print(f"\tactivate {'support' if txo_type == ACTIVATED_SUPPORT_TXO_TYPE else 'claim'} "
|
||||||
|
f"lbry://{activated.name}#{activated.claim_hash.hex()} @ {activated_txo.height}")
|
||||||
|
if reactivate:
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
txo_type, activated.claim_hash, activated_txo.tx_num, activated_txo.position,
|
||||||
|
activated_txo.height, activated.name, amount
|
||||||
|
).get_activate_ops()
|
||||||
|
)
|
||||||
|
|
||||||
|
# go through claims where the controlling claim or supports to the controlling claim have been abandoned
|
||||||
|
# check if takeovers are needed or if the name node is now empty
|
||||||
|
need_reactivate_if_takes_over = {}
|
||||||
|
for need_takeover in abandoned_need_takeover:
|
||||||
|
existing = self.db.get_claim_txos_for_name(need_takeover)
|
||||||
|
has_candidate = False
|
||||||
|
# add existing claims to the queue for the takeover
|
||||||
|
# track that we need to reactivate these if one of them becomes controlling
|
||||||
|
for candidate_claim_hash, (tx_num, nout) in existing.items():
|
||||||
|
if candidate_claim_hash in self.staged_pending_abandoned:
|
||||||
|
continue
|
||||||
|
has_candidate = True
|
||||||
|
existing_activation = self.db.get_activation(tx_num, nout)
|
||||||
|
activate_key = PendingActivationKey(
|
||||||
|
existing_activation, ACTIVATED_CLAIM_TXO_TYPE, tx_num, nout
|
||||||
|
)
|
||||||
|
self.pending_activated[need_takeover][candidate_claim_hash].append((
|
||||||
|
activate_key, self.db.get_claim_txo_amount(candidate_claim_hash, tx_num, nout)
|
||||||
|
))
|
||||||
|
need_reactivate_if_takes_over[(need_takeover, candidate_claim_hash)] = activate_key
|
||||||
|
print(f"\tcandidate to takeover abandoned controlling claim for lbry://{need_takeover} - "
|
||||||
|
f"{activate_key.tx_num}:{activate_key.position} {activate_key.is_claim}")
|
||||||
|
if not has_candidate:
|
||||||
|
# remove name takeover entry, the name is now unclaimed
|
||||||
|
controlling = get_controlling(need_takeover)
|
||||||
|
ops.extend(get_remove_name_ops(need_takeover, controlling.claim_hash, controlling.height))
|
||||||
|
|
||||||
|
# process takeovers from the combined newly added and previously scheduled claims
|
||||||
|
checked_names = set()
|
||||||
|
for name, activated in self.pending_activated.items():
|
||||||
|
checked_names.add(name)
|
||||||
|
if name in abandoned_need_takeover:
|
||||||
|
print(f'\tabandoned {name} need takeover')
|
||||||
|
controlling = controlling_claims[name]
|
||||||
|
amounts = {
|
||||||
|
claim_hash: self._get_pending_effective_amount(name, claim_hash)
|
||||||
|
for claim_hash in activated.keys() if claim_hash not in self.staged_pending_abandoned
|
||||||
|
}
|
||||||
|
if controlling and controlling.claim_hash not in self.staged_pending_abandoned:
|
||||||
|
amounts[controlling.claim_hash] = self._get_pending_effective_amount(name, controlling.claim_hash)
|
||||||
|
winning = max(amounts, key=lambda x: amounts[x])
|
||||||
|
if not controlling or (winning != controlling.claim_hash and name in abandoned_need_takeover) or ((winning != controlling.claim_hash) and
|
||||||
|
(amounts[winning] > amounts[controlling.claim_hash])):
|
||||||
|
if (name, winning) in need_reactivate_if_takes_over:
|
||||||
|
previous_pending_activate = need_reactivate_if_takes_over[(name, winning)]
|
||||||
|
amount = self.db.get_claim_txo_amount(
|
||||||
|
winning, previous_pending_activate.tx_num, previous_pending_activate.position
|
||||||
|
)
|
||||||
|
if winning in self.pending_claim_txos:
|
||||||
|
tx_num, position = self.pending_claim_txos[winning]
|
||||||
|
amount = self.pending_claims[(tx_num, position)].amount
|
||||||
|
else:
|
||||||
|
tx_num, position = previous_pending_activate.tx_num, previous_pending_activate.position
|
||||||
|
if previous_pending_activate.height > height:
|
||||||
|
# the claim had a pending activation in the future, move it to now
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE, winning, tx_num,
|
||||||
|
position, previous_pending_activate.height, name, amount
|
||||||
|
).get_remove_activate_ops()
|
||||||
|
)
|
||||||
|
ops.extend(
|
||||||
|
StagedActivation(
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE, winning, tx_num,
|
||||||
|
position, height, name, amount
|
||||||
|
).get_activate_ops()
|
||||||
|
)
|
||||||
|
ops.extend(get_takeover_name_ops(name, winning, height))
|
||||||
|
else:
|
||||||
|
ops.extend(get_takeover_name_ops(name, winning, height))
|
||||||
|
|
||||||
|
elif winning == controlling.claim_hash:
|
||||||
|
print("\tstill winning")
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print("\tno takeover")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# handle remaining takeovers from abandoned supports
|
||||||
|
for (name, claim_hash), amounts in abandoned_support_check_need_takeover.items():
|
||||||
|
if name in checked_names:
|
||||||
|
continue
|
||||||
|
checked_names.add(name)
|
||||||
|
|
||||||
|
controlling = get_controlling(name)
|
||||||
|
|
||||||
|
amounts = {
|
||||||
|
claim_hash: self._get_pending_effective_amount(name, claim_hash)
|
||||||
|
for claim_hash in self.db.get_claims_for_name(name) if claim_hash not in self.staged_pending_abandoned
|
||||||
|
}
|
||||||
|
if controlling and controlling.claim_hash not in self.staged_pending_abandoned:
|
||||||
|
amounts[controlling.claim_hash] = self._get_pending_effective_amount(name, controlling.claim_hash)
|
||||||
|
winning = max(amounts, key=lambda x: amounts[x])
|
||||||
|
if (controlling and winning != controlling.claim_hash) or (not controlling and winning):
|
||||||
|
print(f"\ttakeover from abandoned support {controlling.claim_hash.hex()} -> {winning.hex()}")
|
||||||
|
ops.extend(get_takeover_name_ops(name, winning, height))
|
||||||
|
|
||||||
for name, activated_claims in pending.items():
|
|
||||||
ops.extend(self._get_name_takeover_ops(height, name, activated_claims))
|
|
||||||
return ops
|
return ops
|
||||||
|
|
||||||
def advance_block(self, block):
|
def advance_block(self, block):
|
||||||
# print("advance ", height)
|
|
||||||
height = self.height + 1
|
height = self.height + 1
|
||||||
|
print("advance ", height)
|
||||||
txs: List[Tuple[Tx, bytes]] = block.transactions
|
txs: List[Tuple[Tx, bytes]] = block.transactions
|
||||||
block_hash = self.coin.header_hash(block.header)
|
block_hash = self.coin.header_hash(block.header)
|
||||||
|
|
||||||
|
@ -881,34 +875,32 @@ class BlockProcessor:
|
||||||
undo_info_append(cache_value)
|
undo_info_append(cache_value)
|
||||||
append_hashX(cache_value[:-12])
|
append_hashX(cache_value[:-12])
|
||||||
|
|
||||||
spend_claim_or_support_ops = self._remove_claim_or_support(txin, spent_claims, zero_delay_claims)
|
spend_claim_or_support_ops = self._spend_claim_or_support_txo(txin, spent_claims)
|
||||||
if spend_claim_or_support_ops:
|
if spend_claim_or_support_ops:
|
||||||
claimtrie_stash_extend(spend_claim_or_support_ops)
|
claimtrie_stash_extend(spend_claim_or_support_ops)
|
||||||
|
|
||||||
# Add the new UTXOs
|
# Add the new UTXOs
|
||||||
for idx, txout in enumerate(tx.outputs):
|
for nout, txout in enumerate(tx.outputs):
|
||||||
# Get the hashX. Ignore unspendable outputs
|
# Get the hashX. Ignore unspendable outputs
|
||||||
hashX = hashX_from_script(txout.pk_script)
|
hashX = hashX_from_script(txout.pk_script)
|
||||||
if hashX:
|
if hashX:
|
||||||
append_hashX(hashX)
|
append_hashX(hashX)
|
||||||
put_utxo(tx_hash + pack('<H', idx), hashX + tx_numb + pack('<Q', txout.value))
|
put_utxo(tx_hash + pack('<H', nout), hashX + tx_numb + pack('<Q', txout.value))
|
||||||
|
|
||||||
# add claim/support txo
|
# add claim/support txo
|
||||||
script = OutputScript(txout.pk_script)
|
script = OutputScript(txout.pk_script)
|
||||||
script.parse()
|
script.parse()
|
||||||
txo = Output(txout.value, script)
|
|
||||||
|
|
||||||
claim_or_support_ops = self._add_claim_or_support(
|
claim_or_support_ops = self._add_claim_or_support(
|
||||||
height, tx_hash, tx_count, idx, txo, txout, script, spent_claims, zero_delay_claims
|
height, tx_hash, tx_count, nout, Output(txout.value, script), spent_claims
|
||||||
)
|
)
|
||||||
if claim_or_support_ops:
|
if claim_or_support_ops:
|
||||||
claimtrie_stash_extend(claim_or_support_ops)
|
claimtrie_stash_extend(claim_or_support_ops)
|
||||||
|
|
||||||
# Handle abandoned claims
|
# Handle abandoned claims
|
||||||
abandon_ops, abandoned_controlling_need_takeover = self._abandon(spent_claims)
|
abandon_ops = self._abandon(spent_claims)
|
||||||
if abandon_ops:
|
if abandon_ops:
|
||||||
claimtrie_stash_extend(abandon_ops)
|
claimtrie_stash_extend(abandon_ops)
|
||||||
abandoned_or_expired_controlling.update(abandoned_controlling_need_takeover)
|
|
||||||
|
|
||||||
append_hashX_by_tx(hashXs)
|
append_hashX_by_tx(hashXs)
|
||||||
update_touched(hashXs)
|
update_touched(hashXs)
|
||||||
|
@ -917,14 +909,13 @@ class BlockProcessor:
|
||||||
tx_count += 1
|
tx_count += 1
|
||||||
|
|
||||||
# handle expired claims
|
# handle expired claims
|
||||||
expired_ops, expired_need_takeover = self._expire_claims(height, zero_delay_claims)
|
expired_ops = self._expire_claims(height)
|
||||||
if expired_ops:
|
if expired_ops:
|
||||||
# print(f"************\nexpire claims at block {height}\n************")
|
print(f"************\nexpire claims at block {height}\n************")
|
||||||
abandoned_or_expired_controlling.update(expired_need_takeover)
|
|
||||||
claimtrie_stash_extend(expired_ops)
|
claimtrie_stash_extend(expired_ops)
|
||||||
|
|
||||||
# activate claims and process takeovers
|
# activate claims and process takeovers
|
||||||
takeover_ops = self._get_takeover_ops(height, zero_delay_claims)
|
takeover_ops = self._get_takeover_ops(height)
|
||||||
if takeover_ops:
|
if takeover_ops:
|
||||||
claimtrie_stash_extend(takeover_ops)
|
claimtrie_stash_extend(takeover_ops)
|
||||||
|
|
||||||
|
@ -939,13 +930,6 @@ class BlockProcessor:
|
||||||
self.tx_count = tx_count
|
self.tx_count = tx_count
|
||||||
self.db.tx_counts.append(self.tx_count)
|
self.db.tx_counts.append(self.tx_count)
|
||||||
|
|
||||||
for touched_claim_hash, amount_changes in self.effective_amount_changes.items():
|
|
||||||
new_effective_amount = sum(amount_changes)
|
|
||||||
assert new_effective_amount >= 0, f'{new_effective_amount}, {touched_claim_hash.hex()}'
|
|
||||||
claimtrie_stash.extend(
|
|
||||||
self.db.get_update_effective_amount_ops(touched_claim_hash, new_effective_amount)
|
|
||||||
)
|
|
||||||
|
|
||||||
undo_claims = b''.join(op.invert().pack() for op in claimtrie_stash)
|
undo_claims = b''.join(op.invert().pack() for op in claimtrie_stash)
|
||||||
self.claimtrie_stash.extend(claimtrie_stash)
|
self.claimtrie_stash.extend(claimtrie_stash)
|
||||||
# print("%i undo bytes for %i (%i claimtrie stash ops)" % (len(undo_claims), height, len(claimtrie_stash)))
|
# print("%i undo bytes for %i (%i claimtrie stash ops)" % (len(undo_claims), height, len(claimtrie_stash)))
|
||||||
|
@ -961,14 +945,18 @@ class BlockProcessor:
|
||||||
|
|
||||||
self.db.flush_dbs(self.flush_data())
|
self.db.flush_dbs(self.flush_data())
|
||||||
|
|
||||||
self.effective_amount_changes.clear()
|
# self.effective_amount_changes.clear()
|
||||||
|
|
||||||
self.pending_claims.clear()
|
self.pending_claims.clear()
|
||||||
self.pending_claim_txos.clear()
|
self.pending_claim_txos.clear()
|
||||||
self.pending_supports.clear()
|
self.pending_supports.clear()
|
||||||
self.pending_support_txos.clear()
|
self.pending_support_txos.clear()
|
||||||
self.pending_abandon.clear()
|
self.pending_removed_support.clear()
|
||||||
self.staged_pending_abandoned.clear()
|
self.staged_pending_abandoned.clear()
|
||||||
|
self.removed_active_support.clear()
|
||||||
|
self.staged_activated_support.clear()
|
||||||
|
self.staged_activated_claim.clear()
|
||||||
|
self.pending_activated.clear()
|
||||||
|
|
||||||
for cache in self.search_cache.values():
|
for cache in self.search_cache.values():
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
|
@ -12,11 +12,13 @@ class DB_PREFIXES(enum.Enum):
|
||||||
channel_to_claim = b'J'
|
channel_to_claim = b'J'
|
||||||
|
|
||||||
claim_short_id_prefix = b'F'
|
claim_short_id_prefix = b'F'
|
||||||
claim_effective_amount_prefix = b'D'
|
# claim_effective_amount_prefix = b'D'
|
||||||
claim_expiration = b'O'
|
claim_expiration = b'O'
|
||||||
|
|
||||||
claim_takeover = b'P'
|
claim_takeover = b'P'
|
||||||
pending_activation = b'Q'
|
pending_activation = b'Q'
|
||||||
|
activated_claim_and_support = b'R'
|
||||||
|
active_amount = b'S'
|
||||||
|
|
||||||
undo_claimtrie = b'M'
|
undo_claimtrie = b'M'
|
||||||
|
|
||||||
|
|
|
@ -45,40 +45,52 @@ class StagedClaimtrieSupport(typing.NamedTuple):
|
||||||
return self._get_add_remove_support_utxo_ops(add=False)
|
return self._get_add_remove_support_utxo_ops(add=False)
|
||||||
|
|
||||||
|
|
||||||
def get_update_effective_amount_ops(name: str, new_effective_amount: int, prev_effective_amount: int, tx_num: int,
|
class StagedActivation(typing.NamedTuple):
|
||||||
position: int, root_tx_num: int, root_position: int, claim_hash: bytes,
|
txo_type: int
|
||||||
activation_height: int, prev_activation_height: int,
|
claim_hash: bytes
|
||||||
signing_hash: Optional[bytes] = None,
|
tx_num: int
|
||||||
claims_in_channel_count: Optional[int] = None):
|
position: int
|
||||||
assert root_position != root_tx_num, f"{tx_num} {position} {root_tx_num} {root_tx_num}"
|
activation_height: int
|
||||||
ops = [
|
name: str
|
||||||
RevertableDelete(
|
amount: int
|
||||||
*Prefixes.claim_effective_amount.pack_item(
|
|
||||||
name, prev_effective_amount, tx_num, position, claim_hash, root_tx_num, root_position,
|
def _get_add_remove_activate_ops(self, add=True):
|
||||||
prev_activation_height
|
op = RevertablePut if add else RevertableDelete
|
||||||
|
print(f"\t{'add' if add else 'remove'} {self.txo_type}, {self.tx_num}, {self.position}, activation={self.activation_height}, {self.name}")
|
||||||
|
return [
|
||||||
|
op(
|
||||||
|
*Prefixes.activated.pack_item(
|
||||||
|
self.txo_type, self.tx_num, self.position, self.activation_height, self.claim_hash, self.name
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
RevertablePut(
|
op(
|
||||||
*Prefixes.claim_effective_amount.pack_item(
|
*Prefixes.pending_activation.pack_item(
|
||||||
name, new_effective_amount, tx_num, position, claim_hash, root_tx_num, root_position,
|
self.activation_height, self.txo_type, self.tx_num, self.position,
|
||||||
activation_height
|
self.claim_hash, self.name
|
||||||
|
)
|
||||||
|
),
|
||||||
|
op(
|
||||||
|
*Prefixes.active_amount.pack_item(
|
||||||
|
self.claim_hash, self.txo_type, self.activation_height, self.tx_num, self.position, self.amount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if signing_hash:
|
|
||||||
ops.extend([
|
def get_activate_ops(self) -> typing.List[RevertableOp]:
|
||||||
|
return self._get_add_remove_activate_ops(add=True)
|
||||||
|
|
||||||
|
def get_remove_activate_ops(self) -> typing.List[RevertableOp]:
|
||||||
|
return self._get_add_remove_activate_ops(add=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_remove_name_ops(name: str, claim_hash: bytes, height: int) -> typing.List[RevertableDelete]:
|
||||||
|
return [
|
||||||
RevertableDelete(
|
RevertableDelete(
|
||||||
*Prefixes.channel_to_claim.pack_item(
|
*Prefixes.claim_takeover.pack_item(
|
||||||
signing_hash, name, prev_effective_amount, tx_num, position, claim_hash, claims_in_channel_count
|
name, claim_hash, height
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.channel_to_claim.pack_item(
|
|
||||||
signing_hash, name, new_effective_amount, tx_num, position, claim_hash, claims_in_channel_count
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
])
|
]
|
||||||
return ops
|
|
||||||
|
|
||||||
|
|
||||||
def get_takeover_name_ops(name: str, claim_hash: bytes, takeover_height: int,
|
def get_takeover_name_ops(name: str, claim_hash: bytes, takeover_height: int,
|
||||||
|
@ -107,76 +119,16 @@ def get_takeover_name_ops(name: str, claim_hash: bytes, takeover_height: int,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_force_activate_ops(name: str, tx_num: int, position: int, claim_hash: bytes, root_claim_tx_num: int,
|
|
||||||
root_claim_tx_position: int, amount: int, effective_amount: int,
|
|
||||||
prev_activation_height: int, new_activation_height: int):
|
|
||||||
return [
|
|
||||||
# delete previous
|
|
||||||
RevertableDelete(
|
|
||||||
*Prefixes.claim_effective_amount.pack_item(
|
|
||||||
name, effective_amount, tx_num, position, claim_hash,
|
|
||||||
root_claim_tx_num, root_claim_tx_position, prev_activation_height
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertableDelete(
|
|
||||||
*Prefixes.claim_to_txo.pack_item(
|
|
||||||
claim_hash, tx_num, position, root_claim_tx_num, root_claim_tx_position,
|
|
||||||
amount, prev_activation_height, name
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertableDelete(
|
|
||||||
*Prefixes.claim_short_id.pack_item(
|
|
||||||
name, claim_hash, root_claim_tx_num, root_claim_tx_position, tx_num,
|
|
||||||
position, prev_activation_height
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertableDelete(
|
|
||||||
*Prefixes.pending_activation.pack_item(
|
|
||||||
prev_activation_height, tx_num, position, claim_hash, name
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
# insert new
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.claim_effective_amount.pack_item(
|
|
||||||
name, effective_amount, tx_num, position, claim_hash,
|
|
||||||
root_claim_tx_num, root_claim_tx_position, new_activation_height
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.claim_to_txo.pack_item(
|
|
||||||
claim_hash, tx_num, position, root_claim_tx_num, root_claim_tx_position,
|
|
||||||
amount, new_activation_height, name
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.claim_short_id.pack_item(
|
|
||||||
name, claim_hash, root_claim_tx_num, root_claim_tx_position, tx_num,
|
|
||||||
position, new_activation_height
|
|
||||||
)
|
|
||||||
),
|
|
||||||
RevertablePut(
|
|
||||||
*Prefixes.pending_activation.pack_item(
|
|
||||||
new_activation_height, tx_num, position, claim_hash, name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class StagedClaimtrieItem(typing.NamedTuple):
|
class StagedClaimtrieItem(typing.NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
claim_hash: bytes
|
claim_hash: bytes
|
||||||
amount: int
|
amount: int
|
||||||
effective_amount: int
|
|
||||||
activation_height: int
|
|
||||||
expiration_height: int
|
expiration_height: int
|
||||||
tx_num: int
|
tx_num: int
|
||||||
position: int
|
position: int
|
||||||
root_claim_tx_num: int
|
root_claim_tx_num: int
|
||||||
root_claim_tx_position: int
|
root_claim_tx_position: int
|
||||||
signing_hash: Optional[bytes]
|
signing_hash: Optional[bytes]
|
||||||
claims_in_channel_count: Optional[int]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_update(self) -> bool:
|
def is_update(self) -> bool:
|
||||||
|
@ -191,25 +143,11 @@ class StagedClaimtrieItem(typing.NamedTuple):
|
||||||
"""
|
"""
|
||||||
op = RevertablePut if add else RevertableDelete
|
op = RevertablePut if add else RevertableDelete
|
||||||
ops = [
|
ops = [
|
||||||
# url resolution by effective amount
|
|
||||||
op(
|
|
||||||
*Prefixes.claim_effective_amount.pack_item(
|
|
||||||
self.name, self.effective_amount, self.tx_num, self.position, self.claim_hash,
|
|
||||||
self.root_claim_tx_num, self.root_claim_tx_position, self.activation_height
|
|
||||||
)
|
|
||||||
),
|
|
||||||
# claim tip by claim hash
|
# claim tip by claim hash
|
||||||
op(
|
op(
|
||||||
*Prefixes.claim_to_txo.pack_item(
|
*Prefixes.claim_to_txo.pack_item(
|
||||||
self.claim_hash, self.tx_num, self.position, self.root_claim_tx_num, self.root_claim_tx_position,
|
self.claim_hash, self.tx_num, self.position, self.root_claim_tx_num, self.root_claim_tx_position,
|
||||||
self.amount, self.activation_height, self.name
|
self.amount, self.name
|
||||||
)
|
|
||||||
),
|
|
||||||
# short url resolution
|
|
||||||
op(
|
|
||||||
*Prefixes.claim_short_id.pack_item(
|
|
||||||
self.name, self.claim_hash, self.root_claim_tx_num, self.root_claim_tx_position, self.tx_num,
|
|
||||||
self.position, self.activation_height
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
# claim hash by txo
|
# claim hash by txo
|
||||||
|
@ -223,15 +161,16 @@ class StagedClaimtrieItem(typing.NamedTuple):
|
||||||
self.name
|
self.name
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
# claim activation
|
# short url resolution
|
||||||
op(
|
op(
|
||||||
*Prefixes.pending_activation.pack_item(
|
*Prefixes.claim_short_id.pack_item(
|
||||||
self.activation_height, self.tx_num, self.position, self.claim_hash, self.name
|
self.name, self.claim_hash, self.root_claim_tx_num, self.root_claim_tx_position, self.tx_num,
|
||||||
|
self.position
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if self.signing_hash and self.claims_in_channel_count is not None:
|
|
||||||
# claims_in_channel_count can be none if the channel doesnt exist
|
if self.signing_hash:
|
||||||
ops.extend([
|
ops.extend([
|
||||||
# channel by stream
|
# channel by stream
|
||||||
op(
|
op(
|
||||||
|
@ -240,8 +179,7 @@ class StagedClaimtrieItem(typing.NamedTuple):
|
||||||
# stream by channel
|
# stream by channel
|
||||||
op(
|
op(
|
||||||
*Prefixes.channel_to_claim.pack_item(
|
*Prefixes.channel_to_claim.pack_item(
|
||||||
self.signing_hash, self.name, self.effective_amount, self.tx_num, self.position,
|
self.signing_hash, self.name, self.tx_num, self.position, self.claim_hash
|
||||||
self.claim_hash, self.claims_in_channel_count
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
@ -267,5 +205,4 @@ class StagedClaimtrieItem(typing.NamedTuple):
|
||||||
)
|
)
|
||||||
delete_claim_ops = delete_prefix(db, DB_PREFIXES.claim_to_txo.value + self.claim_hash)
|
delete_claim_ops = delete_prefix(db, DB_PREFIXES.claim_to_txo.value + self.claim_hash)
|
||||||
delete_supports_ops = delete_prefix(db, DB_PREFIXES.claim_to_support.value + self.claim_hash)
|
delete_supports_ops = delete_prefix(db, DB_PREFIXES.claim_to_support.value + self.claim_hash)
|
||||||
invalidate_channel_ops = self.get_invalidate_channel_ops(db)
|
return delete_short_id_ops + delete_claim_ops + delete_supports_ops + self.get_invalidate_channel_ops(db)
|
||||||
return delete_short_id_ops + delete_claim_ops + delete_supports_ops + invalidate_channel_ops
|
|
||||||
|
|
|
@ -3,6 +3,10 @@ import struct
|
||||||
from lbry.wallet.server.db import DB_PREFIXES
|
from lbry.wallet.server.db import DB_PREFIXES
|
||||||
|
|
||||||
|
|
||||||
|
ACTIVATED_CLAIM_TXO_TYPE = 1
|
||||||
|
ACTIVATED_SUPPORT_TXO_TYPE = 2
|
||||||
|
|
||||||
|
|
||||||
def length_encoded_name(name: str) -> bytes:
|
def length_encoded_name(name: str) -> bytes:
|
||||||
encoded = name.encode('utf-8')
|
encoded = name.encode('utf-8')
|
||||||
return len(encoded).to_bytes(2, byteorder='big') + encoded
|
return len(encoded).to_bytes(2, byteorder='big') + encoded
|
||||||
|
@ -12,6 +16,11 @@ class PrefixRow:
|
||||||
prefix: bytes
|
prefix: bytes
|
||||||
key_struct: struct.Struct
|
key_struct: struct.Struct
|
||||||
value_struct: struct.Struct
|
value_struct: struct.Struct
|
||||||
|
key_part_lambdas = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pack_partial_key(cls, *args) -> bytes:
|
||||||
|
return cls.prefix + cls.key_part_lambdas[len(args)](*args)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, *args) -> bytes:
|
def pack_key(cls, *args) -> bytes:
|
||||||
|
@ -35,20 +44,6 @@ class PrefixRow:
|
||||||
return cls.unpack_key(key), cls.unpack_value(value)
|
return cls.unpack_key(key), cls.unpack_value(value)
|
||||||
|
|
||||||
|
|
||||||
class EffectiveAmountKey(typing.NamedTuple):
|
|
||||||
name: str
|
|
||||||
effective_amount: int
|
|
||||||
tx_num: int
|
|
||||||
position: int
|
|
||||||
|
|
||||||
|
|
||||||
class EffectiveAmountValue(typing.NamedTuple):
|
|
||||||
claim_hash: bytes
|
|
||||||
root_tx_num: int
|
|
||||||
root_position: int
|
|
||||||
activation: int
|
|
||||||
|
|
||||||
|
|
||||||
class ClaimToTXOKey(typing.NamedTuple):
|
class ClaimToTXOKey(typing.NamedTuple):
|
||||||
claim_hash: bytes
|
claim_hash: bytes
|
||||||
tx_num: int
|
tx_num: int
|
||||||
|
@ -59,7 +54,7 @@ class ClaimToTXOValue(typing.NamedTuple):
|
||||||
root_tx_num: int
|
root_tx_num: int
|
||||||
root_position: int
|
root_position: int
|
||||||
amount: int
|
amount: int
|
||||||
activation: int
|
# activation: int
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,7 +78,6 @@ class ClaimShortIDKey(typing.NamedTuple):
|
||||||
class ClaimShortIDValue(typing.NamedTuple):
|
class ClaimShortIDValue(typing.NamedTuple):
|
||||||
tx_num: int
|
tx_num: int
|
||||||
position: int
|
position: int
|
||||||
activation: int
|
|
||||||
|
|
||||||
|
|
||||||
class ClaimToChannelKey(typing.NamedTuple):
|
class ClaimToChannelKey(typing.NamedTuple):
|
||||||
|
@ -97,14 +91,12 @@ class ClaimToChannelValue(typing.NamedTuple):
|
||||||
class ChannelToClaimKey(typing.NamedTuple):
|
class ChannelToClaimKey(typing.NamedTuple):
|
||||||
signing_hash: bytes
|
signing_hash: bytes
|
||||||
name: str
|
name: str
|
||||||
effective_amount: int
|
|
||||||
tx_num: int
|
tx_num: int
|
||||||
position: int
|
position: int
|
||||||
|
|
||||||
|
|
||||||
class ChannelToClaimValue(typing.NamedTuple):
|
class ChannelToClaimValue(typing.NamedTuple):
|
||||||
claim_hash: bytes
|
claim_hash: bytes
|
||||||
claims_in_channel: int
|
|
||||||
|
|
||||||
|
|
||||||
class ClaimToSupportKey(typing.NamedTuple):
|
class ClaimToSupportKey(typing.NamedTuple):
|
||||||
|
@ -148,55 +140,92 @@ class ClaimTakeoverValue(typing.NamedTuple):
|
||||||
|
|
||||||
class PendingActivationKey(typing.NamedTuple):
|
class PendingActivationKey(typing.NamedTuple):
|
||||||
height: int
|
height: int
|
||||||
|
txo_type: int
|
||||||
tx_num: int
|
tx_num: int
|
||||||
position: int
|
position: int
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_support(self) -> bool:
|
||||||
|
return self.txo_type == ACTIVATED_SUPPORT_TXO_TYPE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_claim(self) -> bool:
|
||||||
|
return self.txo_type == ACTIVATED_CLAIM_TXO_TYPE
|
||||||
|
|
||||||
|
|
||||||
class PendingActivationValue(typing.NamedTuple):
|
class PendingActivationValue(typing.NamedTuple):
|
||||||
claim_hash: bytes
|
claim_hash: bytes
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class EffectiveAmountPrefixRow(PrefixRow):
|
class ActivationKey(typing.NamedTuple):
|
||||||
prefix = DB_PREFIXES.claim_effective_amount_prefix.value
|
txo_type: int
|
||||||
key_struct = struct.Struct(b'>QLH')
|
tx_num: int
|
||||||
value_struct = struct.Struct(b'>20sLHL')
|
position: int
|
||||||
|
|
||||||
|
|
||||||
|
class ActivationValue(typing.NamedTuple):
|
||||||
|
height: int
|
||||||
|
claim_hash: bytes
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveAmountKey(typing.NamedTuple):
|
||||||
|
claim_hash: bytes
|
||||||
|
txo_type: int
|
||||||
|
activation_height: int
|
||||||
|
tx_num: int
|
||||||
|
position: int
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveAmountValue(typing.NamedTuple):
|
||||||
|
amount: int
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveAmountPrefixRow(PrefixRow):
|
||||||
|
prefix = DB_PREFIXES.active_amount.value
|
||||||
|
key_struct = struct.Struct(b'>20sBLLH')
|
||||||
|
value_struct = struct.Struct(b'>Q')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>20s').pack,
|
||||||
|
struct.Struct(b'>20sB').pack,
|
||||||
|
struct.Struct(b'>20sBL').pack,
|
||||||
|
struct.Struct(b'>20sBLL').pack,
|
||||||
|
struct.Struct(b'>20sBLLH').pack
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, name: str, effective_amount: int, tx_num: int, position: int):
|
def pack_key(cls, claim_hash: bytes, txo_type: int, activation_height: int, tx_num: int, position: int):
|
||||||
return cls.prefix + length_encoded_name(name) + cls.key_struct.pack(
|
return super().pack_key(claim_hash, txo_type, activation_height, tx_num, position)
|
||||||
0xffffffffffffffff - effective_amount, tx_num, position
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_key(cls, key: bytes) -> EffectiveAmountKey:
|
def unpack_key(cls, key: bytes) -> ActiveAmountKey:
|
||||||
assert key[:1] == cls.prefix
|
return ActiveAmountKey(*super().unpack_key(key))
|
||||||
name_len = int.from_bytes(key[1:3], byteorder='big')
|
|
||||||
name = key[3:3 + name_len].decode()
|
|
||||||
ones_comp_effective_amount, tx_num, position = cls.key_struct.unpack(key[3 + name_len:])
|
|
||||||
return EffectiveAmountKey(
|
|
||||||
name, 0xffffffffffffffff - ones_comp_effective_amount, tx_num, position
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_value(cls, data: bytes) -> EffectiveAmountValue:
|
def unpack_value(cls, data: bytes) -> ActiveAmountValue:
|
||||||
return EffectiveAmountValue(*super().unpack_value(data))
|
return ActiveAmountValue(*super().unpack_value(data))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_value(cls, claim_hash: bytes, root_tx_num: int, root_position: int, activation: int) -> bytes:
|
def pack_value(cls, amount: int) -> bytes:
|
||||||
return super().pack_value(claim_hash, root_tx_num, root_position, activation)
|
return cls.value_struct.pack(amount)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_item(cls, name: str, effective_amount: int, tx_num: int, position: int, claim_hash: bytes,
|
def pack_item(cls, claim_hash: bytes, txo_type: int, activation_height: int, tx_num: int, position: int, amount: int):
|
||||||
root_tx_num: int, root_position: int, activation: int):
|
return cls.pack_key(claim_hash, txo_type, activation_height, tx_num, position), cls.pack_value(amount)
|
||||||
return cls.pack_key(name, effective_amount, tx_num, position), \
|
|
||||||
cls.pack_value(claim_hash, root_tx_num, root_position, activation)
|
|
||||||
|
|
||||||
|
|
||||||
class ClaimToTXOPrefixRow(PrefixRow):
|
class ClaimToTXOPrefixRow(PrefixRow):
|
||||||
prefix = DB_PREFIXES.claim_to_txo.value
|
prefix = DB_PREFIXES.claim_to_txo.value
|
||||||
key_struct = struct.Struct(b'>20sLH')
|
key_struct = struct.Struct(b'>20sLH')
|
||||||
value_struct = struct.Struct(b'>LHQL')
|
value_struct = struct.Struct(b'>LHQ')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>20s').pack,
|
||||||
|
struct.Struct(b'>20sL').pack,
|
||||||
|
struct.Struct(b'>20sLH').pack
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, claim_hash: bytes, tx_num: int, position: int):
|
def pack_key(cls, claim_hash: bytes, tx_num: int, position: int):
|
||||||
|
@ -214,20 +243,20 @@ class ClaimToTXOPrefixRow(PrefixRow):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_value(cls, data: bytes) -> ClaimToTXOValue:
|
def unpack_value(cls, data: bytes) -> ClaimToTXOValue:
|
||||||
root_tx_num, root_position, amount, activation = cls.value_struct.unpack(data[:18])
|
root_tx_num, root_position, amount = cls.value_struct.unpack(data[:14])
|
||||||
name_len = int.from_bytes(data[18:20], byteorder='big')
|
name_len = int.from_bytes(data[14:16], byteorder='big')
|
||||||
name = data[20:20 + name_len].decode()
|
name = data[16:16 + name_len].decode()
|
||||||
return ClaimToTXOValue(root_tx_num, root_position, amount, activation, name)
|
return ClaimToTXOValue(root_tx_num, root_position, amount, name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_value(cls, root_tx_num: int, root_position: int, amount: int, activation: int, name: str) -> bytes:
|
def pack_value(cls, root_tx_num: int, root_position: int, amount: int, name: str) -> bytes:
|
||||||
return cls.value_struct.pack(root_tx_num, root_position, amount, activation) + length_encoded_name(name)
|
return cls.value_struct.pack(root_tx_num, root_position, amount) + length_encoded_name(name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_item(cls, claim_hash: bytes, tx_num: int, position: int, root_tx_num: int, root_position: int,
|
def pack_item(cls, claim_hash: bytes, tx_num: int, position: int, root_tx_num: int, root_position: int,
|
||||||
amount: int, activation: int, name: str):
|
amount: int, name: str):
|
||||||
return cls.pack_key(claim_hash, tx_num, position), \
|
return cls.pack_key(claim_hash, tx_num, position), \
|
||||||
cls.pack_value(root_tx_num, root_position, amount, activation, name)
|
cls.pack_value(root_tx_num, root_position, amount, name)
|
||||||
|
|
||||||
|
|
||||||
class TXOToClaimPrefixRow(PrefixRow):
|
class TXOToClaimPrefixRow(PrefixRow):
|
||||||
|
@ -260,18 +289,32 @@ class TXOToClaimPrefixRow(PrefixRow):
|
||||||
cls.pack_value(claim_hash, name)
|
cls.pack_value(claim_hash, name)
|
||||||
|
|
||||||
|
|
||||||
|
def shortid_key_helper(struct_fmt):
|
||||||
|
packer = struct.Struct(struct_fmt).pack
|
||||||
|
def wrapper(name, *args):
|
||||||
|
return length_encoded_name(name) + packer(*args)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class ClaimShortIDPrefixRow(PrefixRow):
|
class ClaimShortIDPrefixRow(PrefixRow):
|
||||||
prefix = DB_PREFIXES.claim_short_id_prefix.value
|
prefix = DB_PREFIXES.claim_short_id_prefix.value
|
||||||
key_struct = struct.Struct(b'>20sLH')
|
key_struct = struct.Struct(b'>20sLH')
|
||||||
value_struct = struct.Struct(b'>LHL')
|
value_struct = struct.Struct(b'>LH')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
length_encoded_name,
|
||||||
|
shortid_key_helper(b'>20s'),
|
||||||
|
shortid_key_helper(b'>20sL'),
|
||||||
|
shortid_key_helper(b'>20sLH'),
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, name: str, claim_hash: bytes, root_tx_num: int, root_position: int):
|
def pack_key(cls, name: str, claim_hash: bytes, root_tx_num: int, root_position: int):
|
||||||
return cls.prefix + length_encoded_name(name) + cls.key_struct.pack(claim_hash, root_tx_num, root_position)
|
return cls.prefix + length_encoded_name(name) + cls.key_struct.pack(claim_hash, root_tx_num, root_position)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_value(cls, tx_num: int, position: int, activation: int):
|
def pack_value(cls, tx_num: int, position: int):
|
||||||
return super().pack_value(tx_num, position, activation)
|
return super().pack_value(tx_num, position)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_key(cls, key: bytes) -> ClaimShortIDKey:
|
def unpack_key(cls, key: bytes) -> ClaimShortIDKey:
|
||||||
|
@ -286,9 +329,9 @@ class ClaimShortIDPrefixRow(PrefixRow):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_item(cls, name: str, claim_hash: bytes, root_tx_num: int, root_position: int,
|
def pack_item(cls, name: str, claim_hash: bytes, root_tx_num: int, root_position: int,
|
||||||
tx_num: int, position: int, activation: int):
|
tx_num: int, position: int):
|
||||||
return cls.pack_key(name, claim_hash, root_tx_num, root_position), \
|
return cls.pack_key(name, claim_hash, root_tx_num, root_position), \
|
||||||
cls.pack_value(tx_num, position, activation)
|
cls.pack_value(tx_num, position)
|
||||||
|
|
||||||
|
|
||||||
class ClaimToChannelPrefixRow(PrefixRow):
|
class ClaimToChannelPrefixRow(PrefixRow):
|
||||||
|
@ -317,15 +360,33 @@ class ClaimToChannelPrefixRow(PrefixRow):
|
||||||
return cls.pack_key(claim_hash), cls.pack_value(signing_hash)
|
return cls.pack_key(claim_hash), cls.pack_value(signing_hash)
|
||||||
|
|
||||||
|
|
||||||
|
def channel_to_claim_helper(struct_fmt):
|
||||||
|
packer = struct.Struct(struct_fmt).pack
|
||||||
|
|
||||||
|
def wrapper(signing_hash: bytes, name: str, *args):
|
||||||
|
return signing_hash + length_encoded_name(name) + packer(*args)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class ChannelToClaimPrefixRow(PrefixRow):
|
class ChannelToClaimPrefixRow(PrefixRow):
|
||||||
prefix = DB_PREFIXES.channel_to_claim.value
|
prefix = DB_PREFIXES.channel_to_claim.value
|
||||||
key_struct = struct.Struct(b'>QLH')
|
key_struct = struct.Struct(b'>LH')
|
||||||
value_struct = struct.Struct(b'>20sL')
|
value_struct = struct.Struct(b'>20s')
|
||||||
|
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>20s').pack,
|
||||||
|
channel_to_claim_helper(b''),
|
||||||
|
channel_to_claim_helper(b'>s'),
|
||||||
|
channel_to_claim_helper(b'>L'),
|
||||||
|
channel_to_claim_helper(b'>LH'),
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, signing_hash: bytes, name: str, effective_amount: int, tx_num: int, position: int):
|
def pack_key(cls, signing_hash: bytes, name: str, tx_num: int, position: int):
|
||||||
return cls.prefix + signing_hash + length_encoded_name(name) + cls.key_struct.pack(
|
return cls.prefix + signing_hash + length_encoded_name(name) + cls.key_struct.pack(
|
||||||
0xffffffffffffffff - effective_amount, tx_num, position
|
tx_num, position
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -334,24 +395,24 @@ class ChannelToClaimPrefixRow(PrefixRow):
|
||||||
signing_hash = key[1:21]
|
signing_hash = key[1:21]
|
||||||
name_len = int.from_bytes(key[21:23], byteorder='big')
|
name_len = int.from_bytes(key[21:23], byteorder='big')
|
||||||
name = key[23:23 + name_len].decode()
|
name = key[23:23 + name_len].decode()
|
||||||
ones_comp_effective_amount, tx_num, position = cls.key_struct.unpack(key[23 + name_len:])
|
tx_num, position = cls.key_struct.unpack(key[23 + name_len:])
|
||||||
return ChannelToClaimKey(
|
return ChannelToClaimKey(
|
||||||
signing_hash, name, 0xffffffffffffffff - ones_comp_effective_amount, tx_num, position
|
signing_hash, name, tx_num, position
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_value(cls, claim_hash: bytes, claims_in_channel: int) -> bytes:
|
def pack_value(cls, claim_hash: bytes) -> bytes:
|
||||||
return super().pack_value(claim_hash, claims_in_channel)
|
return super().pack_value(claim_hash)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_value(cls, data: bytes) -> ChannelToClaimValue:
|
def unpack_value(cls, data: bytes) -> ChannelToClaimValue:
|
||||||
return ChannelToClaimValue(*cls.value_struct.unpack(data))
|
return ChannelToClaimValue(*cls.value_struct.unpack(data))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_item(cls, signing_hash: bytes, name: str, effective_amount: int, tx_num: int, position: int,
|
def pack_item(cls, signing_hash: bytes, name: str, tx_num: int, position: int,
|
||||||
claim_hash: bytes, claims_in_channel: int):
|
claim_hash: bytes):
|
||||||
return cls.pack_key(signing_hash, name, effective_amount, tx_num, position), \
|
return cls.pack_key(signing_hash, name, tx_num, position), \
|
||||||
cls.pack_value(claim_hash, claims_in_channel)
|
cls.pack_value(claim_hash)
|
||||||
|
|
||||||
|
|
||||||
class ClaimToSupportPrefixRow(PrefixRow):
|
class ClaimToSupportPrefixRow(PrefixRow):
|
||||||
|
@ -412,6 +473,12 @@ class ClaimExpirationPrefixRow(PrefixRow):
|
||||||
prefix = DB_PREFIXES.claim_expiration.value
|
prefix = DB_PREFIXES.claim_expiration.value
|
||||||
key_struct = struct.Struct(b'>LLH')
|
key_struct = struct.Struct(b'>LLH')
|
||||||
value_struct = struct.Struct(b'>20s')
|
value_struct = struct.Struct(b'>20s')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>L').pack,
|
||||||
|
struct.Struct(b'>LL').pack,
|
||||||
|
struct.Struct(b'>LLH').pack,
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, expiration: int, tx_num: int, position: int) -> bytes:
|
def pack_key(cls, expiration: int, tx_num: int, position: int) -> bytes:
|
||||||
|
@ -469,13 +536,20 @@ class ClaimTakeoverPrefixRow(PrefixRow):
|
||||||
return cls.pack_key(name), cls.pack_value(claim_hash, takeover_height)
|
return cls.pack_key(name), cls.pack_value(claim_hash, takeover_height)
|
||||||
|
|
||||||
|
|
||||||
class PendingClaimActivationPrefixRow(PrefixRow):
|
class PendingActivationPrefixRow(PrefixRow):
|
||||||
prefix = DB_PREFIXES.pending_activation.value
|
prefix = DB_PREFIXES.pending_activation.value
|
||||||
key_struct = struct.Struct(b'>LLH')
|
key_struct = struct.Struct(b'>LBLH')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>L').pack,
|
||||||
|
struct.Struct(b'>LB').pack,
|
||||||
|
struct.Struct(b'>LBL').pack,
|
||||||
|
struct.Struct(b'>LBLH').pack
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_key(cls, height: int, tx_num: int, position: int):
|
def pack_key(cls, height: int, txo_type: int, tx_num: int, position: int):
|
||||||
return super().pack_key(height, tx_num, position)
|
return super().pack_key(height, txo_type, tx_num, position)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_key(cls, key: bytes) -> PendingActivationKey:
|
def unpack_key(cls, key: bytes) -> PendingActivationKey:
|
||||||
|
@ -493,11 +567,47 @@ class PendingClaimActivationPrefixRow(PrefixRow):
|
||||||
return PendingActivationValue(claim_hash, name)
|
return PendingActivationValue(claim_hash, name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pack_item(cls, height: int, tx_num: int, position: int, claim_hash: bytes, name: str):
|
def pack_item(cls, height: int, txo_type: int, tx_num: int, position: int, claim_hash: bytes, name: str):
|
||||||
return cls.pack_key(height, tx_num, position), \
|
return cls.pack_key(height, txo_type, tx_num, position), \
|
||||||
cls.pack_value(claim_hash, name)
|
cls.pack_value(claim_hash, name)
|
||||||
|
|
||||||
|
|
||||||
|
class ActivatedPrefixRow(PrefixRow):
|
||||||
|
prefix = DB_PREFIXES.activated_claim_and_support.value
|
||||||
|
key_struct = struct.Struct(b'>BLH')
|
||||||
|
value_struct = struct.Struct(b'>L20s')
|
||||||
|
key_part_lambdas = [
|
||||||
|
lambda: b'',
|
||||||
|
struct.Struct(b'>B').pack,
|
||||||
|
struct.Struct(b'>BL').pack,
|
||||||
|
struct.Struct(b'>BLH').pack
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pack_key(cls, txo_type: int, tx_num: int, position: int):
|
||||||
|
return super().pack_key(txo_type, tx_num, position)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unpack_key(cls, key: bytes) -> ActivationKey:
|
||||||
|
return ActivationKey(*super().unpack_key(key))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pack_value(cls, height: int, claim_hash: bytes, name: str) -> bytes:
|
||||||
|
return cls.value_struct.pack(height, claim_hash) + length_encoded_name(name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unpack_value(cls, data: bytes) -> ActivationValue:
|
||||||
|
height, claim_hash = cls.value_struct.unpack(data[:24])
|
||||||
|
name_len = int.from_bytes(data[24:26], byteorder='big')
|
||||||
|
name = data[26:26 + name_len].decode()
|
||||||
|
return ActivationValue(height, claim_hash, name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pack_item(cls, txo_type: int, tx_num: int, position: int, height: int, claim_hash: bytes, name: str):
|
||||||
|
return cls.pack_key(txo_type, tx_num, position), \
|
||||||
|
cls.pack_value(height, claim_hash, name)
|
||||||
|
|
||||||
|
|
||||||
class Prefixes:
|
class Prefixes:
|
||||||
claim_to_support = ClaimToSupportPrefixRow
|
claim_to_support = ClaimToSupportPrefixRow
|
||||||
support_to_claim = SupportToClaimPrefixRow
|
support_to_claim = SupportToClaimPrefixRow
|
||||||
|
@ -509,10 +619,11 @@ class Prefixes:
|
||||||
channel_to_claim = ChannelToClaimPrefixRow
|
channel_to_claim = ChannelToClaimPrefixRow
|
||||||
|
|
||||||
claim_short_id = ClaimShortIDPrefixRow
|
claim_short_id = ClaimShortIDPrefixRow
|
||||||
claim_effective_amount = EffectiveAmountPrefixRow
|
|
||||||
claim_expiration = ClaimExpirationPrefixRow
|
claim_expiration = ClaimExpirationPrefixRow
|
||||||
|
|
||||||
claim_takeover = ClaimTakeoverPrefixRow
|
claim_takeover = ClaimTakeoverPrefixRow
|
||||||
pending_activation = PendingClaimActivationPrefixRow
|
pending_activation = PendingActivationPrefixRow
|
||||||
|
activated = ActivatedPrefixRow
|
||||||
|
active_amount = ActiveAmountPrefixRow
|
||||||
|
|
||||||
# undo_claimtrie = b'M'
|
# undo_claimtrie = b'M'
|
||||||
|
|
|
@ -18,7 +18,7 @@ import struct
|
||||||
import attr
|
import attr
|
||||||
import zlib
|
import zlib
|
||||||
import base64
|
import base64
|
||||||
from typing import Optional, Iterable
|
from typing import Optional, Iterable, Tuple, DefaultDict, Set, Dict, List
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from asyncio import sleep
|
from asyncio import sleep
|
||||||
from bisect import bisect_right, bisect_left
|
from bisect import bisect_right, bisect_left
|
||||||
|
@ -37,8 +37,9 @@ from lbry.wallet.server.storage import db_class
|
||||||
from lbry.wallet.server.db.revertable import RevertablePut, RevertableDelete, RevertableOp, delete_prefix
|
from lbry.wallet.server.db.revertable import RevertablePut, RevertableDelete, RevertableOp, delete_prefix
|
||||||
from lbry.wallet.server.db import DB_PREFIXES
|
from lbry.wallet.server.db import DB_PREFIXES
|
||||||
from lbry.wallet.server.db.prefixes import Prefixes, PendingActivationValue, ClaimTakeoverValue, ClaimToTXOValue
|
from lbry.wallet.server.db.prefixes import Prefixes, PendingActivationValue, ClaimTakeoverValue, ClaimToTXOValue
|
||||||
from lbry.wallet.server.db.claimtrie import StagedClaimtrieItem, get_update_effective_amount_ops, length_encoded_name
|
from lbry.wallet.server.db.prefixes import ACTIVATED_CLAIM_TXO_TYPE, ACTIVATED_SUPPORT_TXO_TYPE
|
||||||
from lbry.wallet.server.db.claimtrie import get_expiration_height, get_delay_for_name
|
from lbry.wallet.server.db.prefixes import PendingActivationKey, ClaimToTXOKey, TXOToClaimValue
|
||||||
|
from lbry.wallet.server.db.claimtrie import StagedClaimtrieItem, length_encoded_name
|
||||||
|
|
||||||
from lbry.wallet.server.db.elasticsearch import SearchIndex
|
from lbry.wallet.server.db.elasticsearch import SearchIndex
|
||||||
|
|
||||||
|
@ -56,17 +57,6 @@ TXO_STRUCT_unpack = TXO_STRUCT.unpack
|
||||||
TXO_STRUCT_pack = TXO_STRUCT.pack
|
TXO_STRUCT_pack = TXO_STRUCT.pack
|
||||||
|
|
||||||
|
|
||||||
HISTORY_PREFIX = b'A'
|
|
||||||
TX_PREFIX = b'B'
|
|
||||||
BLOCK_HASH_PREFIX = b'C'
|
|
||||||
HEADER_PREFIX = b'H'
|
|
||||||
TX_NUM_PREFIX = b'N'
|
|
||||||
TX_COUNT_PREFIX = b'T'
|
|
||||||
UNDO_PREFIX = b'U'
|
|
||||||
TX_HASH_PREFIX = b'X'
|
|
||||||
HASHX_UTXO_PREFIX = b'h'
|
|
||||||
UTXO_PREFIX = b'u'
|
|
||||||
HASHX_HISTORY_PREFIX = b'x'
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True)
|
@attr.s(slots=True)
|
||||||
|
@ -188,12 +178,22 @@ class LevelDB:
|
||||||
# Search index
|
# Search index
|
||||||
self.search_index = SearchIndex(self.env.es_index_prefix, self.env.database_query_timeout)
|
self.search_index = SearchIndex(self.env.es_index_prefix, self.env.database_query_timeout)
|
||||||
|
|
||||||
def claim_hash_and_name_from_txo(self, tx_num: int, tx_idx: int):
|
def get_claim_from_txo(self, tx_num: int, tx_idx: int) -> Optional[TXOToClaimValue]:
|
||||||
claim_hash_and_name = self.db.get(Prefixes.txo_to_claim.pack_key(tx_num, tx_idx))
|
claim_hash_and_name = self.db.get(Prefixes.txo_to_claim.pack_key(tx_num, tx_idx))
|
||||||
if not claim_hash_and_name:
|
if not claim_hash_and_name:
|
||||||
return
|
return
|
||||||
return Prefixes.txo_to_claim.unpack_value(claim_hash_and_name)
|
return Prefixes.txo_to_claim.unpack_value(claim_hash_and_name)
|
||||||
|
|
||||||
|
def get_activation(self, tx_num, position, is_support=False) -> int:
|
||||||
|
activation = self.db.get(
|
||||||
|
Prefixes.activated.pack_key(
|
||||||
|
ACTIVATED_SUPPORT_TXO_TYPE if is_support else ACTIVATED_CLAIM_TXO_TYPE, tx_num, position
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if activation:
|
||||||
|
return Prefixes.activated.unpack_value(activation).height
|
||||||
|
return -1
|
||||||
|
|
||||||
def get_supported_claim_from_txo(self, tx_num: int, position: int) -> typing.Tuple[Optional[bytes], Optional[int]]:
|
def get_supported_claim_from_txo(self, tx_num: int, position: int) -> typing.Tuple[Optional[bytes], Optional[int]]:
|
||||||
key = Prefixes.support_to_claim.pack_key(tx_num, position)
|
key = Prefixes.support_to_claim.pack_key(tx_num, position)
|
||||||
supported_claim_hash = self.db.get(key)
|
supported_claim_hash = self.db.get(key)
|
||||||
|
@ -228,7 +228,7 @@ class LevelDB:
|
||||||
created_height = bisect_right(self.tx_counts, root_tx_num)
|
created_height = bisect_right(self.tx_counts, root_tx_num)
|
||||||
last_take_over_height = controlling_claim.height
|
last_take_over_height = controlling_claim.height
|
||||||
|
|
||||||
expiration_height = get_expiration_height(height)
|
expiration_height = self.coin.get_expiration_height(height)
|
||||||
support_amount = self.get_support_amount(claim_hash)
|
support_amount = self.get_support_amount(claim_hash)
|
||||||
claim_amount = self.get_claim_txo_amount(claim_hash, tx_num, position)
|
claim_amount = self.get_claim_txo_amount(claim_hash, tx_num, position)
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ class LevelDB:
|
||||||
short_url = f'{name}#{claim_hash.hex()}'
|
short_url = f'{name}#{claim_hash.hex()}'
|
||||||
canonical_url = short_url
|
canonical_url = short_url
|
||||||
if channel_hash:
|
if channel_hash:
|
||||||
channel_vals = self.get_root_claim_txo_and_current_amount(channel_hash)
|
channel_vals = self.get_claim_txo(channel_hash)
|
||||||
if channel_vals:
|
if channel_vals:
|
||||||
channel_name = channel_vals[1].name
|
channel_name = channel_vals[1].name
|
||||||
claims_in_channel = self.get_claims_in_channel_count(channel_hash)
|
claims_in_channel = self.get_claims_in_channel_count(channel_hash)
|
||||||
|
@ -260,11 +260,13 @@ class LevelDB:
|
||||||
:param claim_id: partial or complete claim id
|
:param claim_id: partial or complete claim id
|
||||||
:param amount_order: '$<value>' suffix to a url, defaults to 1 (winning) if no claim id modifier is provided
|
:param amount_order: '$<value>' suffix to a url, defaults to 1 (winning) if no claim id modifier is provided
|
||||||
"""
|
"""
|
||||||
if not amount_order and not claim_id:
|
if (not amount_order and not claim_id) or amount_order == 1:
|
||||||
# winning resolution
|
# winning resolution
|
||||||
controlling = self.get_controlling_claim(normalized_name)
|
controlling = self.get_controlling_claim(normalized_name)
|
||||||
if not controlling:
|
if not controlling:
|
||||||
|
print("none controlling")
|
||||||
return
|
return
|
||||||
|
print("resolved controlling", controlling.claim_hash.hex())
|
||||||
return self._fs_get_claim_by_hash(controlling.claim_hash)
|
return self._fs_get_claim_by_hash(controlling.claim_hash)
|
||||||
|
|
||||||
encoded_name = length_encoded_name(normalized_name)
|
encoded_name = length_encoded_name(normalized_name)
|
||||||
|
@ -279,7 +281,7 @@ class LevelDB:
|
||||||
claim_txo = Prefixes.claim_short_id.unpack_value(v)
|
claim_txo = Prefixes.claim_short_id.unpack_value(v)
|
||||||
return self._prepare_resolve_result(
|
return self._prepare_resolve_result(
|
||||||
claim_txo.tx_num, claim_txo.position, key.claim_hash, key.name, key.root_tx_num,
|
claim_txo.tx_num, claim_txo.position, key.claim_hash, key.name, key.root_tx_num,
|
||||||
key.root_position, claim_txo.activation
|
key.root_position, self.get_activation(claim_txo.tx_num, claim_txo.position)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -302,8 +304,9 @@ class LevelDB:
|
||||||
for k, v in self.db.iterator(prefix=prefix):
|
for k, v in self.db.iterator(prefix=prefix):
|
||||||
key = Prefixes.channel_to_claim.unpack_key(k)
|
key = Prefixes.channel_to_claim.unpack_key(k)
|
||||||
stream = Prefixes.channel_to_claim.unpack_value(v)
|
stream = Prefixes.channel_to_claim.unpack_value(v)
|
||||||
if not candidates or candidates[-1][-1] == key.effective_amount:
|
effective_amount = self.get_effective_amount(stream.claim_hash)
|
||||||
candidates.append((stream.claim_hash, key.tx_num, key.position, key.effective_amount))
|
if not candidates or candidates[-1][-1] == effective_amount:
|
||||||
|
candidates.append((stream.claim_hash, key.tx_num, key.position, effective_amount))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if not candidates:
|
if not candidates:
|
||||||
|
@ -347,12 +350,13 @@ class LevelDB:
|
||||||
return await asyncio.get_event_loop().run_in_executor(self.executor, self._fs_resolve, url)
|
return await asyncio.get_event_loop().run_in_executor(self.executor, self._fs_resolve, url)
|
||||||
|
|
||||||
def _fs_get_claim_by_hash(self, claim_hash):
|
def _fs_get_claim_by_hash(self, claim_hash):
|
||||||
for k, v in self.db.iterator(prefix=DB_PREFIXES.claim_to_txo.value + claim_hash):
|
for k, v in self.db.iterator(prefix=Prefixes.claim_to_txo.pack_partial_key(claim_hash)):
|
||||||
unpacked_k = Prefixes.claim_to_txo.unpack_key(k)
|
unpacked_k = Prefixes.claim_to_txo.unpack_key(k)
|
||||||
unpacked_v = Prefixes.claim_to_txo.unpack_value(v)
|
unpacked_v = Prefixes.claim_to_txo.unpack_value(v)
|
||||||
|
activation_height = self.get_activation(unpacked_k.tx_num, unpacked_k.position)
|
||||||
return self._prepare_resolve_result(
|
return self._prepare_resolve_result(
|
||||||
unpacked_k.tx_num, unpacked_k.position, unpacked_k.claim_hash, unpacked_v.name,
|
unpacked_k.tx_num, unpacked_k.position, unpacked_k.claim_hash, unpacked_v.name,
|
||||||
unpacked_v.root_tx_num, unpacked_v.root_position, unpacked_v.activation
|
unpacked_v.root_tx_num, unpacked_v.root_position, activation_height
|
||||||
)
|
)
|
||||||
|
|
||||||
async def fs_getclaimbyid(self, claim_id):
|
async def fs_getclaimbyid(self, claim_id):
|
||||||
|
@ -360,19 +364,8 @@ class LevelDB:
|
||||||
self.executor, self._fs_get_claim_by_hash, bytes.fromhex(claim_id)
|
self.executor, self._fs_get_claim_by_hash, bytes.fromhex(claim_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
def claim_exists(self, claim_hash: bytes):
|
|
||||||
for _ in self.db.iterator(prefix=DB_PREFIXES.claim_to_txo.value + claim_hash, include_value=False):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_root_claim_txo_and_current_amount(self, claim_hash):
|
|
||||||
for k, v in self.db.iterator(prefix=DB_PREFIXES.claim_to_txo.value + claim_hash):
|
|
||||||
unpacked_k = Prefixes.claim_to_txo.unpack_key(k)
|
|
||||||
unpacked_v = Prefixes.claim_to_txo.unpack_value(v)
|
|
||||||
return unpacked_k, unpacked_v
|
|
||||||
|
|
||||||
def make_staged_claim_item(self, claim_hash: bytes) -> Optional[StagedClaimtrieItem]:
|
def make_staged_claim_item(self, claim_hash: bytes) -> Optional[StagedClaimtrieItem]:
|
||||||
claim_info = self.get_root_claim_txo_and_current_amount(claim_hash)
|
claim_info = self.get_claim_txo(claim_hash)
|
||||||
k, v = claim_info
|
k, v = claim_info
|
||||||
root_tx_num = v.root_tx_num
|
root_tx_num = v.root_tx_num
|
||||||
root_idx = v.root_position
|
root_idx = v.root_position
|
||||||
|
@ -381,16 +374,14 @@ class LevelDB:
|
||||||
tx_num = k.tx_num
|
tx_num = k.tx_num
|
||||||
idx = k.position
|
idx = k.position
|
||||||
height = bisect_right(self.tx_counts, tx_num)
|
height = bisect_right(self.tx_counts, tx_num)
|
||||||
effective_amount = self.get_support_amount(claim_hash) + value
|
|
||||||
signing_hash = self.get_channel_for_claim(claim_hash)
|
signing_hash = self.get_channel_for_claim(claim_hash)
|
||||||
activation_height = v.activation
|
# if signing_hash:
|
||||||
if signing_hash:
|
# count = self.get_claims_in_channel_count(signing_hash)
|
||||||
count = self.get_claims_in_channel_count(signing_hash)
|
# else:
|
||||||
else:
|
# count = 0
|
||||||
count = 0
|
|
||||||
return StagedClaimtrieItem(
|
return StagedClaimtrieItem(
|
||||||
name, claim_hash, value, effective_amount, activation_height, get_expiration_height(height), tx_num, idx,
|
name, claim_hash, value, self.coin.get_expiration_height(height), tx_num, idx,
|
||||||
root_tx_num, root_idx, signing_hash, count
|
root_tx_num, root_idx, signing_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_claim_txo_amount(self, claim_hash: bytes, tx_num: int, position: int) -> Optional[int]:
|
def get_claim_txo_amount(self, claim_hash: bytes, tx_num: int, position: int) -> Optional[int]:
|
||||||
|
@ -398,58 +389,57 @@ class LevelDB:
|
||||||
if v:
|
if v:
|
||||||
return Prefixes.claim_to_txo.unpack_value(v).amount
|
return Prefixes.claim_to_txo.unpack_value(v).amount
|
||||||
|
|
||||||
def get_claim_from_txo(self, claim_hash: bytes) -> Optional[ClaimToTXOValue]:
|
def get_support_txo_amount(self, claim_hash: bytes, tx_num: int, position: int) -> Optional[int]:
|
||||||
|
v = self.db.get(Prefixes.claim_to_support.pack_key(claim_hash, tx_num, position))
|
||||||
|
if v:
|
||||||
|
return Prefixes.claim_to_support.unpack_value(v).amount
|
||||||
|
|
||||||
|
def get_claim_txo(self, claim_hash: bytes) -> Optional[Tuple[ClaimToTXOKey, ClaimToTXOValue]]:
|
||||||
assert claim_hash
|
assert claim_hash
|
||||||
for v in self.db.iterator(prefix=DB_PREFIXES.claim_to_txo.value + claim_hash, include_key=False):
|
for k, v in self.db.iterator(prefix=Prefixes.claim_to_txo.pack_partial_key(claim_hash)):
|
||||||
return Prefixes.claim_to_txo.unpack_value(v)
|
return Prefixes.claim_to_txo.unpack_key(k), Prefixes.claim_to_txo.unpack_value(v)
|
||||||
|
|
||||||
def get_claim_amount(self, claim_hash: bytes) -> Optional[int]:
|
def _get_active_amount(self, claim_hash: bytes, txo_type: int, height: int) -> int:
|
||||||
claim = self.get_claim_from_txo(claim_hash)
|
return sum(
|
||||||
if claim:
|
Prefixes.active_amount.unpack_value(v).amount
|
||||||
return claim.amount
|
for v in self.db.iterator(start=Prefixes.active_amount.pack_partial_key(
|
||||||
|
claim_hash, txo_type, 0), stop=Prefixes.active_amount.pack_partial_key(
|
||||||
def get_effective_amount(self, claim_hash: bytes):
|
claim_hash, txo_type, height), include_key=False)
|
||||||
return (self.get_claim_amount(claim_hash) or 0) + self.get_support_amount(claim_hash)
|
|
||||||
|
|
||||||
def get_update_effective_amount_ops(self, claim_hash: bytes, effective_amount: int):
|
|
||||||
claim_info = self.get_root_claim_txo_and_current_amount(claim_hash)
|
|
||||||
if not claim_info:
|
|
||||||
return []
|
|
||||||
|
|
||||||
root_tx_num = claim_info[1].root_tx_num
|
|
||||||
root_position = claim_info[1].root_position
|
|
||||||
amount = claim_info[1].amount
|
|
||||||
name = claim_info[1].name
|
|
||||||
tx_num = claim_info[0].tx_num
|
|
||||||
position = claim_info[0].position
|
|
||||||
activation = claim_info[1].activation
|
|
||||||
signing_hash = self.get_channel_for_claim(claim_hash)
|
|
||||||
claims_in_channel_count = None
|
|
||||||
if signing_hash:
|
|
||||||
claims_in_channel_count = self.get_claims_in_channel_count(signing_hash)
|
|
||||||
prev_effective_amount = self.get_effective_amount(claim_hash)
|
|
||||||
return get_update_effective_amount_ops(
|
|
||||||
name, effective_amount, prev_effective_amount, tx_num, position,
|
|
||||||
root_tx_num, root_position, claim_hash, activation, activation, signing_hash,
|
|
||||||
claims_in_channel_count
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_effective_amount(self, claim_hash: bytes, support_only=False) -> int:
|
||||||
|
support_amount = self._get_active_amount(claim_hash, ACTIVATED_SUPPORT_TXO_TYPE, self.db_height + 1)
|
||||||
|
if support_only:
|
||||||
|
return support_only
|
||||||
|
return support_amount + self._get_active_amount(claim_hash, ACTIVATED_CLAIM_TXO_TYPE, self.db_height + 1)
|
||||||
|
|
||||||
|
def get_claims_for_name(self, name):
|
||||||
|
claims = []
|
||||||
|
for _k, _v in self.db.iterator(prefix=Prefixes.claim_short_id.pack_partial_key(name)):
|
||||||
|
k, v = Prefixes.claim_short_id.unpack_key(_k), Prefixes.claim_short_id.unpack_value(_v)
|
||||||
|
# claims[v.claim_hash] = (k, v)
|
||||||
|
if k.claim_hash not in claims:
|
||||||
|
claims.append(k.claim_hash)
|
||||||
|
return claims
|
||||||
|
|
||||||
def get_claims_in_channel_count(self, channel_hash) -> int:
|
def get_claims_in_channel_count(self, channel_hash) -> int:
|
||||||
for v in self.db.iterator(prefix=DB_PREFIXES.channel_to_claim.value + channel_hash, include_key=False):
|
count = 0
|
||||||
return Prefixes.channel_to_claim.unpack_value(v).claims_in_channel
|
for _ in self.db.iterator(prefix=Prefixes.channel_to_claim.pack_partial_key(channel_hash), include_key=False):
|
||||||
return 0
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
def get_channel_for_claim(self, claim_hash) -> Optional[bytes]:
|
def get_channel_for_claim(self, claim_hash) -> Optional[bytes]:
|
||||||
return self.db.get(DB_PREFIXES.claim_to_channel.value + claim_hash)
|
return self.db.get(Prefixes.claim_to_channel.pack_key(claim_hash))
|
||||||
|
|
||||||
def get_expired_by_height(self, height: int):
|
def get_expired_by_height(self, height: int) -> Dict[bytes, Tuple[int, int, str, TxInput]]:
|
||||||
expired = {}
|
expired = {}
|
||||||
for _k, _v in self.db.iterator(prefix=DB_PREFIXES.claim_expiration.value + struct.pack(b'>L', height)):
|
for _k, _v in self.db.iterator(prefix=Prefixes.claim_expiration.pack_partial_key(height)):
|
||||||
k, v = Prefixes.claim_expiration.unpack_item(_k, _v)
|
k, v = Prefixes.claim_expiration.unpack_item(_k, _v)
|
||||||
tx_hash = self.total_transactions[k.tx_num]
|
tx_hash = self.total_transactions[k.tx_num]
|
||||||
tx = self.coin.transaction(self.db.get(DB_PREFIXES.TX_PREFIX.value + tx_hash))
|
tx = self.coin.transaction(self.db.get(DB_PREFIXES.TX_PREFIX.value + tx_hash))
|
||||||
# treat it like a claim spend so it will delete/abandon properly
|
# treat it like a claim spend so it will delete/abandon properly
|
||||||
# the _spend_claim function this result is fed to expects a txi, so make a mock one
|
# the _spend_claim function this result is fed to expects a txi, so make a mock one
|
||||||
|
print(f"\texpired lbry://{v.name} {v.claim_hash.hex()}")
|
||||||
expired[v.claim_hash] = (
|
expired[v.claim_hash] = (
|
||||||
k.tx_num, k.position, v.name,
|
k.tx_num, k.position, v.name,
|
||||||
TxInput(prev_hash=tx_hash, prev_idx=k.position, script=tx.outputs[k.position].pk_script, sequence=0)
|
TxInput(prev_hash=tx_hash, prev_idx=k.position, script=tx.outputs[k.position].pk_script, sequence=0)
|
||||||
|
@ -462,28 +452,21 @@ class LevelDB:
|
||||||
return
|
return
|
||||||
return Prefixes.claim_takeover.unpack_value(controlling)
|
return Prefixes.claim_takeover.unpack_value(controlling)
|
||||||
|
|
||||||
def get_claims_for_name(self, name: str):
|
def get_claim_txos_for_name(self, name: str):
|
||||||
claim_hashes = set()
|
txos = {}
|
||||||
for k in self.db.iterator(prefix=Prefixes.claim_short_id.prefix + length_encoded_name(name),
|
for k, v in self.db.iterator(prefix=Prefixes.claim_short_id.pack_partial_key(name)):
|
||||||
include_value=False):
|
claim_hash = Prefixes.claim_short_id.unpack_key(k).claim_hash
|
||||||
claim_hashes.add(Prefixes.claim_short_id.unpack_key(k).claim_hash)
|
tx_num, nout = Prefixes.claim_short_id.unpack_value(v)
|
||||||
return claim_hashes
|
txos[claim_hash] = tx_num, nout
|
||||||
|
return txos
|
||||||
|
|
||||||
def get_activated_claims_at_height(self, height: int) -> typing.Set[PendingActivationValue]:
|
def get_activated_at_height(self, height: int) -> DefaultDict[PendingActivationValue, List[PendingActivationKey]]:
|
||||||
claims = set()
|
activated = defaultdict(list)
|
||||||
prefix = Prefixes.pending_activation.prefix + height.to_bytes(4, byteorder='big')
|
for _k, _v in self.db.iterator(prefix=Prefixes.pending_activation.pack_partial_key(height)):
|
||||||
for _v in self.db.iterator(prefix=prefix, include_key=False):
|
k = Prefixes.pending_activation.unpack_key(_k)
|
||||||
v = Prefixes.pending_activation.unpack_value(_v)
|
v = Prefixes.pending_activation.unpack_value(_v)
|
||||||
claims.add(v)
|
activated[v].append(k)
|
||||||
return claims
|
return activated
|
||||||
|
|
||||||
def get_activation_delay(self, claim_hash: bytes, name: str) -> int:
|
|
||||||
controlling = self.get_controlling_claim(name)
|
|
||||||
if not controlling:
|
|
||||||
return 0
|
|
||||||
if claim_hash == controlling.claim_hash:
|
|
||||||
return 0
|
|
||||||
return get_delay_for_name(self.db_height - controlling.height)
|
|
||||||
|
|
||||||
async def _read_tx_counts(self):
|
async def _read_tx_counts(self):
|
||||||
if self.tx_counts is not None:
|
if self.tx_counts is not None:
|
||||||
|
@ -494,7 +477,7 @@ class LevelDB:
|
||||||
def get_counts():
|
def get_counts():
|
||||||
return tuple(
|
return tuple(
|
||||||
util.unpack_be_uint64(tx_count)
|
util.unpack_be_uint64(tx_count)
|
||||||
for tx_count in self.db.iterator(prefix=TX_COUNT_PREFIX, include_key=False)
|
for tx_count in self.db.iterator(prefix=DB_PREFIXES.TX_COUNT_PREFIX.value, include_key=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
tx_counts = await asyncio.get_event_loop().run_in_executor(self.executor, get_counts)
|
tx_counts = await asyncio.get_event_loop().run_in_executor(self.executor, get_counts)
|
||||||
|
@ -509,7 +492,7 @@ class LevelDB:
|
||||||
|
|
||||||
async def _read_txids(self):
|
async def _read_txids(self):
|
||||||
def get_txids():
|
def get_txids():
|
||||||
return list(self.db.iterator(prefix=TX_HASH_PREFIX, include_key=False))
|
return list(self.db.iterator(prefix=DB_PREFIXES.TX_HASH_PREFIX.value, include_key=False))
|
||||||
|
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
self.logger.info("loading txids")
|
self.logger.info("loading txids")
|
||||||
|
@ -528,7 +511,7 @@ class LevelDB:
|
||||||
|
|
||||||
def get_headers():
|
def get_headers():
|
||||||
return [
|
return [
|
||||||
header for header in self.db.iterator(prefix=HEADER_PREFIX, include_key=False)
|
header for header in self.db.iterator(prefix=DB_PREFIXES.HEADER_PREFIX.value, include_key=False)
|
||||||
]
|
]
|
||||||
|
|
||||||
headers = await asyncio.get_event_loop().run_in_executor(self.executor, get_headers)
|
headers = await asyncio.get_event_loop().run_in_executor(self.executor, get_headers)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from bisect import bisect_right
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from lbry.testcase import CommandTestCase
|
from lbry.testcase import CommandTestCase
|
||||||
from lbry.wallet.transaction import Transaction, Output
|
from lbry.wallet.transaction import Transaction, Output
|
||||||
|
@ -43,35 +44,52 @@ class BaseResolveTestCase(CommandTestCase):
|
||||||
|
|
||||||
async def assertMatchClaim(self, claim_id):
|
async def assertMatchClaim(self, claim_id):
|
||||||
expected = json.loads(await self.blockchain._cli_cmnd('getclaimbyid', claim_id))
|
expected = json.loads(await self.blockchain._cli_cmnd('getclaimbyid', claim_id))
|
||||||
resolved, _ = await self.conductor.spv_node.server.bp.db.fs_getclaimbyid(claim_id)
|
claim = await self.conductor.spv_node.server.bp.db.fs_getclaimbyid(claim_id)
|
||||||
print(expected)
|
if not expected:
|
||||||
print(resolved)
|
self.assertIsNone(claim)
|
||||||
self.assertDictEqual({
|
return
|
||||||
'claim_id': expected['claimId'],
|
self.assertEqual(expected['claimId'], claim.claim_hash.hex())
|
||||||
'activation_height': expected['validAtHeight'],
|
self.assertEqual(expected['validAtHeight'], claim.activation_height)
|
||||||
'last_takeover_height': expected['lastTakeoverHeight'],
|
self.assertEqual(expected['lastTakeoverHeight'], claim.last_takeover_height)
|
||||||
'txid': expected['txId'],
|
self.assertEqual(expected['txId'], claim.tx_hash[::-1].hex())
|
||||||
'nout': expected['n'],
|
self.assertEqual(expected['n'], claim.position)
|
||||||
'amount': expected['amount'],
|
self.assertEqual(expected['amount'], claim.amount)
|
||||||
'effective_amount': expected['effectiveAmount']
|
self.assertEqual(expected['effectiveAmount'], claim.effective_amount)
|
||||||
}, {
|
return claim
|
||||||
'claim_id': resolved.claim_hash.hex(),
|
|
||||||
'activation_height': resolved.activation_height,
|
|
||||||
'last_takeover_height': resolved.last_takeover_height,
|
|
||||||
'txid': resolved.tx_hash[::-1].hex(),
|
|
||||||
'nout': resolved.position,
|
|
||||||
'amount': resolved.amount,
|
|
||||||
'effective_amount': resolved.effective_amount
|
|
||||||
})
|
|
||||||
return resolved
|
|
||||||
|
|
||||||
async def assertMatchClaimIsWinning(self, name, claim_id):
|
async def assertMatchClaimIsWinning(self, name, claim_id):
|
||||||
self.assertEqual(claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
self.assertEqual(claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
||||||
await self.assertMatchClaim(claim_id)
|
await self.assertMatchClaim(claim_id)
|
||||||
|
await self.assertMatchClaimsForName(name)
|
||||||
|
|
||||||
|
async def assertMatchClaimsForName(self, name):
|
||||||
|
expected = json.loads(await self.blockchain._cli_cmnd('getclaimsforname', name))
|
||||||
|
|
||||||
|
print(len(expected['claims']), 'from lbrycrd for ', name)
|
||||||
|
|
||||||
|
db = self.conductor.spv_node.server.bp.db
|
||||||
|
|
||||||
|
def check_supports(claim_id, lbrycrd_supports):
|
||||||
|
for i, (tx_num, position, amount) in enumerate(db.get_supports(bytes.fromhex(claim_id))):
|
||||||
|
support = lbrycrd_supports[i]
|
||||||
|
self.assertEqual(support['txId'], db.total_transactions[tx_num][::-1].hex())
|
||||||
|
self.assertEqual(support['n'], position)
|
||||||
|
self.assertEqual(support['height'], bisect_right(db.tx_counts, tx_num))
|
||||||
|
self.assertEqual(support['validAtHeight'], db.get_activation(tx_num, position, is_support=True))
|
||||||
|
|
||||||
|
# self.assertEqual(len(expected['claims']), len(db_claims.claims))
|
||||||
|
# self.assertEqual(expected['lastTakeoverHeight'], db_claims.lastTakeoverHeight)
|
||||||
|
|
||||||
|
for c in expected['claims']:
|
||||||
|
check_supports(c['claimId'], c['supports'])
|
||||||
|
claim_hash = bytes.fromhex(c['claimId'])
|
||||||
|
self.assertEqual(c['validAtHeight'], db.get_activation(
|
||||||
|
db.total_transactions.index(bytes.fromhex(c['txId'])[::-1]), c['n']
|
||||||
|
))
|
||||||
|
self.assertEqual(c['effectiveAmount'], db.get_effective_amount(claim_hash))
|
||||||
|
|
||||||
|
|
||||||
class ResolveCommand(BaseResolveTestCase):
|
class ResolveCommand(BaseResolveTestCase):
|
||||||
|
|
||||||
async def test_resolve_response(self):
|
async def test_resolve_response(self):
|
||||||
channel_id = self.get_claim_id(
|
channel_id = self.get_claim_id(
|
||||||
await self.channel_create('@abc', '0.01')
|
await self.channel_create('@abc', '0.01')
|
||||||
|
@ -170,6 +188,7 @@ class ResolveCommand(BaseResolveTestCase):
|
||||||
await self.stream_create('foo', '0.9', allow_duplicate_name=True))
|
await self.stream_create('foo', '0.9', allow_duplicate_name=True))
|
||||||
# plain winning claim
|
# plain winning claim
|
||||||
await self.assertResolvesToClaimId('foo', claim_id3)
|
await self.assertResolvesToClaimId('foo', claim_id3)
|
||||||
|
|
||||||
# amount order resolution
|
# amount order resolution
|
||||||
await self.assertResolvesToClaimId('foo$1', claim_id3)
|
await self.assertResolvesToClaimId('foo$1', claim_id3)
|
||||||
await self.assertResolvesToClaimId('foo$2', claim_id2)
|
await self.assertResolvesToClaimId('foo$2', claim_id2)
|
||||||
|
@ -275,9 +294,7 @@ class ResolveCommand(BaseResolveTestCase):
|
||||||
winner_id = self.get_claim_id(c)
|
winner_id = self.get_claim_id(c)
|
||||||
|
|
||||||
# winning_one = await self.check_lbrycrd_winning(one)
|
# winning_one = await self.check_lbrycrd_winning(one)
|
||||||
winning_two = await self.assertMatchWinningClaim(two)
|
await self.assertMatchClaimIsWinning(two, winner_id)
|
||||||
|
|
||||||
self.assertEqual(winner_id, winning_two.claim_hash.hex())
|
|
||||||
|
|
||||||
r1 = await self.resolve(f'lbry://{one}')
|
r1 = await self.resolve(f'lbry://{one}')
|
||||||
r2 = await self.resolve(f'lbry://{two}')
|
r2 = await self.resolve(f'lbry://{two}')
|
||||||
|
@ -385,24 +402,37 @@ class ResolveCommand(BaseResolveTestCase):
|
||||||
|
|
||||||
|
|
||||||
class ResolveClaimTakeovers(BaseResolveTestCase):
|
class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
async def test_activation_delay(self):
|
async def _test_activation_delay(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.1', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(320)
|
await self.generate(320)
|
||||||
# a claim of higher amount made now will have a takeover delay of 10
|
# a claim of higher amount made now will have a takeover delay of 10
|
||||||
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(9)
|
await self.generate(9)
|
||||||
# not yet
|
# not yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
# the new claim should have activated
|
# the new claim should have activated
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
return first_claim_id, second_claim_id
|
||||||
|
|
||||||
|
async def test_activation_delay(self):
|
||||||
|
await self._test_activation_delay()
|
||||||
|
|
||||||
|
async def test_activation_delay_then_abandon_then_reclaim(self):
|
||||||
|
name = 'derp'
|
||||||
|
first_claim_id, second_claim_id = await self._test_activation_delay()
|
||||||
|
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
|
||||||
|
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=second_claim_id)
|
||||||
|
await self.generate(1)
|
||||||
|
await self.assertNoClaimForName(name)
|
||||||
|
await self._test_activation_delay()
|
||||||
|
|
||||||
async def test_block_takeover_with_delay_1_support(self):
|
async def test_block_takeover_with_delay_1_support(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
|
@ -415,46 +445,46 @@ class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(8)
|
await self.generate(8)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# prevent the takeover by adding a support one block before the takeover happens
|
# prevent the takeover by adding a support one block before the takeover happens
|
||||||
await self.support_create(first_claim_id, bid='1.0')
|
await self.support_create(first_claim_id, bid='1.0')
|
||||||
# one more block until activation
|
# one more block until activation
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
|
||||||
async def test_block_takeover_with_delay_0_support(self):
|
async def test_block_takeover_with_delay_0_support(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(320)
|
await self.generate(320)
|
||||||
# a claim of higher amount made now will have a takeover delay of 10
|
# a claim of higher amount made now will have a takeover delay of 10
|
||||||
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(9)
|
await self.generate(9)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# prevent the takeover by adding a support on the same block the takeover would happen
|
# prevent the takeover by adding a support on the same block the takeover would happen
|
||||||
await self.support_create(first_claim_id, bid='1.0')
|
await self.support_create(first_claim_id, bid='1.0')
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
|
||||||
async def _test_almost_prevent_takeover(self, name: str, blocks: int = 9):
|
async def _test_almost_prevent_takeover(self, name: str, blocks: int = 9):
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(320)
|
await self.generate(320)
|
||||||
# a claim of higher amount made now will have a takeover delay of 10
|
# a claim of higher amount made now will have a takeover delay of 10
|
||||||
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(blocks)
|
await self.generate(blocks)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# prevent the takeover by adding a support on the same block the takeover would happen
|
# prevent the takeover by adding a support on the same block the takeover would happen
|
||||||
tx = await self.daemon.jsonrpc_support_create(first_claim_id, '1.0')
|
tx = await self.daemon.jsonrpc_support_create(first_claim_id, '1.0')
|
||||||
await self.ledger.wait(tx)
|
await self.ledger.wait(tx)
|
||||||
|
@ -465,7 +495,7 @@ class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
first_claim_id, second_claim_id, tx = await self._test_almost_prevent_takeover(name, 9)
|
first_claim_id, second_claim_id, tx = await self._test_almost_prevent_takeover(name, 9)
|
||||||
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
|
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
|
||||||
async def test_almost_prevent_takeover_remove_support_one_block_after_supported(self):
|
async def test_almost_prevent_takeover_remove_support_one_block_after_supported(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
|
@ -473,35 +503,35 @@ class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
|
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
|
||||||
async def test_abandon_before_takeover(self):
|
async def test_abandon_before_takeover(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(320)
|
await self.generate(320)
|
||||||
# a claim of higher amount made now will have a takeover delay of 10
|
# a claim of higher amount made now will have a takeover delay of 10
|
||||||
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(8)
|
await self.generate(8)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# abandon the winning claim
|
# abandon the winning claim
|
||||||
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
|
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
# the takeover and activation should happen a block earlier than they would have absent the abandon
|
# the takeover and activation should happen a block earlier than they would have absent the abandon
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
|
||||||
async def test_abandon_before_takeover_no_delay_update(self): # TODO: fix race condition line 506
|
async def test_abandon_before_takeover_no_delay_update(self): # TODO: fix race condition line 506
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(320)
|
await self.generate(320)
|
||||||
# block 527
|
# block 527
|
||||||
# a claim of higher amount made now will have a takeover delay of 10
|
# a claim of higher amount made now will have a takeover delay of 10
|
||||||
|
@ -510,19 +540,23 @@ class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
# sanity check
|
# sanity check
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
await self.assertMatchClaimsForName(name)
|
||||||
await self.generate(8)
|
await self.generate(8)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
await self.assertMatchClaimsForName(name)
|
||||||
# abandon the winning claim
|
# abandon the winning claim
|
||||||
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
|
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
|
||||||
await self.daemon.jsonrpc_stream_update(second_claim_id, '0.1')
|
await self.daemon.jsonrpc_stream_update(second_claim_id, '0.1')
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
|
|
||||||
# the takeover and activation should happen a block earlier than they would have absent the abandon
|
# the takeover and activation should happen a block earlier than they would have absent the abandon
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
await self.assertMatchClaimsForName(name)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
# await self.ledger.on_header.where(lambda e: e.height == 537)
|
# await self.ledger.on_header.where(lambda e: e.height == 537)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
await self.assertMatchClaimsForName(name)
|
||||||
|
|
||||||
async def test_abandon_controlling_support_before_pending_takeover(self):
|
async def test_abandon_controlling_support_before_pending_takeover(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
|
@ -533,54 +567,78 @@ class ResolveClaimTakeovers(BaseResolveTestCase):
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
||||||
await self.generate(321)
|
await self.generate(321)
|
||||||
|
|
||||||
second_claim_id = (await self.stream_create(name, '1.1', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.9', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
|
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
# takeover should not have happened yet
|
# takeover should not have happened yet
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(8)
|
await self.generate(8)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# abandon the support that causes the winning claim to have the highest staked
|
# abandon the support that causes the winning claim to have the highest staked
|
||||||
tx = await self.daemon.jsonrpc_txo_spend(type='support', txid=controlling_support_tx.id)
|
tx = await self.daemon.jsonrpc_txo_spend(type='support', txid=controlling_support_tx.id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
# await self.assertMatchClaim(second_claim_id)
|
||||||
|
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
|
||||||
|
await self.assertMatchClaim(first_claim_id)
|
||||||
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
|
||||||
async def test_remove_controlling_support(self):
|
async def test_remove_controlling_support(self):
|
||||||
name = 'derp'
|
name = 'derp'
|
||||||
# initially claim the name
|
# initially claim the name
|
||||||
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
|
first_claim_id = (await self.stream_create(name, '0.2'))['outputs'][0]['claim_id']
|
||||||
first_support_tx = await self.daemon.jsonrpc_support_create(first_claim_id, '0.9')
|
first_support_tx = await self.daemon.jsonrpc_support_create(first_claim_id, '0.9')
|
||||||
await self.ledger.wait(first_support_tx)
|
await self.ledger.wait(first_support_tx)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
|
||||||
await self.generate(321) # give the first claim long enough for a 10 block takeover delay
|
await self.generate(320) # give the first claim long enough for a 10 block takeover delay
|
||||||
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
|
||||||
# make a second claim which will take over the name
|
# make a second claim which will take over the name
|
||||||
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
second_claim_id = (await self.stream_create(name, '0.1', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
second_claim_support_tx = await self.daemon.jsonrpc_support_create(second_claim_id, '1.0')
|
|
||||||
await self.ledger.wait(second_claim_support_tx)
|
|
||||||
self.assertNotEqual(first_claim_id, second_claim_id)
|
self.assertNotEqual(first_claim_id, second_claim_id)
|
||||||
|
second_claim_support_tx = await self.daemon.jsonrpc_support_create(second_claim_id, '1.5')
|
||||||
|
await self.ledger.wait(second_claim_support_tx)
|
||||||
|
await self.generate(1) # neither the second claim or its support have activated yet
|
||||||
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
|
||||||
# the name resolves to the first claim
|
await self.generate(9) # claim activates, but is not yet winning
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(9)
|
|
||||||
# still resolves to the first claim
|
await self.generate(1) # support activates, takeover happens
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
await self.generate(1) # second claim takes over
|
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.daemon.jsonrpc_txo_spend(type='support', claim_id=second_claim_id, blocking=True)
|
||||||
await self.generate(33) # give the second claim long enough for a 1 block takeover delay
|
await self.generate(1) # support activates, takeover happens
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
# abandon the support that causes the winning claim to have the highest staked
|
|
||||||
await self.daemon.jsonrpc_txo_spend(type='support', txid=second_claim_support_tx.id)
|
async def test_claim_expiration(self):
|
||||||
|
name = 'derp'
|
||||||
|
# starts at height 206
|
||||||
|
vanishing_claim = (await self.stream_create('vanish', '0.1'))['outputs'][0]['claim_id']
|
||||||
|
|
||||||
|
await self.generate(493)
|
||||||
|
# in block 701 and 702
|
||||||
|
first_claim_id = (await self.stream_create(name, '0.3'))['outputs'][0]['claim_id']
|
||||||
|
await self.assertMatchClaimIsWinning('vanish', vanishing_claim)
|
||||||
|
await self.generate(100) # block 801, expiration fork happened
|
||||||
|
await self.assertNoClaimForName('vanish')
|
||||||
|
# second claim is in block 802
|
||||||
|
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
|
||||||
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
|
await self.generate(498)
|
||||||
|
await self.assertMatchClaimIsWinning(name, first_claim_id)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
await self.generate(1) # first claim takes over
|
await self.generate(100)
|
||||||
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
|
await self.assertMatchClaimIsWinning(name, second_claim_id)
|
||||||
|
await self.generate(1)
|
||||||
|
await self.assertNoClaimForName(name)
|
||||||
|
|
||||||
|
|
||||||
class ResolveAfterReorg(BaseResolveTestCase):
|
class ResolveAfterReorg(BaseResolveTestCase):
|
||||||
|
|
||||||
async def reorg(self, start):
|
async def reorg(self, start):
|
||||||
blocks = self.ledger.headers.height - start
|
blocks = self.ledger.headers.height - start
|
||||||
self.blockchain.block_expected = start - 1
|
self.blockchain.block_expected = start - 1
|
||||||
|
|
Loading…
Reference in a new issue