Skip to content

Commit a5c6ab2

Browse files
glevcomsbrogli
authored andcommitted
feat(script): deprecate unnecessary opcodes
1 parent 6ef1f93 commit a5c6ab2

File tree

25 files changed

+301
-107
lines changed

25 files changed

+301
-107
lines changed

.yamllint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
extends: default
22

3+
ignore:
4+
- .venv/
5+
36
rules:
47
document-start: disable
58
line-length:

hathor/conf/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ def GENESIS_TX2_TIMESTAMP(self) -> int:
485485
# Used to enable fee-based tokens.
486486
ENABLE_FEE_BASED_TOKENS: FeatureSetting = FeatureSetting.DISABLED
487487

488+
# Used to enable opcodes V2.
489+
ENABLE_OPCODES_V2: FeatureSetting = FeatureSetting.DISABLED
490+
488491
# List of enabled blueprints.
489492
BLUEPRINTS: dict[bytes, str] = {}
490493

hathor/consensus/consensus.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
from hathor.consensus.transaction_consensus import TransactionConsensusAlgorithmFactory
2525
from hathor.execution_manager import non_critical_code
2626
from hathor.feature_activation.feature import Feature
27+
from hathor.nanocontracts.exception import NCInvalidSignature
2728
from hathor.nanocontracts.execution import NCBlockExecutor
2829
from hathor.profiler import get_cpu_profiler
2930
from hathor.pubsub import HathorEvents, PubSubManager
3031
from hathor.transaction import BaseTransaction, Block, Transaction
31-
from hathor.transaction.exceptions import RewardLocked
32+
from hathor.transaction.exceptions import InvalidInputData, RewardLocked, TooManySigOps
3233
from hathor.util import not_none
34+
from hathor.verification.verification_params import VerificationParams
3335

