Skip to content

Commit 783f397

Browse files
committed
review changes
1 parent 5bfe276 commit 783f397

File tree

9 files changed

+59
-40
lines changed

9 files changed

+59
-40
lines changed

hathor/transaction/scripts/execute.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import struct
16+
from dataclasses import dataclass
1617
from typing import TYPE_CHECKING, NamedTuple, Optional, Union
1718

1819
from hathor.conf.get_settings import get_global_settings
@@ -23,11 +24,21 @@
2324
from hathor.transaction.scripts.script_context import ScriptContext
2425

2526

26-
class ScriptExtras(NamedTuple):
27+
@dataclass(slots=True, frozen=True, kw_only=True)
28+
class ScriptExtras:
29+
"""
30+
A simple container for auxiliary data that may be used during execution of scripts.
31+
"""
2732
tx: Transaction
28-
txin: TxInput
29-
spent_tx: BaseTransaction
3033
input_index: int
34+
spent_tx: BaseTransaction
35+
36+
@property
37+
def txin(self) -> TxInput:
38+
return self.tx.inputs[self.input_index]
39+
40+
def __post_init__(self) -> None:
41+
assert self.txin.tx_id == self.spent_tx.hash
3142

3243

3344
# XXX: Because the Stack is a heterogeneous list of bytes and int, and some OPs only work for when the stack has some
@@ -96,7 +107,7 @@ def evaluate_final_stack(stack: Stack, log: list[str]) -> None:
96107
raise FinalStackInvalid('\n'.join(log))
97108

98109

99-
def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction, *, input_index: int) -> 'ScriptContext':
110+
def script_eval(tx: Transaction, spent_tx: BaseTransaction, *, input_index: int) -> 'ScriptContext':
100111
"""Evaluates the output script and input data according to
101112
a very limited subset of Bitcoin's scripting language.
102113
@@ -111,10 +122,10 @@ def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction, *, in
111122
112123
:raises ScriptError: if script verification fails
113124
"""
114-
input_data = txin.data
115-
output_script = spent_tx.outputs[txin.index].script
125+
extras = ScriptExtras(tx=tx, input_index=input_index, spent_tx=spent_tx)
126+
input_data = extras.txin.data
127+
output_script = spent_tx.outputs[extras.txin.index].script
116128
log: list[str] = []
117-
extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, input_index=input_index)
118129

119130
from hathor.transaction.scripts import MultiSig
120131
if MultiSig.re_match.search(output_script):
@@ -123,7 +134,7 @@ def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction, *, in
123134
# we can't use input_data + output_script because it will end with an invalid stack
124135
# i.e. the signatures will still be on the stack after ouput_script is executed
125136
redeem_script_pos = MultiSig.get_multisig_redeem_script_pos(input_data)
126-
full_data = txin.data[redeem_script_pos:] + output_script
137+
full_data = extras.txin.data[redeem_script_pos:] + output_script
127138
execute_eval(full_data, log, extras)
128139

129140
# Second, we need to validate that the signatures on the input_data solves the redeem_script

hathor/transaction/scripts/opcode.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import struct
1717
from enum import IntEnum
1818

19+
import pydantic
1920
from cryptography.exceptions import InvalidSignature
2021
from cryptography.hazmat.primitives import hashes
2122
from cryptography.hazmat.primitives.asymmetric import ec
@@ -643,7 +644,7 @@ def op_sighash_bitmask(context: ScriptContext) -> None:
643644
inputs=bytes_to_int(inputs),
644645
outputs=bytes_to_int(outputs)
645646
)
646-
except Exception as e:
647+
except pydantic.ValidationError as e:
647648
raise CustomSighashModelInvalid('Could not construct sighash bitmask.') from e
648649

649650
if context.extras.input_index not in sighash.get_input_indexes():
@@ -669,7 +670,7 @@ def op_max_inputs_outputs(context: ScriptContext) -> None:
669670
max_inputs=bytes_to_int(max_inputs),
670671
max_outputs=bytes_to_int(max_outputs)
671672
)
672-
except Exception as e:
673+
except pydantic.ValidationError as e:
673674
raise InputsOutputsLimitModelInvalid("Could not construct inputs and outputs limits.") from e
674675

675676
tx_inputs_len = len(context.extras.tx.inputs)

