Преглед изворни кода

fix(hip-3-pusher): Multisig update support (only 1/N, no KMS) (#3147)

* fix(hip-3-pusher): Multisig update support (only 1/N, no KMS)

* fix market name

* newline
Mike Rolish пре 4 недеља
родитељ
комит
740c5e1f5d

+ 4 - 0
apps/hip-3-pusher/config/config.toml

@@ -11,6 +11,10 @@ publish_interval = 3.0
 publish_timeout = 5.0
 enable_publish = false
 
+[multisig]
+enable_multisig = false
+multisig_address = "0x0000000000000000000000000000000000000005"
+
 [kms]
 enable_kms = false
 aws_kms_key_id_path = "/path/to/aws_kms_key_id.txt"

+ 1 - 1
apps/hip-3-pusher/pyproject.toml

@@ -1,6 +1,6 @@
 [project]
 name = "hip-3-pusher"
-version = "0.1.6"
+version = "0.1.7"
 description = "Hyperliquid HIP-3 market oracle pusher"
 readme = "README.md"
 requires-python = "==3.13.*"

+ 6 - 0
apps/hip-3-pusher/src/pusher/config.py

@@ -10,6 +10,11 @@ class KMSConfig(BaseModel):
     aws_kms_key_id_path: Optional[FilePath] = None
 
 
+class MultisigConfig(BaseModel):
+    enable_multisig: bool
+    multisig_address: Optional[str] = None
+
+
 class LazerConfig(BaseModel):
     lazer_urls: list[str]
     lazer_api_key: str
@@ -52,3 +57,4 @@ class Config(BaseModel):
     kms: KMSConfig
     lazer: LazerConfig
     hermes: HermesConfig
+    multisig: MultisigConfig

+ 62 - 6
apps/hip-3-pusher/src/pusher/publisher.py

@@ -7,6 +7,7 @@ from pathlib import Path
 from eth_account import Account
 from eth_account.signers.local import LocalAccount
 from hyperliquid.exchange import Exchange
+from hyperliquid.utils.signing import get_timestamp_ms, sign_multi_sig_l1_action_payload
 
 from pusher.config import Config
 from pusher.exception import PushError
@@ -29,19 +30,28 @@ class Publisher:
 
         self.kms_signer = None
         self.enable_kms = False
-        oracle_account = None
+        self.oracle_account = None
         if not config.kms.enable_kms:
             oracle_pusher_key = Path(config.hyperliquid.oracle_pusher_key_path).read_text().strip()
-            oracle_account: LocalAccount = Account.from_key(oracle_pusher_key)
-            logger.info("oracle pusher local pubkey: {}", oracle_account.address)
-        self.publisher_exchanges = [Exchange(wallet=oracle_account,
+            self.oracle_account: LocalAccount = Account.from_key(oracle_pusher_key)
+            logger.info("oracle pusher local pubkey: {}", self.oracle_account.address)
+        self.publisher_exchanges = [Exchange(wallet=self.oracle_account,
                                              base_url=url,
                                              timeout=config.hyperliquid.publish_timeout)
                                     for url in self.push_urls]
         if config.kms.enable_kms:
+            # TODO: Add KMS/multisig support
+            if config.multisig.enable_multisig:
+                raise Exception("KMS/multisig not yet supported")
+
             self.enable_kms = True
             self.kms_signer = KMSSigner(config, self.publisher_exchanges)
 
+        if config.multisig.enable_multisig:
+            if not config.multisig.multisig_address:
+                raise Exception("Multisig enabled but missing multisig address")
+            self.multisig_address = config.multisig.multisig_address
+
         self.market_name = config.hyperliquid.market_name
         self.market_symbol = config.hyperliquid.market_symbol
         self.enable_publish = config.hyperliquid.enable_publish
@@ -68,12 +78,12 @@ class Publisher:
             return
         else:
             logger.debug("Current oracle price: {}", oracle_px)
-            oracle_pxs[self.market_symbol] = oracle_px
+            oracle_pxs[f"{self.market_name}:{self.market_symbol}"] = oracle_px
 
         mark_pxs = []
         external_perp_pxs = {}
         if self.price_state.hl_mark_price:
-            external_perp_pxs[self.market_symbol] = self.price_state.hl_mark_price.price
+            external_perp_pxs[f"{self.market_name}:{self.market_symbol}"] = self.price_state.hl_mark_price.price
 
         # TODO: "Each update can change oraclePx and markPx by at most 1%."
         # TODO: "The markPx cannot be updated such that open interest would be 10x the open interest cap."
@@ -87,6 +97,12 @@ class Publisher:
                         all_mark_pxs=mark_pxs,
                         external_perp_pxs=external_perp_pxs,
                     )
+                elif self.multisig_address:
+                    push_response = self._send_multisig_update(
+                        oracle_pxs=oracle_pxs,
+                        all_mark_pxs=mark_pxs,
+                        external_perp_pxs=external_perp_pxs,
+                    )
                 else:
                     push_response = self._send_update(
                         oracle_pxs=oracle_pxs,
@@ -133,3 +149,43 @@ class Publisher:
         self.metrics.push_interval_histogram.record(push_interval, self.metrics_labels)
         self.last_push_time = now
         logger.debug("Push interval: {}", push_interval)
+
+    def _send_multisig_update(self, oracle_pxs, all_mark_pxs, external_perp_pxs):
+        for exchange in self.publisher_exchanges:
+            try:
+                return self._send_single_multisig_update(
+                    exchange=exchange,
+                    oracle_pxs=oracle_pxs,
+                    all_mark_pxs=all_mark_pxs,
+                    external_perp_pxs=external_perp_pxs,
+                )
+            except Exception as e:
+                logger.exception("_send_single_multisig_update exception for endpoint: {} error: {}", exchange.base_url, repr(e))
+
+        raise PushError("all push endpoints failed for multisig")
+
+    def _send_single_multisig_update(self, exchange, oracle_pxs, all_mark_pxs, external_perp_pxs):
+        timestamp = get_timestamp_ms()
+        oracle_pxs_wire = sorted(list(oracle_pxs.items()))
+        mark_pxs_wire = [sorted(list(mark_pxs.items())) for mark_pxs in all_mark_pxs]
+        external_perp_pxs_wire = sorted(list(external_perp_pxs.items()))
+        action = {
+            "type": "perpDeploy",
+            "setOracle": {
+                "dex": self.market_name,
+                "oraclePxs": oracle_pxs_wire,
+                "markPxs": mark_pxs_wire,
+                "externalPerpPxs": external_perp_pxs_wire,
+            },
+        }
+        signatures = [sign_multi_sig_l1_action_payload(
+            wallet=self.oracle_account,
+            action=action,
+            is_mainnet=not self.use_testnet,
+            vault_address=None,
+            timestamp=timestamp,
+            expires_after=None,
+            payload_multi_sig_user=self.multisig_address,
+            outer_signer=self.oracle_account.address,
+        )]
+        return exchange.multi_sig(self.multisig_address, action, signatures, timestamp)

+ 1 - 1
apps/hip-3-pusher/uv.lock

@@ -329,7 +329,7 @@ wheels = [
 
 [[package]]
 name = "hip-3-pusher"
-version = "0.1.6"
+version = "0.1.7"
 source = { editable = "." }
 dependencies = [
     { name = "boto3" },