3436
if TYPE_CHECKING:
3537
from hathor.conf.settings import HathorSettings
@@ -431,6 +433,9 @@ def _feature_activation_rules(self, tx: Transaction, new_best_block: Block) -> b
431433
case Feature.COUNT_CHECKDATASIG_OP:
432434
if not self._checkdatasig_count_rule(tx):
433435
return False
436+
case Feature.OPCODES_V2:
437+
if not self._opcodes_v2_activation_rule(tx, new_best_block):
438+
return False
434439
case (
435440
Feature.INCREASE_MAX_MERKLE_PATH_LENGTH
436441
| Feature.NOP_FEATURE_1
@@ -491,8 +496,41 @@ def _checkdatasig_count_rule(self, tx: Transaction) -> bool:
491496
# a fail and the tx will be removed from the mempool.
492497
try:
493498
VertexVerifier._verify_sigops_output(settings=self._settings, vertex=tx, enable_checkdatasig_count=True)
494-
except Exception:
499+
except Exception as e:
500+
if not isinstance(e, TooManySigOps):
501+
self.log.exception('unexpected exception in mempool-reverification')
502+
return False
503+
return True
504+
505+
def _opcodes_v2_activation_rule(self, tx: Transaction, new_best_block: Block) -> bool:
506+
"""Check whether a tx became invalid because of the opcodes V2 feature."""
507+
from hathor.verification.nano_header_verifier import NanoHeaderVerifier
508+
from hathor.verification.transaction_verifier import TransactionVerifier
509+
510+
# We check all txs regardless of the feature state, because this rule
511+
# already prohibited mempool txs before the block feature activation.
512+
513+
params = VerificationParams.default_for_mempool(best_block=new_best_block)
514+
515+
# Any exception in the inputs verification will be considered
516+
# a fail and the tx will be removed from the mempool.
517+
try:
518+
TransactionVerifier._verify_inputs(self._settings, tx, params, skip_script=False)
519+
except Exception as e:
520+
if not isinstance(e, InvalidInputData):
521+
self.log.exception('unexpected exception in mempool-reverification')
495522
return False
523+
524+
# Any exception in the nc_signature verification will be considered
525+
# a fail and the tx will be removed from the mempool.
526+
if tx.is_nano_contract():
527+
try:
528+
NanoHeaderVerifier._verify_nc_signature(self._settings, tx, params)
529+
except Exception as e:
530+
if not isinstance(e, NCInvalidSignature):
531+
self.log.exception('unexpected exception in mempool-reverification')
532+
return False
533+
496534
return True
497535

498536

hathor/feature_activation/feature.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ class Feature(StrEnum):
3232
COUNT_CHECKDATASIG_OP = 'COUNT_CHECKDATASIG_OP'
3333
NANO_CONTRACTS = 'NANO_CONTRACTS'
3434
FEE_TOKENS = 'FEE_TOKENS'
35+
OPCODES_V2 = 'OPCODES_V2'

hathor/feature_activation/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from hathor.feature_activation.feature import Feature
2121
from hathor.feature_activation.model.feature_state import FeatureState
22+
from hathor.transaction.scripts.opcode import OpcodesVersion
2223

2324
if TYPE_CHECKING:
2425
from hathor.conf.settings import FeatureSetting, HathorSettings
@@ -33,6 +34,7 @@ class Features:
3334
count_checkdatasig_op: bool
3435
nanocontracts: bool
3536
fee_tokens: bool
37+
opcodes_version: OpcodesVersion
3638

3739
@staticmethod
3840
def from_vertex(*, settings: HathorSettings, feature_service: FeatureService, vertex: Vertex) -> Features:
@@ -43,17 +45,21 @@ def from_vertex(*, settings: HathorSettings, feature_service: FeatureService, ve
4345
Feature.COUNT_CHECKDATASIG_OP: FeatureSetting.FEATURE_ACTIVATION,
4446
Feature.NANO_CONTRACTS: settings.ENABLE_NANO_CONTRACTS,
4547
Feature.FEE_TOKENS: settings.ENABLE_FEE_BASED_TOKENS,
48+
Feature.OPCODES_V2: settings.ENABLE_OPCODES_V2,
4649
}
4750

4851
feature_is_active: dict[Feature, bool] = {
4952
feature: _is_feature_active(setting, feature_states.get(feature, FeatureState.DEFINED))
5053
for feature, setting in feature_settings.items()
5154
}
5255

56+
opcodes_version = OpcodesVersion.V2 if feature_is_active[Feature.OPCODES_V2] else OpcodesVersion.V1
57+
5358
return Features(
5459
count_checkdatasig_op=feature_is_active[Feature.COUNT_CHECKDATASIG_OP],
5560
nanocontracts=feature_is_active[Feature.NANO_CONTRACTS],
5661
fee_tokens=feature_is_active[Feature.FEE_TOKENS],
62+
opcodes_version=opcodes_version,
5763
)
5864

5965

hathor/nanocontracts/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from hathor.nanocontracts.exception import BlueprintSyntaxError, NCSerializationError
3333
from hathor.nanocontracts.faux_immutable import FauxImmutableMeta
3434
from hathor.serialization import SerializationError
35+
from hathor.transaction.scripts.opcode import OpcodesVersion
3536
from hathor.transaction.util import bytes_to_int, get_deposit_token_withdraw_amount, int_to_bytes
3637
from hathor.utils.typing import InnerTypeMixin
3738

@@ -162,7 +163,7 @@ def checksig(self, script: bytes) -> bool:
162163
from hathor.transaction.exceptions import ScriptError
163164
from hathor.transaction.scripts import ScriptExtras
164165
from hathor.transaction.scripts.execute import raw_script_eval
165-
extras = ScriptExtras(tx=self) # type: ignore[arg-type]
166+
extras = ScriptExtras(tx=self, version=OpcodesVersion.V2) # type: ignore[arg-type]
166167
try:
167168
raw_script_eval(input_data=self.script_input, output_script=script, extras=extras)
168169
except ScriptError:

hathor/p2p/sync_v2/transaction_streaming_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from hathor.p2p.sync_v2.streamers import StreamEnd
2929
from hathor.transaction import BaseTransaction, Transaction
3030
from hathor.transaction.exceptions import HathorError, TxValidationError
31+
from hathor.transaction.scripts.opcode import OpcodesVersion
3132
from hathor.types import VertexId
3233
from hathor.verification.verification_params import VerificationParams
3334

@@ -59,6 +60,7 @@ def __init__(self,
5960
count_checkdatasig_op=False,
6061
nanocontracts=False,
6162
fee_tokens=False,
63+
opcodes_version=OpcodesVersion.V1,
6264
)
6365
)
6466

