5 from __future__
import annotations
7 ETH_PRIVATE_KEY_FILE_DEFAULT, ETH_RPC_ENDPOINT_DEFAULTS, \
8 ETH_NETWORK_FILE_DEFAULT, ETH_NETWORK_DEFAULT, \
9 ZETH_PUBLIC_ADDRESS_FILE_DEFAULT
12 InstanceDescription, get_block_number, compile_files
18 open_web3, short_commitment, EtherValue, get_zeth_dir, from_zeth_units
20 from click
import ClickException
22 from os.path
import exists, join, splitext
24 from typing
import Dict, Tuple, Optional, Callable, Any, cast
29 Simple description of a network. Name (may be used in some cases to
30 understand the type of network) and endpoint URL.
36 certificate: Optional[str] =
None,
37 insecure: bool =
False):
44 json_dict: Dict[str, Any] = {
51 json_dict[
"insecure"] = self.
insecure
52 return json.dumps(json_dict)
55 def from_json(network_config_json: str) -> NetworkConfig:
56 json_dict = json.loads(network_config_json)
58 name=json_dict[
"name"],
59 endpoint=json_dict[
"endpoint"],
60 certificate=json_dict.get(
"certificate",
None),
61 insecure=json_dict.get(
"insecure",
None))
66 Context for users of these client tools
70 eth_network: Optional[str],
71 prover_server_endpoint: str,
72 prover_config_file: str,
86 Parse the `eth_network` parameter to extract a URL. If `eth_network` does
87 not contain a URL, try interpreting it as a network name, otherwise
88 interpret it as a file to load the network config from. Fall back to a
89 default network config filename, and finally the default network name.
91 if eth_network
is None:
92 if exists(ETH_NETWORK_FILE_DEFAULT):
93 eth_network = ETH_NETWORK_FILE_DEFAULT
95 eth_network = ETH_NETWORK_DEFAULT
97 if eth_network.startswith(
"http"):
102 if exists(eth_network):
103 with open(eth_network)
as network_f:
104 return NetworkConfig.from_json(network_f.read())
108 endpoint = ETH_RPC_ENDPOINT_DEFAULTS[eth_network]
110 except KeyError
as ex:
111 raise ClickException(f
"invalid network name / url: {eth_network}")
from ex
116 url=eth_net.endpoint,
117 certificate=eth_net.certificate,
118 insecure=eth_net.insecure)
123 Parse a string as either an eth address, or a contract instance file.
125 if contract_addr.startswith(
"0x"):
126 return Web3.toChecksumAddress(contract_addr)
127 if exists(contract_addr):
128 with open(contract_addr,
"r")
as instance_f:
129 instance = InstanceDescription.from_json_dict(json.load(instance_f))
130 return Web3.toChecksumAddress(instance.address)
131 raise ClickException(
132 f
"failed to parse as address or instance file: {contract_addr}")
142 Holds an InstanceDescription for the mixer contract, and optionally an
143 InstanceDescription for the token contract. When serialized to json, the
144 InstanceDescription for the mixer is held in the top-level object, so that
145 MixerDescription is compatible with a regular contract instance.
149 mixer: InstanceDescription,
150 token: Optional[InstanceDescription],
151 permitted_dispatcher: Optional[str],
152 vk_hash: Optional[str]):
163 zeth_mixer: Dict[str, Any] = {}
169 zeth_mixer[
"vk_hash"] = self.
vk_hash
170 json_dict[
"zeth_mixer"] = zeth_mixer
175 zeth_mixer = json_dict[
"zeth_mixer"]
177 mixer = InstanceDescription.from_json_dict(json_dict)
178 token_dict = cast(Optional[Dict[str, Any]], zeth_mixer.get(
"token",
None))
179 token = InstanceDescription.from_json_dict(token_dict) \
180 if token_dict
else None
181 permitted_dispatcher = \
182 cast(Optional[str], zeth_mixer.get(
"permitted_dispatcher",
None))
183 vk_hash = cast(Optional[str], zeth_mixer.get(
"vk_hash",
None))
189 openzeppelin_dir = join(
190 zeth_dir,
"zeth_contracts",
"node_modules",
"openzeppelin-solidity")
192 openzeppelin_dir,
"contracts",
"token",
"ERC20",
"IERC20.sol")
194 erc20_interface = compiled_sol[ierc20_path +
":IERC20"]
195 return erc20_interface[
"abi"]
203 mixer_desc_file: str,
204 mixer_desc: MixerDescription) ->
None:
206 Write the mixer (and token) instance information
208 with open(mixer_desc_file,
"w")
as instance_f:
209 json.dump(mixer_desc.to_json_dict(), instance_f)
214 Return mixer and token (if present) contract instances
216 with open(mixer_desc_file,
"r")
as desc_f:
217 return MixerDescription.from_json_dict(json.load(desc_f))
225 return ctx.address_file
230 Load a ZethAddressPub from a key file.
234 with open(pub_addr_file,
"r")
as pub_addr_f:
235 return ZethAddressPub.parse(pub_addr_f.read())
239 pub_addr: ZethAddressPub, pub_addr_file: str) ->
None:
241 Write a ZethAddressPub to a file
243 with open(pub_addr_file,
"w")
as pub_addr_f:
244 pub_addr_f.write(
str(pub_addr))
252 with open(addr_file,
"r")
as addr_f:
253 return ZethAddressPriv.from_json(addr_f.read())
257 secret_addr: ZethAddressPriv, addr_file: str) ->
None:
259 Write ZethAddressPriv to file
261 with open(addr_file,
"w")
as addr_f:
262 addr_f.write(secret_addr.to_json())
267 Load a ZethAddress secret from a file, and the associated public address,
268 and return as a ZethAddress.
270 return ZethAddress.from_secret_public(
277 js_secret: ZethAddressPriv,
278 ctx: ClientConfig) -> Wallet:
280 Load a wallet using a secret key.
282 wallet_dir = ctx.wallet_dir
286 mixer_instance, WALLET_USERNAME, wallet_dir, js_secret, tree_hash)
292 pp: PairingParameters,
293 wait_tx: Optional[str],
294 callback: Optional[Callable[[ZethNoteDescription],
None]] =
None,
295 batch_size: Optional[int] =
None) -> int:
297 Implementation of sync, reused by several commands. Returns the
298 block_number synced to. Also updates and saves the MerkleTree.
300 def _do_sync() -> int:
301 wallet_next_block = wallet.get_next_block()
304 if chain_block_number >= wallet_next_block:
305 new_merkle_root: Optional[bytes] =
None
307 print(f
"SYNCHING blocks ({wallet_next_block} - {chain_block_number})")
308 mixer_instance = wallet.mixer_instance
315 new_merkle_root = mix_result.new_merkle_root
316 for note_desc
in wallet.receive_notes(
317 mix_result.output_events, pp):
321 spent_commits = wallet.mark_nullifiers_used(mix_result.nullifiers)
322 for commit
in spent_commits:
323 print(f
" SPENT: {commit}")
325 wallet.update_and_save_state(next_block=chain_block_number + 1)
329 our_merkle_root = wallet.merkle_tree.get_root()
330 assert new_merkle_root == our_merkle_root
332 return chain_block_number
340 tx_receipt = web3.eth.waitForTransactionReceipt(wait_tx, 10000)
341 tx = web3.eth.getTransaction(wait_tx)
342 gas_used = tx_receipt.gasUsed
343 status = tx_receipt.status
344 size_bytes = len(tx.input)
346 f
"{wait_tx[0:8]}: gasUsed={gas_used}, status={status}, "
347 f
"input_size={size_bytes}")
354 The name of a public address file, given the secret address file.
356 return splitext(addr_file)[0] +
".pub"
361 Given a file name, which could point to a private or public key file, guess
362 at the name of the public key file.
365 if exists(pub_addr_file):
367 if exists(base_file):
370 raise ClickException(f
"No public key file {pub_addr_file} or {base_file}")
375 Create a prover client using the settings from the commands context.
378 ctx.prover_server_endpoint, ctx.prover_config_file)
383 prover_client: Optional[ProverClient] =
None) -> MixerClient:
385 Create a MixerClient for an existing deployment.
393 prover_client: Optional[ProverClient] =
None
394 ) -> Tuple[MixerClient, MixerDescription]:
396 Create a MixerClient and MixerDescription object, for an existing deployment.
400 mixer_instance = mixer_desc.mixer.instantiate(web3)
401 if prover_client
is None:
403 prover_config = prover_client.get_configuration()
404 mixer_client =
MixerClient(web3, prover_config, mixer_instance)
405 return (mixer_client, mixer_desc)
410 Generate a short human-readable description of a commitment.
414 return f
"{cm}: value={value} ETH, addr={note_desc.address}"
418 print(f
" NEW NOTE: {zeth_note_short(note_desc)}")
421 def parse_output(output_str: str) -> Tuple[ZethAddressPub, EtherValue]:
423 Parse a string of the form "<receiver_pub_address>,<value>" to an output
424 specification. <receiver_pub_address> can be a file name containing the
425 address. "<value>" is interpreted as the <default-address-file>,<value>.
427 parts = output_str.split(
",")
429 addr = ZETH_PUBLIC_ADDRESS_FILE_DEFAULT
431 elif len(parts) == 2:
435 raise ClickException(f
"invalid output spec: {output_str}")
438 with open(addr,
"r")
as addr_f:
441 return (ZethAddressPub.parse(addr),
EtherValue(value))
446 Given an --eth-addr command line param, either parse the address, load from
447 the file, or use a default file name.
449 eth_addr = eth_addr
or ETH_ADDRESS_DEFAULT
450 if eth_addr.startswith(
"0x"):
451 return Web3.toChecksumAddress(eth_addr)
453 with open(eth_addr,
"r")
as eth_addr_f:
454 return Web3.toChecksumAddress(eth_addr_f.read().rstrip())
455 raise ClickException(f
"could find file or parse eth address: {eth_addr}")
459 if exists(eth_addr_file):
460 raise ClickException(f
"refusing to overwrite address \"{eth_addr_file}\"")
461 with open(eth_addr_file,
"w")
as eth_addr_f:
462 eth_addr_f.write(eth_addr)
466 private_key_file = private_key_file
or ETH_PRIVATE_KEY_FILE_DEFAULT
467 if exists(private_key_file):
468 with open(private_key_file,
"rb")
as private_key_f:
469 return private_key_f.read(32)
474 if exists(private_key_file):
475 raise ClickException(
476 f
"refusing to overwrite private key \"{private_key_file}\"")
477 with open(private_key_file,
"wb")
as private_key_f:
478 private_key_f.write(private_key)