Skip to content

Commit dbcff55

Browse files
authored
refactor: cleanup full verification (#1218)
1 parent 4513ddf commit dbcff55

18 files changed

+68
-300
lines changed

hathor/builder/builder.py

-20
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,6 @@ def __init__(self) -> None:
182182

183183
self._enable_stratum_server: Optional[bool] = None
184184

185-
self._full_verification: Optional[bool] = None
186-
187185
self._soft_voided_tx_ids: Optional[set[bytes]] = None
188186

189187
self._execution_manager: ExecutionManager | None = None
@@ -239,9 +237,6 @@ def build(self) -> BuildArtifacts:
239237

240238
kwargs: dict[str, Any] = {}
241239

242-
if self._full_verification is not None:
243-
kwargs['full_verification'] = self._full_verification
244-
245240
if self._enable_event_queue is not None:
246241
kwargs['enable_event_queue'] = self._enable_event_queue
247242

@@ -778,21 +773,6 @@ def disable_sync_v2(self) -> 'Builder':
778773
self._sync_v2_support = SyncSupportLevel.DISABLED
779774
return self
780775

781-
def set_full_verification(self, full_verification: bool) -> 'Builder':
782-
self.check_if_can_modify()
783-
self._full_verification = full_verification
784-
return self
785-
786-
def enable_full_verification(self) -> 'Builder':
787-
self.check_if_can_modify()
788-
self._full_verification = True
789-
return self
790-
791-
def disable_full_verification(self) -> 'Builder':
792-
self.check_if_can_modify()
793-
self._full_verification = False
794-
return self
795-
796776
def enable_ipv6(self) -> 'Builder':
797777
self.check_if_can_modify()
798778
self._enable_ipv6 = True

hathor/builder/cli_builder.py

-3
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
234234
self.log.debug('enable utxo index')
235235
tx_storage.indexes.enable_utxo_index()
236236

237-
self.check_or_raise(not self._args.x_full_verification, '--x-full-verification is deprecated')
238-
239237
soft_voided_tx_ids = set(settings.SOFT_VOIDED_TX_IDS)
240238
consensus_algorithm = ConsensusAlgorithm(
241239
soft_voided_tx_ids,
@@ -331,7 +329,6 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
331329
wallet=self.wallet,
332330
checkpoints=settings.CHECKPOINTS,
333331
environment_info=get_environment_info(args=str(self._args), peer_id=str(peer.id)),
334-
full_verification=False,
335332
enable_event_queue=self._args.x_enable_event_queue or self._args.enable_event_queue,
336333
bit_signaling_service=bit_signaling_service,
337334
verification_service=verification_service,

hathor/cli/events_simulator/events_simulator.py

-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ def execute(args: Namespace, reactor: 'ReactorProtocol') -> None:
6161
simulator = Simulator(args.seed)
6262
simulator.start()
6363
builder = simulator.get_default_builder() \
64-
.disable_full_verification() \
6564
.enable_event_queue()
6665

6766
manager = simulator.create_peer(builder)

hathor/cli/run_node.py

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ def create_parser(cls) -> ArgumentParser:
124124
parser.add_argument('--cache-interval', type=int, help='Cache flush interval')
125125
parser.add_argument('--recursion-limit', type=int, help='Set python recursion limit')
126126
parser.add_argument('--allow-mining-without-peers', action='store_true', help='Allow mining without peers')
127-
parser.add_argument('--x-full-verification', action='store_true', help=SUPPRESS) # deprecated
128127
parser.add_argument('--procname-prefix', help='Add a prefix to the process name', default='')
129128
parser.add_argument('--allow-non-standard-script', action='store_true', help='Accept non-standard scripts on '
130129
'/push-tx API')

hathor/cli/run_node_args.py

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class RunNodeArgs(BaseModel, extra=Extra.allow):
5959
cache_interval: Optional[int]
6060
recursion_limit: Optional[int]
6161
allow_mining_without_peers: bool
62-
x_full_verification: bool
6362
procname_prefix: str
6463
allow_non_standard_script: bool
6564
max_output_script_size: Optional[int]

hathor/conf/settings.py

-4
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,6 @@ def GENESIS_TX2_TIMESTAMP(self) -> int:
359359
# Amount in which tx min weight reaches the middle point between the minimum and maximum weight
360360
MIN_TX_WEIGHT_K: int = 100
361361

362-
# When the node is being initialized (with a full verification) we don't verify
363-
# the difficulty of all blocks, we execute the validation every N blocks only
364-
VERIFY_WEIGHT_EVERY_N_BLOCKS: int = 1000
365-
366362
# Capabilities
367363
CAPABILITY_WHITELIST: str = 'whitelist'
368364
CAPABILITY_SYNC_VERSION: str = 'sync-version'

hathor/manager.py

+12-201
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import datetime
1615
import sys
1716
import time
1817
from cProfile import Profile
@@ -53,7 +52,6 @@
5352
from hathor.reward_lock import is_spent_reward_locked
5453
from hathor.stratum import StratumFactory
5554
from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, Transaction, TxVersion
56-
from hathor.transaction.exceptions import TxValidationError
5755
from hathor.transaction.storage.exceptions import TransactionDoesNotExist
5856
from hathor.transaction.storage.transaction_storage import TransactionStorage
5957
from hathor.transaction.storage.tx_allow_scope import TxAllowScope
@@ -115,7 +113,6 @@ def __init__(
115113
checkpoints: Optional[list[Checkpoint]] = None,
116114
rng: Optional[Random] = None,
117115
environment_info: Optional[EnvironmentInfo] = None,
118-
full_verification: bool = False,
119116
enable_event_queue: bool = False,
120117
poa_block_producer: PoaBlockProducer | None = None,
121118
# Websocket factory
@@ -223,10 +220,6 @@ def __init__(
223220
# Thread pool used to resolve pow when sending tokens
224221
self.pow_thread_pool = ThreadPool(minthreads=0, maxthreads=settings.MAX_POW_THREADS, name='Pow thread pool')
225222

226-
# Full verification execute all validations for transactions and blocks when initializing the node
227-
# Can be activated on the command line with --full-verification
228-
self._full_verification = full_verification
229-
230223
# List of whitelisted peers
231224
self.peers_whitelist: list[PeerId] = []
232225

@@ -272,33 +265,16 @@ def start(self) -> None:
272265
)
273266
sys.exit(-1)
274267

275-
# If it's a full verification, we save on the storage that we are starting it
276-
# this is required because if we stop the initilization in the middle, the metadata
277-
# saved on the storage is not reliable anymore, only if we finish it
278-
if self._full_verification:
279-
self.tx_storage.start_full_verification()
280-
else:
281-
# If it's a fast initialization and the last time a full initialization stopped in the middle
282-
# we can't allow the full node to continue, so we need to remove the storage and do a full sync
283-
# or execute an initialization with full verification
284-
if self.tx_storage.is_running_full_verification():
285-
self.log.error(
286-
'Error initializing node. The last time you started your node you did a full verification '
287-
'that was stopped in the middle. The storage is not reliable anymore and, because of that, '
288-
'you must initialize with a full verification again or remove your storage and do a full sync.'
289-
)
290-
sys.exit(-1)
291-
292-
# If self.tx_storage.is_running_manager() is True, the last time the node was running it had a sudden crash
293-
# because of that, we must run a full verification because some storage data might be wrong.
294-
# The metadata is the only piece of the storage that may be wrong, not the blocks and transactions.
295-
if self.tx_storage.is_running_manager():
296-
self.log.error(
297-
'Error initializing node. The last time you executed your full node it wasn\'t stopped correctly. '
298-
'The storage is not reliable anymore and, because of that, so you must run a full verification '
299-
'or remove your storage and do a full sync.'
300-
)
301-
sys.exit(-1)
268+
# If self.tx_storage.is_running_manager() is True, the last time the node was running it had a sudden crash
269+
# because of that, we must run a sync from scratch or from a snapshot.
270+
# The metadata is the only piece of the storage that may be wrong, not the blocks and transactions.
271+
if self.tx_storage.is_running_manager():
272+
self.log.error(
273+
'Error initializing node. The last time you executed your full node it wasn\'t stopped correctly. '
274+
'The storage is not reliable anymore and, because of that you must remove your storage and do a'
275+
'sync from scratch or from a snapshot.'
276+
)
277+
sys.exit(-1)
302278

303279
if self._enable_event_queue:
304280
self._event_manager.start(str(self.my_peer.id))
@@ -312,16 +288,7 @@ def start(self) -> None:
312288
self.tx_storage.disable_lock()
313289
# Open scope for initialization.
314290
self.tx_storage.set_allow_scope(TxAllowScope.VALID | TxAllowScope.PARTIAL | TxAllowScope.INVALID)
315-
# Initialize manager's components.
316-
if self._full_verification:
317-
self.tx_storage.reset_indexes()
318-
self._initialize_components_full_verification()
319-
# Before calling self._initialize_components_full_verification() I start 'full verification' mode and
320-
# after that I need to finish it. It's just to know if the full node has stopped a full initialization
321-
# in the middle.
322-
self.tx_storage.finish_full_verification()
323-
else:
324-
self._initialize_components_new()
291+
self._initialize_components()
325292
self.tx_storage.set_allow_scope(TxAllowScope.VALID)
326293
self.tx_storage.enable_lock()
327294

@@ -414,159 +381,7 @@ def stop_profiler(self, save_to: Optional[str] = None) -> None:
414381
if save_to:
415382
self.profiler.dump_stats(save_to)
416383

417-
def _initialize_components_full_verification(self) -> None:
418-
"""You are not supposed to run this method manually. You should run `doStart()` to initialize the
419-
manager.
420-
421-
This method runs through all transactions, verifying them and updating our wallet.
422-
"""
423-
assert not self._enable_event_queue, 'this method cannot be used if the events feature is enabled.'
424-
assert self._full_verification
425-
426-
self.log.info('initialize')
427-
if self.wallet:
428-
self.wallet._manually_initialize()
429-
t0 = time.time()
430-
t1 = t0
431-
cnt = 0
432-
cnt2 = 0
433-
t2 = t0
434-
h = 0
435-
436-
block_count = 0
437-
tx_count = 0
438-
439-
self.tx_storage.pre_init()
440-
assert self.tx_storage.indexes is not None
441-
442-
self._verify_soft_voided_txs()
443-
444-
# Checkpoints as {height: hash}
445-
checkpoint_heights = {}
446-
for cp in self.checkpoints:
447-
checkpoint_heights[cp.height] = cp.hash
448-
449-
# self.start_profiler()
450-
self.log.debug('reset all metadata')
451-
for tx in self.tx_storage.get_all_transactions():
452-
tx.reset_metadata()
453-
454-
self.log.debug('load blocks and transactions')
455-
for tx in self.tx_storage._topological_sort_dfs():
456-
tx_meta = tx.get_metadata()
457-
458-
t2 = time.time()
459-
dt = LogDuration(t2 - t1)
460-
dcnt = cnt - cnt2
461-
tx_rate = '?' if dt == 0 else dcnt / dt
462-
h = max(h, (tx.static_metadata.height if isinstance(tx, Block) else 0))
463-
if dt > 30:
464-
ts_date = datetime.datetime.fromtimestamp(self.tx_storage.latest_timestamp)
465-
if h == 0:
466-
self.log.debug('start loading transactions...')
467-
else:
468-
self.log.info('load transactions...', tx_rate=tx_rate, tx_new=dcnt, dt=dt,
469-
total=cnt, latest_ts=ts_date, height=h)
470-
t1 = t2
471-
cnt2 = cnt
472-
cnt += 1
473-
474-
# It's safe to skip block weight verification during initialization because
475-
# we trust the difficulty stored in metadata
476-
skip_block_weight_verification = True
477-
if block_count % self._settings.VERIFY_WEIGHT_EVERY_N_BLOCKS == 0:
478-
skip_block_weight_verification = False
479-
480-
try:
481-
# TODO: deal with invalid tx
482-
tx._update_parents_children_metadata()
483-
484-
if self.tx_storage.can_validate_full(tx):
485-
tx.update_initial_metadata()
486-
if tx.is_genesis:
487-
assert tx.validate_checkpoint(self.checkpoints)
488-
assert self.verification_service.validate_full(
489-
tx,
490-
skip_block_weight_verification=skip_block_weight_verification
491-
)
492-
self.tx_storage.add_to_indexes(tx)
493-
with self.tx_storage.allow_only_valid_context():
494-
self.consensus_algorithm.unsafe_update(tx)
495-
self.tx_storage.indexes.update(tx)
496-
if self.tx_storage.indexes.mempool_tips is not None:
497-
self.tx_storage.indexes.mempool_tips.update(tx) # XXX: move to indexes.update
498-
self.tx_storage.save_transaction(tx, only_metadata=True)
499-
else:
500-
assert self.verification_service.validate_basic(
501-
tx,
502-
skip_block_weight_verification=skip_block_weight_verification
503-
)
504-
self.tx_storage.save_transaction(tx, only_metadata=True)
505-
except (InvalidNewTransaction, TxValidationError):
506-
self.log.error('unexpected error when initializing', tx=tx, exc_info=True)
507-
raise
508-
509-
if tx.is_block:
510-
block_count += 1
511-
512-
# this works because blocks on the best chain are iterated from lower to higher height
513-
assert tx_meta.validation.is_at_least_basic()
514-
assert isinstance(tx, Block)
515-
blk_height = tx.get_height()
516-
if not tx_meta.voided_by and tx_meta.validation.is_fully_connected():
517-
# XXX: this might not be needed when making a full init because the consensus should already have
518-
self.tx_storage.indexes.height.add_reorg(blk_height, tx.hash, tx.timestamp)
519-
520-
# Check if it's a checkpoint block
521-
if blk_height in checkpoint_heights:
522-
if tx.hash == checkpoint_heights[blk_height]:
523-
del checkpoint_heights[blk_height]
524-
else:
525-
# If the hash is different from checkpoint hash, we stop the node
526-
self.log.error('Error initializing the node. Checkpoint validation error.')
527-
sys.exit()
528-
else:
529-
tx_count += 1
530-
531-
if time.time() - t2 > 1:
532-
dt = LogDuration(time.time() - t2)
533-
self.log.warn('tx took too long to load', tx=tx.hash_hex, dt=dt)
534-
535-
# we have to have a best_block by now
536-
# assert best_block is not None
537-
538-
self.tx_storage.indexes._manually_initialize(self.tx_storage)
539-
540-
self.log.debug('done loading transactions')
541-
542-
# Check if all checkpoints in database are ok
543-
my_best_height = self.tx_storage.get_height_best_block()
544-
if checkpoint_heights:
545-
# If I have checkpoints that were not validated I must check if they are all in a height I still don't have
546-
first = min(list(checkpoint_heights.keys()))
547-
if first <= my_best_height:
548-
# If the height of the first checkpoint not validated is lower than the height of the best block
549-
# Then it's missing this block
550-
self.log.error('Error initializing the node. Checkpoint validation error.')
551-
sys.exit()
552-
553-
best_height = self.tx_storage.get_height_best_block()
554-
if best_height != h:
555-
self.log.warn('best height doesn\'t match', best_height=best_height, max_height=h)
556-
557-
# self.stop_profiler(save_to='profiles/initializing.prof')
558-
self.state = self.NodeState.READY
559-
560-
total_load_time = LogDuration(t2 - t0)
561-
tx_rate = '?' if total_load_time == 0 else cnt / total_load_time
562-
563-
environment_info = self.environment_info.as_dict() if self.environment_info else {}
564-
565-
# Changing the field names in this log could impact log collectors that parse them
566-
self.log.info('ready', vertex_count=cnt, tx_rate=tx_rate, total_load_time=total_load_time, height=h,
567-
blocks=block_count, txs=tx_count, **environment_info)
568-
569-
def _initialize_components_new(self) -> None:
384+
def _initialize_components(self) -> None:
570385
"""You are not supposed to run this method manually. You should run `doStart()` to initialize the
571386
manager.
572387
@@ -593,10 +408,6 @@ def _initialize_components_new(self) -> None:
593408
started_at=started_at, last_started_at=last_started_at)
594409

595410
self._verify_soft_voided_txs()
596-
597-
# TODO: move support for full-verification here, currently we rely on the original _initialize_components
598-
# method for full-verification to work, if we implement it here we'll reduce a lot of duplicate and
599-
# complex code
600411
self.tx_storage.indexes._manually_initialize(self.tx_storage)
601412

602413
# Verify if all checkpoints that exist in the database are correct

hathor/simulator/simulator.py

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def get_default_builder(self) -> Builder:
8181
return Builder() \
8282
.set_peer(PrivatePeer.auto_generated()) \
8383
.set_soft_voided_tx_ids(set()) \
84-
.enable_full_verification() \
8584
.enable_sync_v2() \
8685
.use_memory() \
8786
.set_settings(self.settings)

0 commit comments

Comments
 (0)