Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions hathor/consensus/consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ def _feature_activation_rules(self, tx: Transaction, new_best_block: Block) -> b
case Feature.FEE_TOKENS:
if not self._fee_tokens_activation_rule(tx, is_active):
return False
case Feature.TRANSFER_HEADER:
if not self._transfer_headers_activation_rule(tx, is_active):
return False
case Feature.COUNT_CHECKDATASIG_OP:
if not self._checkdatasig_count_rule(tx):
return False
Expand Down Expand Up @@ -526,6 +529,18 @@ def _checkdatasig_count_rule(self, tx: Transaction) -> bool:
return False
return True

def _transfer_headers_activation_rule(self, tx: Transaction, is_active: bool) -> bool:
"""
Check whether a tx became invalid because the reorg changed the transfer-headers feature activation state.
"""
if is_active:
return True

if tx.has_transfer_header():
return False

return True

def _opcodes_v2_activation_rule(self, tx: Transaction) -> bool:
"""Check whether a tx became invalid because of the opcodes V2 feature."""
from hathor.verification.nano_header_verifier import NanoHeaderVerifier
Expand Down
18 changes: 18 additions & 0 deletions hathor/dag_builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
NC_WITHDRAWAL_KEY = 'nc_withdrawal'
TOKEN_VERSION_KEY = 'token_version'
FEE_KEY = 'fee'
NC_TRANSFER_INPUT_KEY = 'nc_transfer_input'
NC_TRANSFER_OUTPUT_KEY = 'nc_transfer_output'


class DAGBuilder:
Expand Down Expand Up @@ -239,6 +241,22 @@ def _add_nc_attribute(self, name: str, key: str, value: str) -> None:
actions.append((token, amount))
node.attrs[key] = actions

elif key == NC_TRANSFER_INPUT_KEY:
transfer_inputs = node.get_attr_list(key, default=[])
token, amount, (wallet,) = parse_amount_token(value)
if amount < 0:
raise SyntaxError(f'unexpected negative amount in `{key}`')
transfer_inputs.append((wallet, token, amount))
node.attrs[key] = transfer_inputs

elif key == NC_TRANSFER_OUTPUT_KEY:
transfer_outputs = node.get_attr_list(key, default=[])
token, amount, (wallet,) = parse_amount_token(value)
if amount < 0:
raise SyntaxError(f'unexpected negative amount in `{key}`')
transfer_outputs.append((wallet, token, amount))
node.attrs[key] = transfer_outputs

else:
node.attrs[key] = value

Expand Down
115 changes: 111 additions & 4 deletions hathor/dag_builder/vertex_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@
# limitations under the License.

import ast
import hashlib
import re
from collections import defaultdict
from types import ModuleType
from typing import Iterator, cast
from typing import Any, Iterator, cast

from typing_extensions import assert_never

from hathor.conf.settings import HathorSettings
from hathor.crypto.util import decode_address, get_address_from_public_key_bytes
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.dag_builder.builder import FEE_KEY, NC_DEPOSIT_KEY, NC_WITHDRAWAL_KEY, DAGBuilder, DAGNode
from hathor.dag_builder.builder import (
FEE_KEY,
NC_DEPOSIT_KEY,
NC_TRANSFER_INPUT_KEY,
NC_TRANSFER_OUTPUT_KEY,
NC_WITHDRAWAL_KEY,
DAGBuilder,
DAGNode,
)
from hathor.dag_builder.types import DAGNodeType, VertexResolverType, WalletFactoryType
from hathor.dag_builder.utils import get_literal, is_literal
from hathor.nanocontracts import Blueprint, OnChainBlueprint
Expand All @@ -42,6 +51,7 @@
from hathor.transaction.base_transaction import TxInput, TxOutput
from hathor.transaction.headers.fee_header import FeeHeader, FeeHeaderEntry
from hathor.transaction.headers.nano_header import ADDRESS_LEN_BYTES
from hathor.transaction.headers.transfer_header import InputAddress, TxTransferInput, TxTransferOutput
from hathor.transaction.scripts.p2pkh import P2PKH
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.wallet import BaseWallet, HDWallet, KeyPair
Expand Down Expand Up @@ -322,16 +332,112 @@ def _parse_nc_id(self, ast_node: ast.AST) -> tuple[bytes, BlueprintId | None]:
child_contract_id = derive_child_contract_id(parent_id, salt, blueprint_id)
return child_contract_id, blueprint_id

