Skip to content

Commit 783483a

Browse files
Del remote signer config
1 parent 2803caf commit 783483a

File tree

5 files changed

+34
-133
lines changed

5 files changed

+34
-133
lines changed

src/commands/remote_signer_setup.py

+17-91
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,17 @@
66

77
import aiohttp
88
import click
9-
import milagro_bls_binding as bls
109
from aiohttp import ClientTimeout
1110
from eth_typing import BLSPrivateKey, HexAddress
12-
from web3 import Web3
1311

1412
from src.common.credentials import Credential
15-
from src.common.execution import get_oracles
1613
from src.common.logging import setup_logging
1714
from src.common.password import get_or_create_password_file
1815
from src.common.utils import log_verbose
1916
from src.common.validators import validate_eth_address
2017
from src.common.vault_config import VaultConfig
2118
from src.config.settings import REMOTE_SIGNER_TIMEOUT, settings
2219
from src.validators.keystores.local import LocalKeystore
23-
from src.validators.keystores.remote import RemoteSignerKeystore
24-
from src.validators.signing.key_shares import private_key_to_private_key_shares
2520

2621

2722
@click.option(
@@ -39,14 +34,6 @@
3934
required=True,
4035
help='The base URL of the remote signer, e.g. http://signer:9000',
4136
)
42-
@click.option(
43-
'--remove-existing-keys',
44-
type=bool,
45-
is_flag=True,
46-
help='Whether to remove existing keys from the remote signer. Useful'
47-
' when the oracle set changes and the previously generated key shares'
48-
' are no longer going to be used.',
49-
)
5037
@click.option(
5138
'--data-dir',
5239
default=str(Path.home() / '.stakewise'),
@@ -80,7 +67,6 @@
8067
def remote_signer_setup(
8168
vault: HexAddress,
8269
remote_signer_url: str,
83-
remove_existing_keys: bool,
8470
data_dir: str,
8571
keystores_dir: str | None,
8672
execution_endpoints: str,
@@ -105,63 +91,39 @@ def remote_signer_setup(
10591
asyncio.get_running_loop()
10692
# we need to create a separate thread so we can block before returning
10793
with ThreadPoolExecutor(1) as pool:
108-
pool.submit(
109-
lambda: asyncio.run(main(remove_existing_keys=remove_existing_keys))
110-
).result()
94+
pool.submit(lambda: asyncio.run(main())).result()
11195
except RuntimeError as e:
11296
if 'no running event loop' == e.args[0]:
11397
# no event loop running
114-
asyncio.run(main(remove_existing_keys=remove_existing_keys))
98+
asyncio.run(main())
11599
else:
116100
raise e
117101
except Exception as e:
118102
log_verbose(e)
119103

120104

121-
# pylint: disable-next=too-many-locals
122-
async def main(remove_existing_keys: bool) -> None:
105+
async def main() -> None:
123106
setup_logging()
124107
keystores = await LocalKeystore.load()
125108
if len(keystores) == 0:
126109
raise click.ClickException('Keystores not found.')
127110

128111
# Check if remote signer's keymanager API is reachable before taking further steps
129-
async with aiohttp.ClientSession(
130-
timeout=ClientTimeout(connect=REMOTE_SIGNER_TIMEOUT)
131-
) as session:
112+
async with aiohttp.ClientSession(timeout=ClientTimeout(REMOTE_SIGNER_TIMEOUT)) as session:
132113
resp = await session.get(f'{settings.remote_signer_url}/eth/v1/keystores')
133114
if resp.status != 200:
134115
raise RuntimeError(f'Failed to connect to remote signer, returned {await resp.text()}')
135116

136-
oracles = await get_oracles()
137-
138-
try:
139-
remote_signer_keystore = RemoteSignerKeystore.load_from_file(
140-
settings.remote_signer_config_file
141-
)
142-
except FileNotFoundError:
143-
remote_signer_keystore = RemoteSignerKeystore({})
144-
145117
credentials = []
146118
for pubkey, private_key in keystores.keys.items(): # pylint: disable=no-member
147-
private_key_shares = private_key_to_private_key_shares(
148-
private_key=private_key,
149-
threshold=oracles.exit_signature_recover_threshold,
150-
total=len(oracles.public_keys),
151-
)
152-
153-
for idx, private_key_share in enumerate(private_key_shares):
154-
credentials.append(
155-
Credential(
156-
private_key=BLSPrivateKey(int.from_bytes(private_key_share, 'big')),
157-
path=f'share_{pubkey}_{idx}',
158-
network=settings.network,
159-
vault=settings.vault,
160-
)
119+
credentials.append(
120+
Credential(
121+
private_key=BLSPrivateKey(int.from_bytes(private_key, 'big')),
122+
path=f'{pubkey}', # todo
123+
network=settings.network,
124+
vault=settings.vault,
161125
)
162-
remote_signer_keystore.pubkeys_to_shares[pubkey] = [
163-
Web3.to_hex(bls.SkToPk(priv_key)) for priv_key in private_key_shares
164-
]
126+
)
165127

166128
click.echo(
167129
f'Successfully generated {len(credentials)} key shares'
@@ -170,16 +132,14 @@ async def main(remove_existing_keys: bool) -> None:
170132

171133
# Import as keystores to remote signer
172134
password = get_or_create_password_file(settings.keystores_password_file)
173-
key_share_keystores = []
135+
encrypted_keystores = []
174136
for credential in credentials:
175-
key_share_keystores.append(deepcopy(credential.encrypt_signing_keystore(password=password)))
137+
encrypted_keystores.append(deepcopy(credential.encrypt_signing_keystore(password=password)))
176138

177-
async with aiohttp.ClientSession(
178-
timeout=ClientTimeout(connect=REMOTE_SIGNER_TIMEOUT)
179-
) as session:
139+
async with aiohttp.ClientSession(timeout=ClientTimeout(REMOTE_SIGNER_TIMEOUT)) as session:
180140
data = {
181-
'keystores': [ksk.as_json() for ksk in key_share_keystores],
182-
'passwords': [password for _ in key_share_keystores],
141+
'keystores': [ks.as_json() for ks in encrypted_keystores],
142+
'passwords': [password for _ in encrypted_keystores],
183143
}
184144
resp = await session.post(f'{settings.remote_signer_url}/eth/v1/keystores', json=data)
185145
if resp.status != 200:
@@ -189,7 +149,7 @@ async def main(remove_existing_keys: bool) -> None:
189149
)
190150

191151
click.echo(
192-
f'Successfully imported {len(key_share_keystores)} key shares into remote signer.',
152+
f'Successfully imported {len(encrypted_keystores)} keys into remote signer.',
193153
)
194154

195155
# Remove local keystores - keys are loaded in remote signer and are not
@@ -199,40 +159,6 @@ async def main(remove_existing_keys: bool) -> None:
199159

200160
click.echo('Removed keystores from local filesystem.')
201161

202-
# Remove outdated keystores from remote signer
203-
if remove_existing_keys:
204-
active_pubkey_shares = {
205-
pk for pk_list in remote_signer_keystore.pubkeys_to_shares.values() for pk in pk_list
206-
}
207-
208-
async with aiohttp.ClientSession(
209-
timeout=ClientTimeout(connect=REMOTE_SIGNER_TIMEOUT)
210-
) as session:
211-
resp = await session.get(f'{settings.remote_signer_url}/eth/v1/keystores')
212-
pubkeys_data = (await resp.json())['data']
213-
pubkeys_remote_signer = {
214-
pubkey_dict.get('validating_pubkey') for pubkey_dict in pubkeys_data
215-
}
216-
217-
# Only remove pubkeys from signer that are no longer needed
218-
inactive_pubkeys = pubkeys_remote_signer - active_pubkey_shares
219-
220-
resp = await session.delete(
221-
f'{settings.remote_signer_url}/eth/v1/keystores',
222-
json={'pubkeys': list(inactive_pubkeys)},
223-
)
224-
if resp.status != 200:
225-
raise RuntimeError(
226-
f'Error occurred while deleting existing keys from remote signer'
227-
f' - status code {resp.status}, body: {await resp.text()}'
228-
)
229-
230-
click.echo(
231-
f'Removed {len(inactive_pubkeys)} keys from remote signer',
232-
)
233-
234-
remote_signer_keystore.save(settings.remote_signer_config_file)
235-
236162
click.echo(
237163
f'Done.'
238164
f' Successfully configured operator to use remote signer'

src/validators/keystores/load.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from src.validators.keystores.hashi_vault import HashiVaultKeystore
66
from src.validators.keystores.local import LocalKeystore
77
from src.validators.keystores.remote import RemoteSignerKeystore
8+
from src.validators.utils import load_deposit_data
89

910
logger = logging.getLogger(__name__)
1011

1112

1213
async def load_keystore() -> BaseKeystore:
1314
if settings.remote_signer_url:
14-
remote_keystore = await RemoteSignerKeystore.load()
15+
deposit_data = load_deposit_data(settings.vault, settings.deposit_data_file)
16+
remote_keystore = RemoteSignerKeystore(deposit_data.public_keys)
1517
logger.info(
1618
'Using remote signer at %s for %i public keys',
1719
settings.remote_signer_url,

src/validators/keystores/remote.py

+8-41
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import dataclasses
2-
import json
32
import logging
43
from dataclasses import dataclass
5-
from pathlib import Path
64

75
import milagro_bls_binding as bls
86
from aiohttp import ClientSession, ClientTimeout
@@ -50,37 +48,21 @@ class VoluntaryExitRequestModel:
5048

5149

5250
class RemoteSignerKeystore(BaseKeystore):
53-
pubkeys_to_shares: dict[HexStr, list[HexStr]]
54-
55-
def __init__(self, pubkeys_to_shares: dict[HexStr, list[HexStr]]):
56-
self.pubkeys_to_shares = pubkeys_to_shares
51+
def __init__(self, public_keys: list[HexStr]):
52+
self._public_keys = public_keys
5753

5854
def __bool__(self) -> bool:
59-
return len(self.pubkeys_to_shares) > 0
55+
return bool(self._public_keys)
6056

6157
def __len__(self) -> int:
62-
return len(self.pubkeys_to_shares)
58+
return len(self._public_keys)
6359

6460
def __contains__(self, public_key):
65-
return public_key in self.pubkeys_to_shares
66-
67-
@classmethod
68-
def load_from_data(cls, data: dict) -> 'RemoteSignerKeystore':
69-
return cls._load_data(data)
70-
71-
@classmethod
72-
def load_from_file(cls, path: str | Path) -> 'RemoteSignerKeystore':
73-
with open(path, 'r', encoding='utf-8') as f:
74-
data = json.load(f)
75-
return cls._load_data(data['pubkeys_to_shares'])
61+
return public_key in self._public_keys
7662

77-
@classmethod
78-
async def load(cls) -> 'RemoteSignerKeystore':
79-
return cls.load_from_file(settings.remote_signer_config_file)
80-
81-
def save(self, path: str | Path) -> None:
82-
with open(path, 'w', encoding='utf-8') as f:
83-
json.dump({'pubkeys_to_shares': self.pubkeys_to_shares}, f)
63+
@property
64+
def public_keys(self) -> list[HexStr]:
65+
return self._public_keys
8466

8567
async def get_exit_signature_shards(
8668
self,
@@ -132,21 +114,6 @@ async def get_exit_signature(
132114
bls.Verify(BLSPubkey(Web3.to_bytes(hexstr=public_key)), message, exit_signature)
133115
return exit_signature
134116

135-
@property
136-
def public_keys(self) -> list[HexStr]:
137-
return list(self.pubkeys_to_shares.keys())
138-
139-
@classmethod
140-
def _load_data(cls, data: dict) -> 'RemoteSignerKeystore':
141-
pubkeys_to_shares = {}
142-
for full_pubkey, pubkey_shares in data.items():
143-
pubkeys_to_shares[full_pubkey] = [HexStr(s) for s in pubkey_shares]
144-
145-
if len(pubkeys_to_shares.keys()) == 0:
146-
raise RuntimeError('Remote signer config does not contain any pubkeys')
147-
148-
return RemoteSignerKeystore(pubkeys_to_shares=pubkeys_to_shares)
149-
150117
async def _sign(
151118
self,
152119
public_key: BLSPubkey,

src/validators/typings.py

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class DepositData:
2626
validators: list[Validator]
2727
tree: StandardMerkleTree
2828

29+
@property
30+
def public_keys(self) -> list[HexStr]:
31+
return [v.public_key for v in self.validators]
32+
2933

3034
@dataclass
3135
class ExitSignatureShards:

src/validators/utils.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import dataclasses
3+
import functools
34
import json
45
import logging
56
import random
@@ -131,6 +132,7 @@ async def send_approval_request(
131132
)
132133

133134

135+
@functools.cache
134136
def load_deposit_data(vault: HexAddress, deposit_data_file: Path) -> DepositData:
135137
"""Loads and verifies deposit data."""
136138
with open(deposit_data_file, 'r', encoding='utf-8') as f:

0 commit comments

Comments
 (0)