hathor/transaction/resources/create_tx.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from hathor.transaction import Transaction, TxInput, TxOutput
2323
from hathor.transaction.scripts import create_output_script
2424
from hathor.util import api_catch_exceptions, json_dumpb, json_loadb
25+
from hathor.verification.verification_params import VerificationParams
2526

2627

2728
def from_raw_output(raw_output: dict, tokens: list[bytes]) -> TxOutput:
@@ -116,11 +117,12 @@ def _verify_unsigned_skip_pow(self, tx: Transaction) -> None:
116117
verifiers.tx.verify_output_token_indexes(tx)
117118
verifiers.vertex.verify_sigops_output(tx, enable_checkdatasig_count=True)
118119
verifiers.tx.verify_sigops_input(tx, enable_checkdatasig_count=True)
120+
best_block = self.manager.tx_storage.get_best_block()
121+
params = VerificationParams.default_for_mempool(best_block=best_block)
119122
# need to run verify_inputs first to check if all inputs exist
120-
verifiers.tx.verify_inputs(tx, skip_script=True)
123+
verifiers.tx.verify_inputs(tx, params, skip_script=True)
121124
verifiers.vertex.verify_parents(tx)
122125

123-
best_block = self.manager.tx_storage.get_best_block()
124126
block_storage = self.manager.get_nc_block_storage(best_block)
125127
verifiers.tx.verify_sum(self.manager._settings, tx, tx.get_complete_token_info(block_storage))
126128

hathor/transaction/scripts/execute.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
1517
import struct
1618
from dataclasses import dataclass
17-
from typing import NamedTuple, Optional, Union
19+
from typing import TYPE_CHECKING, NamedTuple, Optional, Union
1820

1921
from hathor.transaction import BaseTransaction, Transaction, TxInput
2022
from hathor.transaction.exceptions import DataIndexError, FinalStackInvalid, InvalidScriptError, OutOfData
2123

24+
if TYPE_CHECKING:
25+
from hathor.transaction.scripts.opcode import OpcodesVersion
26+
2227

2328
@dataclass(slots=True, frozen=True, kw_only=True)
2429
class ScriptExtras:
2530
tx: Transaction
31+
version: OpcodesVersion
2632

2733

2834
@dataclass(slots=True, frozen=True, kw_only=True)
@@ -72,7 +78,7 @@ def execute_eval(data: bytes, log: list[str], extras: ScriptExtras) -> None:
7278
continue
7379

7480
# this is an opcode manipulating the stack
75-
execute_op_code(Opcode(opcode), context)
81+
execute_op_code(Opcode(opcode), context, extras.version)
7682

7783
evaluate_final_stack(stack, log)
7884

@@ -94,7 +100,7 @@ def evaluate_final_stack(stack: Stack, log: list[str]) -> None:
94100
raise FinalStackInvalid('\n'.join(log))
95101

96102