def _get_next_nc_seqnum(self, nc_pubkey: bytes) -> int:
address = get_address_from_public_key_bytes(nc_pubkey)
def _get_next_nc_seqnum(self, address: bytes) -> int:
cur = self._next_nc_seqnum[address]
self._next_nc_seqnum[address] = cur + 1
return cur

def _sync_next_nc_seqnum(self, address: bytes, seqnum: int) -> None:
self._next_nc_seqnum[address] = max(self._next_nc_seqnum[address], seqnum + 1)

def add_headers_if_needed(self, node: DAGNode, vertex: BaseTransaction) -> None:
"""Add the configured headers."""
self.add_nano_header_if_needed(node, vertex)
self.add_fee_header_if_needed(node, vertex)
self.add_transfer_header_if_needed(node, vertex)

def _get_token_index(self, token_name: str, vertex: Transaction) -> int:
token_index = 0
if token_name != 'HTR':
token_creation_tx = self._vertices[token_name]
if token_creation_tx.hash not in vertex.tokens:
vertex.tokens.append(token_creation_tx.hash)
token_index = 1 + vertex.tokens.index(token_creation_tx.hash)
return token_index

def add_transfer_header_if_needed(self, node: DAGNode, vertex: BaseTransaction) -> None:
inputs = node.get_attr_list(NC_TRANSFER_INPUT_KEY, default=[])
outputs = node.get_attr_list(NC_TRANSFER_OUTPUT_KEY, default=[])

if not inputs and not outputs:
return

if not isinstance(vertex, Transaction):
raise TypeError('TransferHeader is only supported for transactions')

transfer_addresses: list[InputAddress] = []
transfer_signers: list[tuple[bytes, Any]] = []
transfer_address_indexes: dict[str, int] = {}
transfer_inputs: list[TxTransferInput] = []

def get_transfer_seqnum(*, wallet_name: str, address: bytes) -> int:
if vertex.is_nano_contract():
nano_header = vertex.get_nano_header()
if nano_header.nc_address == address:
return nano_header.nc_seqnum
if 'transfer_seqnum' in node.attrs:
return int(node.attrs['transfer_seqnum'])
return self._get_next_nc_seqnum(address)

for wallet_name, token_name, amount in inputs:
wallet = self.get_wallet(wallet_name)
assert isinstance(wallet, HDWallet)
privkey = wallet.get_key_at_index(0)
pubkey_bytes = privkey.sec()
address = get_address_from_public_key_bytes(pubkey_bytes)
token_index = self._get_token_index(token_name, vertex)
address_index = transfer_address_indexes.get(wallet_name)
if address_index is None:
address_index = len(transfer_addresses)
transfer_address_indexes[wallet_name] = address_index
transfer_addresses.append(InputAddress(
address=address,
seqnum=get_transfer_seqnum(wallet_name=wallet_name, address=address),
script=b'',
))
self._sync_next_nc_seqnum(address, transfer_addresses[-1].seqnum)
transfer_signers.append((pubkey_bytes, privkey))

transfer_inputs.append(TxTransferInput(
address_index=address_index,
amount=amount,
token_index=token_index,
))

transfer_outputs: list[TxTransferOutput] = []
for wallet_name, token_name, amount in outputs:
wallet = self.get_wallet(wallet_name)
assert isinstance(wallet, HDWallet)
privkey = wallet.get_key_at_index(0)
pubkey_bytes = privkey.sec()
address = get_address_from_public_key_bytes(pubkey_bytes)
token_index = self._get_token_index(token_name, vertex)
transfer_outputs.append(TxTransferOutput(
address=address,
amount=amount,
token_index=token_index,
))

from hathor.transaction.headers import TransferHeader
transfer_header = TransferHeader(
tx=vertex,
addresses=transfer_addresses,
inputs=transfer_inputs,
outputs=transfer_outputs,
)
vertex.headers.append(transfer_header)