hathor/transaction/token_creation_tx.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ def get_sighash_all(self) -> bytes:
133133
:return: Serialization of the inputs, outputs and tokens
134134
:rtype: bytes
135135
"""
136-
if self._sighash_cache:
137-
return self._sighash_cache
136+
if self._sighash_all_cache:
137+
return self._sighash_all_cache
138138

139139
struct_bytes = pack(
140140
_SIGHASH_ALL_FORMAT_STRING,
@@ -155,7 +155,7 @@ def get_sighash_all(self) -> bytes:
155155
struct_bytes += b''.join(tx_outputs)
156156

157157
struct_bytes += self.serialize_token_info()
158-
self._sighash_cache = struct_bytes
158+
self._sighash_all_cache = struct_bytes
159159

160160
return struct_bytes
161161

hathor/transaction/transaction.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(self,
7575
super().__init__(nonce=nonce, timestamp=timestamp, signal_bits=signal_bits, version=version, weight=weight,
7676
inputs=inputs or [], outputs=outputs or [], parents=parents or [], hash=hash, storage=storage)
7777
self.tokens = tokens or []
78-
self._sighash_cache: Optional[bytes] = None
78+
self._sighash_all_cache: Optional[bytes] = None
7979
self._sighash_data_cache: Optional[bytes] = None
8080

8181
@property
@@ -215,11 +215,11 @@ def get_sighash_all(self) -> bytes:
215215
# This method does not depend on the input itself, however we call it for each one to sign it.
216216
# For transactions that have many inputs there is a significant decrease on the verify time
217217
# when using this cache, so we call this method only once.
218-
if self._sighash_cache:
219-
return self._sighash_cache
218+
if self._sighash_all_cache:
219+
return self._sighash_all_cache
220220

221221
sighash = self._get_sighash(inputs=self.inputs, outputs=self.outputs)
222-
self._sighash_cache = sighash
222+
self._sighash_all_cache = sighash
223223

224224
return sighash
225225

hathor/verification/transaction_verifier.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,7 @@ def verify_script(
156156
"""
157157
from hathor.transaction.scripts import script_eval
158158
try:
159-
input_tx = tx.inputs[input_index]
160-
return script_eval(tx, input_tx, spent_tx, input_index=input_index)
159+
return script_eval(tx, spent_tx, input_index=input_index)
161160
except ScriptError as e:
162161
raise InvalidInputData(e) from e
163162

tests/tx/test_multisig.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def test_spend_multisig(self):
134134
expected_dict = {'type': 'MultiSig', 'address': self.multisig_address_b58, 'timelock': None}
135135
self.assertEqual(cls_script.to_human_readable(), expected_dict)
136136

137-
script_eval(tx, tx_input, tx1, input_index=0)
137+
script_eval(tx, tx1, input_index=0)
138138

139139
# Script error
140140
with self.assertRaises(ScriptError):

tests/tx/test_nano_contracts.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ def test_match_values(self):
3636
input_data = NanoContractMatchValues.create_input_data(
3737
base64.b64decode(oracle_data), base64.b64decode(oracle_signature), base64.b64decode(pubkey))
3838
txin = TxInput(b'aa', 0, input_data)
39-
spent_tx = Transaction(outputs=[TxOutput(20, script)])
40-
tx = Transaction(outputs=[TxOutput(20, P2PKH.create_output_script(address))])
41-
script_eval(tx, txin, spent_tx, input_index=0)
39+
spent_tx = Transaction(hash=b'aa', outputs=[TxOutput(20, script)])
40+
tx = Transaction(inputs=[txin], outputs=[TxOutput(20, P2PKH.create_output_script(address))])
41+
script_eval(tx, spent_tx, input_index=0)

tests/tx/test_scripts.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def test_checksig(self):
253253
signature = self.genesis_private_key.sign(hashed_data, ec.ECDSA(hashes.SHA256()))
254254
pubkey_bytes = get_public_key_bytes_compressed(self.genesis_public_key)
255255

256-
extras = ScriptExtras(tx=tx, txin=Mock(), spent_tx=Mock(), input_index=0)
256+
extras = ScriptExtras(tx=tx, spent_tx=block, input_index=0)
257257

258258
# wrong signature puts False (0) on stack
259259
stack = [b'aaaaaaaaa', pubkey_bytes]
@@ -278,7 +278,7 @@ def test_checksig_cache(self):
278278
signature = self.genesis_private_key.sign(hashed_data, ec.ECDSA(hashes.SHA256()))
279279
pubkey_bytes = get_public_key_bytes_compressed(self.genesis_public_key)
280280

281-
extras = ScriptExtras(tx=tx, txin=Mock(), spent_tx=Mock(), input_index=0)
281+
extras = ScriptExtras(tx=tx, spent_tx=block, input_index=0)
282282

283283
stack = [signature, pubkey_bytes]
284284
self.assertIsNone(tx._sighash_data_cache)
@@ -508,34 +508,40 @@ def test_find_p2pkh(self):
508508
out_genesis = P2PKH.create_output_script(genesis_address)
509509

