17
17
from struct import pack
18
18
from typing import TYPE_CHECKING , Any , Iterator , NamedTuple , Optional
19
19
20
- from hathor import daa
21
20
from hathor .checkpoint import Checkpoint
22
21
from hathor .exception import InvalidNewTransaction
23
22
from hathor .profiler import get_cpu_profiler
24
23
from hathor .transaction import BaseTransaction , Block , TxInput , TxOutput , TxVersion
25
24
from hathor .transaction .base_transaction import TX_HASH_SIZE
26
- from hathor .transaction .exceptions import (
27
- ConflictingInputs ,
28
- DuplicatedParents ,
29
- IncorrectParents ,
30
- InexistentInput ,
31
- InputOutputMismatch ,
32
- InvalidInputData ,
33
- InvalidInputDataSize ,
34
- InvalidToken ,
35
- NoInputError ,
36
- RewardLocked ,
37
- ScriptError ,
38
- TimestampError ,
39
- TooManyInputs ,
40
- TooManySigOps ,
41
- WeightError ,
42
- )
43
- from hathor .transaction .util import VerboseCallback , get_deposit_amount , get_withdraw_amount , unpack , unpack_len
25
+ from hathor .transaction .exceptions import InvalidToken
26
+ from hathor .transaction .util import VerboseCallback , unpack , unpack_len
44
27
from hathor .types import TokenUid , VertexId
45
28
from hathor .util import not_none
46
29
@@ -296,78 +279,6 @@ def verify_checkpoint(self, checkpoints: list[Checkpoint]) -> None:
296
279
raise InvalidNewTransaction (f'Invalid new transaction { self .hash_hex } : expected to reach a checkpoint but '
297
280
'none of its children is checkpoint-valid' )
298
281
299
- def verify_parents_basic (self ) -> None :
300
- """Verify number and non-duplicity of parents."""
301
- assert self .storage is not None
302
-
303
- # check if parents are duplicated
304
- parents_set = set (self .parents )
305
- if len (self .parents ) > len (parents_set ):
306
- raise DuplicatedParents ('Tx has duplicated parents: {}' , [tx_hash .hex () for tx_hash in self .parents ])
307
-
308
- if len (self .parents ) != 2 :
309
- raise IncorrectParents (f'wrong number of parents (tx type): { len (self .parents )} , expecting 2' )
310
-
311
- def verify_weight (self ) -> None :
312
- """Validate minimum tx difficulty."""
313
- min_tx_weight = daa .minimum_tx_weight (self )
314
- max_tx_weight = min_tx_weight + self ._settings .MAX_TX_WEIGHT_DIFF
315
- if self .weight < min_tx_weight - self ._settings .WEIGHT_TOL :
316
- raise WeightError (f'Invalid new tx { self .hash_hex } : weight ({ self .weight } ) is '
317
- f'smaller than the minimum weight ({ min_tx_weight } )' )
318
- elif min_tx_weight > self ._settings .MAX_TX_WEIGHT_DIFF_ACTIVATION and self .weight > max_tx_weight :
319
- raise WeightError (f'Invalid new tx { self .hash_hex } : weight ({ self .weight } ) is '
320
- f'greater than the maximum allowed ({ max_tx_weight } )' )
321
-
322
- def verify_unsigned_skip_pow (self ) -> None :
323
- """ Same as .verify but skipping pow and signature verification."""
324
- self .verify_number_of_inputs ()
325
- self .verify_number_of_outputs ()
326
- self .verify_outputs ()
327
- self .verify_sigops_output ()
328
- self .verify_sigops_input ()
329
- self .verify_inputs (skip_script = True ) # need to run verify_inputs first to check if all inputs exist
330
- self .verify_parents ()
331
- self .verify_sum ()
332
-
333
- def verify_without_storage (self ) -> None :
334
- """ Run all verifications that do not need a storage.
335
- """
336
- self .verify_pow ()
337
- self .verify_number_of_inputs ()
338
- self .verify_outputs ()
339
- self .verify_sigops_output ()
340
-
341
- def verify_number_of_inputs (self ) -> None :
342
- """Verify number of inputs is in a valid range"""
343
- if len (self .inputs ) > self ._settings .MAX_NUM_INPUTS :
344
- raise TooManyInputs ('Maximum number of inputs exceeded' )
345
-
346
- if len (self .inputs ) == 0 :
347
- if not self .is_genesis :
348
- raise NoInputError ('Transaction must have at least one input' )
349
-
350
- def verify_sigops_input (self ) -> None :
351
- """ Count sig operations on all inputs and verify that the total sum is below the limit
352
- """
353
- from hathor .transaction .scripts import get_sigops_count
354
- from hathor .transaction .storage .exceptions import TransactionDoesNotExist
355
- n_txops = 0
356
- for tx_input in self .inputs :
357
- try :
358
- spent_tx = self .get_spent_tx (tx_input )
359
- except TransactionDoesNotExist :
360
- raise InexistentInput ('Input tx does not exist: {}' .format (tx_input .tx_id .hex ()))
361
- assert spent_tx .hash is not None
362
- if tx_input .index >= len (spent_tx .outputs ):
363
- raise InexistentInput ('Output spent by this input does not exist: {} index {}' .format (
364
- tx_input .tx_id .hex (), tx_input .index ))
365
- n_txops += get_sigops_count (tx_input .data , spent_tx .outputs [tx_input .index ].script )
366
-
367
- if n_txops > self ._settings .MAX_TX_SIGOPS_INPUT :
368
- raise TooManySigOps (
369
- 'TX[{}]: Max number of sigops for inputs exceeded ({})' .format (self .hash_hex , n_txops ))
370
-
371
282
def verify_outputs (self ) -> None :
372
283
"""Verify outputs reference an existing token uid in the tokens list
373
284
@@ -406,92 +317,6 @@ def get_token_info_from_inputs(self) -> dict[TokenUid, TokenInfo]:
406
317
407
318
return token_dict
408
319
409
- def update_token_info_from_outputs (self , token_dict : dict [TokenUid , TokenInfo ]) -> None :
410
- """Iterate over the outputs and add values to token info dict. Updates the dict in-place.
411
-
412
- Also, checks if no token has authorities on the outputs not present on the inputs
413
-
414
- :raises InvalidToken: when there's an error in token operations
415
- """
416
- # iterate over outputs and add values to token_dict
417
- for index , tx_output in enumerate (self .outputs ):
418
- token_uid = self .get_token_uid (tx_output .get_token_index ())
419
- token_info = token_dict .get (token_uid )
420
- if token_info is None :
421
- raise InvalidToken ('no inputs for token {}' .format (token_uid .hex ()))
422
- else :
423
- # for authority outputs, make sure the same capability (mint/melt) was present in the inputs
424
- if tx_output .can_mint_token () and not token_info .can_mint :
425
- raise InvalidToken ('output has mint authority, but no input has it: {}' .format (
426
- tx_output .to_human_readable ()))
427
- if tx_output .can_melt_token () and not token_info .can_melt :
428
- raise InvalidToken ('output has melt authority, but no input has it: {}' .format (
429
- tx_output .to_human_readable ()))
430
-
431
- if tx_output .is_token_authority ():
432
- # make sure we only have authorities that we know of
433
- if tx_output .value > TxOutput .ALL_AUTHORITIES :
434
- raise InvalidToken ('Invalid authorities in output (0b{0:b})' .format (tx_output .value ))
435
- else :
436
- # for regular outputs, just subtract from the total amount
437
- sum_tokens = token_info .amount + tx_output .value
438
- token_dict [token_uid ] = TokenInfo (sum_tokens , token_info .can_mint , token_info .can_melt )
439
-
440
- def check_authorities_and_deposit (self , token_dict : dict [TokenUid , TokenInfo ]) -> None :
441
- """Verify that the sum of outputs is equal of the sum of inputs, for each token. If sum of inputs
442
- and outputs is not 0, make sure inputs have mint/melt authority.
443
-
444
- token_dict sums up all tokens present in the tx and their properties (amount, can_mint, can_melt)
445
- amount = outputs - inputs, thus:
446
- - amount < 0 when melting
447
- - amount > 0 when minting
448
-
449
- :raises InputOutputMismatch: if sum of inputs is not equal to outputs and there's no mint/melt
450
- """
451
- withdraw = 0
452
- deposit = 0
453
- for token_uid , token_info in token_dict .items ():
454
- if token_uid == self ._settings .HATHOR_TOKEN_UID :
455
- continue
456
-
457
- if token_info .amount == 0 :
458
- # that's the usual behavior, nothing to do
459
- pass
460
- elif token_info .amount < 0 :
461
- # tokens have been melted
462
- if not token_info .can_melt :
463
- raise InputOutputMismatch ('{} {} tokens melted, but there is no melt authority input' .format (
464
- token_info .amount , token_uid .hex ()))
465
- withdraw += get_withdraw_amount (token_info .amount )
466
- else :
467
- # tokens have been minted
468
- if not token_info .can_mint :
469
- raise InputOutputMismatch ('{} {} tokens minted, but there is no mint authority input' .format (
470
- (- 1 ) * token_info .amount , token_uid .hex ()))
471
- deposit += get_deposit_amount (token_info .amount )
472
-
473
- # check whether the deposit/withdraw amount is correct
474
- htr_expected_amount = withdraw - deposit
475
- htr_info = token_dict [self ._settings .HATHOR_TOKEN_UID ]
476
- if htr_info .amount != htr_expected_amount :
477
- raise InputOutputMismatch ('HTR balance is different than expected. (amount={}, expected={})' .format (
478
- htr_info .amount ,
479
- htr_expected_amount ,
480
- ))
481
-
482
- def verify_sum (self ) -> None :
483
- """Verify that the sum of outputs is equal of the sum of inputs, for each token.
484
-
485
- If there are authority UTXOs involved, tokens can be minted or melted, so the above rule may
486
- not be respected.
487
-
488
- :raises InvalidToken: when there's an error in token operations
489
- :raises InputOutputMismatch: if sum of inputs is not equal to outputs and there's no mint/melt
490
- """
491
- token_dict = self .get_token_info_from_inputs ()
492
- self .update_token_info_from_outputs (token_dict )
493
- self .check_authorities_and_deposit (token_dict )
494
-
495
320
def iter_spent_rewards (self ) -> Iterator [Block ]:
496
321
"""Iterate over all the rewards being spent, assumes tx has been verified."""
497
322
for input_tx in self .inputs :
@@ -500,51 +325,6 @@ def iter_spent_rewards(self) -> Iterator[Block]:
500
325
assert isinstance (spent_tx , Block )
501
326
yield spent_tx
502
327
503
- def verify_inputs (self , * , skip_script : bool = False ) -> None :
504
- """Verify inputs signatures and ownership and all inputs actually exist"""
505
- from hathor .transaction .storage .exceptions import TransactionDoesNotExist
506
-
507
- spent_outputs : set [tuple [VertexId , int ]] = set ()
508
- for input_tx in self .inputs :
509
- if len (input_tx .data ) > self ._settings .MAX_INPUT_DATA_SIZE :
510
- raise InvalidInputDataSize ('size: {} and max-size: {}' .format (
511
- len (input_tx .data ), self ._settings .MAX_INPUT_DATA_SIZE
512
- ))
513
-
514
- try :
515
- spent_tx = self .get_spent_tx (input_tx )
516
- assert spent_tx .hash is not None
517
- if input_tx .index >= len (spent_tx .outputs ):
518
- raise InexistentInput ('Output spent by this input does not exist: {} index {}' .format (
519
- input_tx .tx_id .hex (), input_tx .index ))
520
- except TransactionDoesNotExist :
521
- raise InexistentInput ('Input tx does not exist: {}' .format (input_tx .tx_id .hex ()))
522
-
523
- if self .timestamp <= spent_tx .timestamp :
524
- raise TimestampError ('tx={} timestamp={}, spent_tx={} timestamp={}' .format (
525
- self .hash .hex () if self .hash else None ,
526
- self .timestamp ,
527
- spent_tx .hash .hex (),
528
- spent_tx .timestamp ,
529
- ))
530
-
531
- if not skip_script :
532
- self .verify_script (input_tx , spent_tx )
533
-
534
- # check if any other input in this tx is spending the same output
535
- key = (input_tx .tx_id , input_tx .index )
536
- if key in spent_outputs :
537
- raise ConflictingInputs ('tx {} inputs spend the same output: {} index {}' .format (
538
- self .hash_hex , input_tx .tx_id .hex (), input_tx .index ))
539
- spent_outputs .add (key )
540
-
541
- def verify_reward_locked (self ) -> None :
542
- """Will raise `RewardLocked` if any reward is spent before the best block height is enough, considering only
543
- the block rewards spent by this tx itself, and not the inherited `min_height`."""
544
- info = self .get_spent_reward_locked_info ()
545
- if info is not None :
546
- raise RewardLocked (f'Reward { info .block_hash .hex ()} still needs { info .blocks_needed } to be unlocked.' )
547
-
548
328
def is_spent_reward_locked (self ) -> bool :
549
329
""" Check whether any spent reward is currently locked, considering only the block rewards spent by this tx
550
330
itself, and not the inherited `min_height`"""
@@ -578,17 +358,6 @@ def _spent_reward_needed_height(self, block: Block) -> int:
578
358
needed_height = self ._settings .REWARD_SPEND_MIN_BLOCKS - spend_blocks
579
359
return max (needed_height , 0 )
580
360
581
- def verify_script (self , input_tx : TxInput , spent_tx : BaseTransaction ) -> None :
582
- """
583
- :type input_tx: TxInput
584
- :type spent_tx: Transaction
585
- """
586
- from hathor .transaction .scripts import script_eval
587
- try :
588
- script_eval (self , input_tx , spent_tx )
589
- except ScriptError as e :
590
- raise InvalidInputData (e ) from e
591
-
592
361
def is_double_spending (self ) -> bool :
593
362
""" Iterate through inputs to check if they were already spent
594
363
Used to prevent users from sending double spending transactions to the network
0 commit comments