sighash_data = vertex.get_sighash_all_data()
sighash_data_hash = hashlib.sha256(sighash_data).digest()
for i, input_address in enumerate(transfer_header.addresses):
pubkey_bytes, privkey = transfer_signers[i]
signature = privkey.sign(sighash_data_hash)
script = P2PKH.create_input_data(public_key_bytes=pubkey_bytes, signature=signature)
transfer_header.addresses[i] = InputAddress(
address=input_address.address,
seqnum=input_address.seqnum,
script=script,
)

def add_nano_header_if_needed(self, node: DAGNode, vertex: BaseTransaction) -> None:
if 'nc_id' not in node.attrs:
Expand Down Expand Up @@ -426,6 +532,7 @@ def append_actions(action: NCActionType, key: str) -> None:
nano_header.nc_seqnum = int(node.attrs['nc_seqnum'])
else:
nano_header.nc_seqnum = self._get_next_nc_seqnum(nano_header.nc_address)
self._sync_next_nc_seqnum(nano_header.nc_address, nano_header.nc_seqnum)

def add_fee_header_if_needed(self, node: DAGNode, vertex: BaseTransaction) -> None:
"""Add a FeeHeader if one is configured."""
Expand Down
1 change: 1 addition & 0 deletions hathor/feature_activation/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ class Feature(StrEnum):
COUNT_CHECKDATASIG_OP = 'COUNT_CHECKDATASIG_OP'
NANO_CONTRACTS = 'NANO_CONTRACTS'
FEE_TOKENS = 'FEE_TOKENS'
TRANSFER_HEADER = 'TRANSFER_HEADER'
OPCODES_V2 = 'OPCODES_V2'
NANO_RUNTIME_V2 = 'NANO_RUNTIME_V2'
9 changes: 7 additions & 2 deletions hathor/feature_activation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ class Features:
nanocontracts: bool
fee_tokens: bool
opcodes_version: OpcodesVersion
nano_runtime_version: NanoRuntimeVersion
nano_runtime_version: NanoRuntimeVersion = NanoRuntimeVersion.V1
transfer_headers: bool = False

@staticmethod
def from_vertex(*, settings: HathorSettings, feature_service: FeatureService, vertex: Vertex) -> Features:
"""Return information about each feature according to the state in the provided vertex."""
"""Return active/inactive state for every runtime feature according to the provided settings and vertex."""
from hathorlib.conf.settings import FeatureSetting
feature_states = feature_service.get_feature_states(vertex=vertex)
feature_settings = {
Feature.COUNT_CHECKDATASIG_OP: FeatureSetting.FEATURE_ACTIVATION,
Feature.NANO_CONTRACTS: settings.ENABLE_NANO_CONTRACTS,
Feature.FEE_TOKENS: settings.ENABLE_FEE_BASED_TOKENS,
Feature.TRANSFER_HEADER: settings.ENABLE_TRANSFER_HEADER,
Feature.OPCODES_V2: settings.ENABLE_OPCODES_V2,
Feature.NANO_RUNTIME_V2: settings.ENABLE_NANO_RUNTIME_V2,
}
Expand All @@ -68,6 +70,7 @@ def from_vertex(*, settings: HathorSettings, feature_service: FeatureService, ve
fee_tokens=feature_is_active[Feature.FEE_TOKENS],
opcodes_version=opcodes_version,
nano_runtime_version=nano_runtime_version,
transfer_headers=feature_is_active[Feature.TRANSFER_HEADER],
)

@staticmethod
Expand All @@ -91,6 +94,7 @@ def for_mempool(*, settings: HathorSettings, feature_service: FeatureService, be
# Permissive features (come from the block state):
nanocontracts=features.nanocontracts,
fee_tokens=features.fee_tokens,
transfer_headers=features.transfer_headers,
# Indifferent features (come from the block state):
nano_runtime_version=features.nano_runtime_version,
)
Expand All @@ -114,6 +118,7 @@ def all_enabled() -> Features:
fee_tokens=True,
opcodes_version=OpcodesVersion.V2,
nano_runtime_version=NanoRuntimeVersion.V2,
transfer_headers=True,
)


Expand Down
Loading
Loading