97-
def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction) -> None:
103+
def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction, version: OpcodesVersion) -> None:
98104
"""Evaluates the output script and input data according to
99105
a very limited subset of Bitcoin's scripting language.
100106
@@ -112,7 +118,7 @@ def script_eval(tx: Transaction, txin: TxInput, spent_tx: BaseTransaction) -> No
112118
raw_script_eval(
113119
input_data=txin.data,
114120
output_script=spent_tx.outputs[txin.index].script,
115-
extras=UtxoScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx),
121+
extras=UtxoScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx, version=version),
116122
)
117123

118124

hathor/transaction/scripts/opcode.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import datetime
1616
import struct
1717
from enum import IntEnum
18+
from typing import Callable
1819

1920
from cryptography.exceptions import InvalidSignature
2021
from cryptography.hazmat.primitives import hashes
@@ -48,6 +49,11 @@
4849
from hathor.transaction.scripts.script_context import ScriptContext
4950

5051

52+
class OpcodesVersion(IntEnum):
53+
V1 = 1
54+
V2 = 2
55+
56+
5157
class Opcode(IntEnum):
5258
OP_0 = 0x50
5359
OP_1 = 0x51
@@ -626,7 +632,7 @@ def op_integer(opcode: int, stack: Stack) -> None:
626632
raise ScriptError(e) from e
627633

628634

629-
def execute_op_code(opcode: Opcode, context: ScriptContext) -> None:
635+
def execute_op_code(opcode: Opcode, context: ScriptContext, version: OpcodesVersion) -> None:
630636
"""
631637
Execute a function opcode.
632638
@@ -635,17 +641,27 @@ def execute_op_code(opcode: Opcode, context: ScriptContext) -> None:
635641
context: the script context to be manipulated.
636642
"""
637643
context.logs.append(f'Executing function opcode {opcode.name} ({hex(opcode.value)})')
638-
match opcode:
639-
case Opcode.OP_DUP: op_dup(context)
640-
case Opcode.OP_EQUAL: op_equal(context)
641-
case Opcode.OP_EQUALVERIFY: op_equalverify(context)
642-
case Opcode.OP_CHECKSIG: op_checksig(context)
643-
case Opcode.OP_HASH160: op_hash160(context)
644-
case Opcode.OP_GREATERTHAN_TIMESTAMP: op_greaterthan_timestamp(context)
645-
case Opcode.OP_CHECKMULTISIG: op_checkmultisig(context)
646-
case Opcode.OP_DATA_STREQUAL: op_data_strequal(context)
647-
case Opcode.OP_DATA_GREATERTHAN: op_data_greaterthan(context)
648-
case Opcode.OP_DATA_MATCH_VALUE: op_data_match_value(context)
649-
case Opcode.OP_CHECKDATASIG: op_checkdatasig(context)
650-
case Opcode.OP_FIND_P2PKH: op_find_p2pkh(context)
651-
case _: raise ScriptError(f'unknown opcode: {opcode}')
644+
opcode_fns: dict[Opcode, Callable[[ScriptContext], None]] = {
645+
Opcode.OP_DUP: op_dup,
646+
Opcode.OP_EQUAL: op_equal,
647+
Opcode.OP_EQUALVERIFY: op_equalverify,
648+
Opcode.OP_CHECKSIG: op_checksig,
649+
Opcode.OP_HASH160: op_hash160,
650+
Opcode.OP_GREATERTHAN_TIMESTAMP: op_greaterthan_timestamp,
651+
Opcode.OP_CHECKMULTISIG: op_checkmultisig,
652+
}
653+
654+
if version == OpcodesVersion.V1:
655+
opcode_fns.update({
656+
Opcode.OP_DATA_STREQUAL: op_data_strequal,
657+
Opcode.OP_DATA_GREATERTHAN: op_data_greaterthan,
658+
Opcode.OP_DATA_MATCH_VALUE: op_data_match_value,
659+
Opcode.OP_CHECKDATASIG: op_checkdatasig,
660+
Opcode.OP_FIND_P2PKH: op_find_p2pkh,
661+
})
662+
663+
opcode_fn = opcode_fns.get(opcode)
664+
if opcode_fn is None:
665+
raise ScriptError(f'unknown opcode: {opcode}')
666+
667+
opcode_fn(context)

0 commit comments

Comments
 (0)