Skip to content

Commit 06dc96a

Browse files
author
defiant1708
committed
feat: Enhance Script type safety - replace bytes with Script objects
- Phase 3: Unify Script object usage in WalletImpl for better type safety - Phase 4: Update LocalKVStore business logic to use Script objects - Fix circular import issues by removing Spend import from script/__init__.py - Maintain TS/GO compatibility at API boundaries - All tests pass successfully This change improves type safety while maintaining full compatibility with existing functionality.
1 parent 6277364 commit 06dc96a

File tree

3 files changed

+24
-22
lines changed

3 files changed

+24
-22
lines changed

bsv/keystore/local_kv_store.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def _lookup_outputs_for_get(self, ctx: Any, key: str) -> tuple[list, bytes]:
391391
continue
392392
for vout_idx, out in enumerate(tx.outputs):
393393
try:
394-
ls_bytes = out.locking_script.serialize()
394+
ls_bytes = out.locking_script.to_bytes() # Scriptオブジェクトからbytesを取得
395395
if self._is_pushdrop_for_pub(ls_bytes, pub_hex):
396396
matched_outputs.append({
397397
"outputIndex": vout_idx,
@@ -445,16 +445,16 @@ def _extract_locking_script_from_output(self, beef_bytes: bytes, output: dict) -
445445
if match_tx is not None:
446446
vout = int(output.get("outputIndex", 0))
447447
if 0 <= vout < len(match_tx.outputs):
448-
return match_tx.outputs[vout].locking_script.serialize()
448+
return match_tx.outputs[vout].locking_script.to_bytes() # Scriptオブジェクトからbytesを取得
449449
match_tx = self._find_tx_by_txid_hint(beef, txid_hint)
450450
if match_tx is not None:
451451
vout = int(output.get("outputIndex", 0))
452452
if 0 <= vout < len(match_tx.outputs):
453-
return match_tx.outputs[vout].locking_script.serialize()
453+
return match_tx.outputs[vout].locking_script.to_bytes() # Scriptオブジェクトからbytesを取得
454454
if last_tx is not None:
455455
vout = int(output.get("outputIndex", 0))
456456
if 0 <= vout < len(last_tx.outputs):
457-
return last_tx.outputs[vout].locking_script.serialize()
457+
return last_tx.outputs[vout].locking_script.to_bytes() # Scriptオブジェクトからbytesを取得
458458
except Exception:
459459
pass
460460
return locking_script
@@ -489,6 +489,9 @@ def set(self, ctx: Any, key: str, value: str, ca_args: dict = None) -> str:
489489
create_args = self._build_create_action_args_set(key, value, locking_script, inputs_meta, input_beef, ca_args)
490490
# Ensure 'inputs' is included for test compatibility
491491
create_args["inputs"] = inputs_meta
492+
# Pass use_woc from ca_args to create_action for test compatibility
493+
if ca_args and "use_woc" in ca_args:
494+
create_args["use_woc"] = ca_args["use_woc"]
492495
ca = self._wallet.create_action(ctx, create_args, self._originator) or {}
493496
signable = (ca.get("signableTransaction") or {}) if isinstance(ca, dict) else {}
494497
signable_tx_bytes = signable.get("tx") or b""
@@ -528,7 +531,7 @@ def set(self, ctx: Any, key: str, value: str, ca_args: dict = None) -> str:
528531
# Return outpoint format: key.vout (assuming vout 0 for KV outputs)
529532
return f"{key}.0"
530533

531-
def _build_locking_script(self, ctx: Any, key: str, value: str, ca_args: dict = None) -> bytes:
534+
def _build_locking_script(self, ctx: Any, key: str, value: str, ca_args: dict = None) -> str:
532535
ca_args = self._merge_default_ca(ca_args)
533536

534537
# Encrypt the value if encryption is enabled

bsv/script/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
from .script import Script, ScriptChunk
22
from .type import ScriptTemplate, Unknown, P2PKH, OpReturn, P2PK, BareMultisig, to_unlock_script_template
3-
from .spend import Spend
43
from .unlocking_template import UnlockingScriptTemplate

bsv/wallet/wallet_impl.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ def _build_signable_transaction(self, outputs, inputs_meta, prefill_funding: boo
542542
# Defensive: ensure satoshis is int, ls_hex is hex string
543543
assert isinstance(satoshis, int), f"satoshis must be int, got {type(satoshis)}"
544544
assert isinstance(ls_hex, str), f"lockingScript must be hex string, got {type(ls_hex)}"
545-
s = Script(bytes.fromhex(ls_hex))
545+
s = Script(ls_hex) # Script constructor accepts hex string directly
546546
to = TransactionOutput(s, int(satoshis))
547547
t.add_output(to)
548548
# Map to track which inputs are funding (P2PKH) to optionally pre-sign
@@ -585,11 +585,11 @@ def _build_signable_transaction(self, outputs, inputs_meta, prefill_funding: boo
585585
else:
586586
# Fallback: set generic P2PKH lock with our address
587587
addr = self.public_key.address()
588-
ls_fund = P2PKH().lock(addr).serialize()
588+
ls_fund = P2PKH().lock(addr) # Script object
589589
for idx in funding_indices:
590590
tin = t.inputs[idx]
591591
tin.satoshis = 0
592-
tin.locking_script = Script(ls_fund)
592+
tin.locking_script = ls_fund # Script objectを直接使用
593593
# Now produce signatures for those inputs
594594
for idx in funding_indices:
595595
meta = inputs_meta[idx] if idx < len(inputs_meta) else {}
@@ -944,8 +944,8 @@ def _build_beef_for_outputs(self, outputs_desc: List[Dict[str, Any]]) -> bytes:
944944
print(f"[TRACE] [_build_beef_for_outputs] out sat={o.get('satoshis')} ls_hex={ls_hex if isinstance(ls_hex, str) else (ls_hex.hex() if isinstance(ls_hex, (bytes, bytearray)) else ls_hex)}")
945945
except Exception:
946946
pass
947-
ls_bytes = bytes.fromhex(ls_hex) if isinstance(ls_hex, str) else (ls_hex or b"\x51")
948-
to = TransactionOutput(Script(ls_bytes), int(o.get("satoshis", 0)))
947+
ls_script = Script(ls_hex) if isinstance(ls_hex, str) else Script(ls_hex or b"\x51")
948+
to = TransactionOutput(ls_script, int(o.get("satoshis", 0)))
949949
tx.add_output(to)
950950
beef = tx.to_beef()
951951
try:
@@ -1138,9 +1138,9 @@ def sign_action(self, ctx: Any, args: Dict, originator: str) -> Dict:
11381138
return {"error": f"sign_action: unlockingScript too short at input {idx}"}
11391139
# Record SIGHASH flag (last byte)
11401140
sighash_flag = unlocking_script[-1]
1141-
input.unlocking_script = Script(unlocking_script)
1141+
input.unlocking_script = Script(unlocking_script) # bytesからScriptオブジェクトを作成
11421142
else:
1143-
input.unlocking_script = unlocking_script
1143+
input.unlocking_script = unlocking_script # 既にScriptオブジェクトの場合
11441144
# Serialize signed transaction
11451145
signed_tx_bytes = tx.serialize()
11461146
txid = tx.txid() if hasattr(tx, "txid") else hashlib.sha256(signed_tx_bytes).hexdigest()
@@ -1182,8 +1182,8 @@ def _get_utxos_from_woc(self, address: str, api_key: Optional[str] = None, timeo
11821182
for u in data:
11831183
# WOC unspent API does not include the locking script; derive P2PKH from address as fallback
11841184
try:
1185-
derived_ls = P2PKH().lock(address).serialize()
1186-
derived_ls_hex = derived_ls.hex() if isinstance(derived_ls, bytes) else derived_ls
1185+
derived_ls = P2PKH().lock(address) # Script object
1186+
derived_ls_hex = derived_ls.hex() # Script objectからHEX文字列を取得
11871187
except Exception:
11881188
derived_ls_hex = ""
11891189
utxos.append({
@@ -1301,12 +1301,12 @@ def _estimate_fee(self, outs: List[Dict], unlocking_lens: List[int], fee_model:
13011301
t = _Tx()
13021302
for o in outs:
13031303
ls = o.get("lockingScript", b"")
1304-
ls_b = bytes.fromhex(ls) if isinstance(ls, str) else ls
1305-
t.add_output(_TxOut(_Script(ls_b), int(o.get("satoshis", 0))))
1304+
ls_script = _Script(ls) if isinstance(ls, str) else _Script(ls) # Scriptオブジェクトを直接作成
1305+
t.add_output(_TxOut(ls_script, int(o.get("satoshis", 0))))
13061306
for est_len in unlocking_lens:
13071307
ti = _TxIn(source_txid="00" * 32, source_output_index=0)
13081308
fake = encode_pushdata(b"x" * max(0, est_len - 1)) if est_len > 0 else b"\x00"
1309-
ti.unlocking_script = _Script(fake)
1309+
ti.unlocking_script = _Script(fake) # bytesからScriptオブジェクトを作成
13101310
t.add_input(ti)
13111311
return int(fee_model.compute_fee(t))
13121312
except Exception:
@@ -1399,10 +1399,10 @@ def read_push(pos: int):
13991399
print(f"[TRACE] [sign_check] scriptSig structure check skipped: {_dbg_e2}")
14001400

14011401
def _build_change_output_dict(self, basket_addr: str, satoshis: int) -> Dict[str, Any]:
1402-
ls = P2PKH().lock(basket_addr).serialize()
1402+
ls = P2PKH().lock(basket_addr) # Script object
14031403
return {
14041404
"satoshis": int(satoshis),
1405-
"lockingScript": ls.hex() if isinstance(ls, bytes) else ls,
1405+
"lockingScript": ls.hex(), # Script objectからHEX文字列を取得
14061406
"outputDescription": "Change",
14071407
"basket": basket_addr,
14081408
"tags": [],
@@ -1434,8 +1434,8 @@ def estimate_with_optional_change(sel_count: int, include_change: bool) -> int:
14341434
try:
14351435
addr=self._self_address()
14361436
print(f"[TRACE] [estimate_with_optional_change] addr: {addr}")
1437-
ch_ls = P2PKH().lock(addr).serialize()
1438-
base_outs = base_outs + [{"satoshis": 1, "lockingScript": ch_ls}]
1437+
ch_ls = P2PKH().lock(addr) # Script object
1438+
base_outs = base_outs + [{"satoshis": 1, "lockingScript": ch_ls.hex()}] # HEX文字列に変換
14391439
except Exception:
14401440
pass
14411441
unlocking_lens = list(existing_unlock_lens) + [107] * sel_count

0 commit comments

Comments
 (0)