510510
from hathor.transaction import Transaction, TxInput, TxOutput
511-
spent_tx = Transaction(outputs=[TxOutput(1, b'nano_contract_code')])
512-
txin = TxInput(b'dont_care', 0, b'data')
511+
spent_tx = Transaction(hash=b'some_hash', outputs=[TxOutput(1, b'nano_contract_code')])
512+
txin = TxInput(b'some_hash', 0, b'data')
513513

514514
# try with just 1 output
515515
stack = [genesis_address]
516-
tx = Transaction(outputs=[TxOutput(1, out_genesis)])
517-
extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, input_index=0)
516+
tx = Transaction(inputs=[txin], outputs=[TxOutput(1, out_genesis)])
517+
extras = ScriptExtras(tx=tx, spent_tx=spent_tx, input_index=0)
518518
op_find_p2pkh(ScriptContext(stack=stack, logs=[], extras=extras, settings=Mock()))
519519
self.assertEqual(stack.pop(), 1)
520520

521521
# several outputs and correct output among them
522522
stack = [genesis_address]
523-
tx = Transaction(outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out_genesis), TxOutput(1, out3)])
524-
extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, input_index=0)
523+
tx = Transaction(
524+
inputs=[txin],
525+
outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out_genesis), TxOutput(1, out3)]
526+
)
527+
extras = ScriptExtras(tx=tx, spent_tx=spent_tx, input_index=0)
525528
op_find_p2pkh(ScriptContext(stack=stack, logs=[], extras=extras, settings=Mock()))
526529
self.assertEqual(stack.pop(), 1)
527530

528531
# several outputs without correct amount output
529532
stack = [genesis_address]
530-
tx = Transaction(outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(2, out_genesis), TxOutput(1, out3)])
531-
extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, input_index=0)
533+
tx = Transaction(
534+
inputs=[txin],
535+
outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(2, out_genesis), TxOutput(1, out3)]
536+
)
537+
extras = ScriptExtras(tx=tx, spent_tx=spent_tx, input_index=0)
532538
with self.assertRaises(VerifyFailed):
533539
op_find_p2pkh(ScriptContext(stack=stack, logs=[], extras=extras, settings=Mock()))
534540

535541
# several outputs without correct address output
536542
stack = [genesis_address]
537-
tx = Transaction(outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out3)])
538-
extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, input_index=0)
543+
tx = Transaction(inputs=[txin], outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out3)])
544+
extras = ScriptExtras(tx=tx, spent_tx=spent_tx, input_index=0)
539545
with self.assertRaises(VerifyFailed):
540546
op_find_p2pkh(ScriptContext(stack=stack, logs=[], extras=extras, settings=Mock()))
541547

@@ -545,11 +551,13 @@ def test_greaterthan_timestamp(self):
545551

546552
timestamp = 1234567
547553

548-
from hathor.transaction import Transaction
549-
tx = Transaction()
554+
from hathor.transaction import Transaction, TxInput
555+
spent_tx = Transaction(hash=b'some_hash')
556+
tx_input = TxInput(tx_id=b'some_hash', index=0, data=b'')
557+
tx = Transaction(inputs=[tx_input])
550558

551559
stack = [struct.pack('!I', timestamp)]
552-
extras = ScriptExtras(tx=tx, txin=Mock(), spent_tx=Mock(), input_index=0)
560+
extras = ScriptExtras(tx=tx, spent_tx=spent_tx, input_index=0)
553561

554562
with self.assertRaises(TimeLocked):
555563
tx.timestamp = timestamp - 1
@@ -575,7 +583,7 @@ def test_checkmultisig(self):
575583
tx = Transaction(inputs=[txin], outputs=[txout])
576584

577585
data_to_sign = tx.get_sighash_all()
578-
extras = ScriptExtras(tx=tx, txin=Mock(), spent_tx=Mock(), input_index=0)
586+
extras = ScriptExtras(tx=tx, spent_tx=block, input_index=0)
579587

580588
wallet = HDWallet()
581589
wallet._manually_initialize()

tests/tx/test_tx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,7 @@ def test_wallet_index(self):
10441044
self.assertEqual(len(self.tx_storage.indexes.addresses.get_from_address(output3_address_b58)), 1)
10451045
self.assertEqual(len(self.tx_storage.indexes.addresses.get_from_address(new_address_b58)), 1)
10461046

1047-
def test_sighash_cache(self):
1047+
def test_sighash_all_cache(self):
10481048
from unittest import mock
10491049

10501050
address = get_address_from_public_key(self.genesis_public_key)

0 commit comments

Comments
 (0)