Skip to content

Commit

Permalink
defund leftover ETH from nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
vepkenez committed May 27, 2022
1 parent e4d95a5 commit 34d2fa5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 5 deletions.
4 changes: 2 additions & 2 deletions nucypher_ops/cli/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def list(network, namespace, all, as_json):
@click.option('--cloudprovider', help="aws or digitalocean")
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--include-host', 'include_hosts', help="destroy only the named hosts", multiple=True, type=click.STRING)
@click.option('--include-host', 'include_hosts', help="Peform this operation on only the named hosts", multiple=True, type=click.STRING)
def destroy(cloudprovider, namespace, network, include_hosts):
"""Cleans up all previously created resources for the given network for the same cloud provider"""

Expand Down Expand Up @@ -163,7 +163,7 @@ def destroy(cloudprovider, namespace, network, include_hosts):
@cli.command('remove')
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--include-host', 'include_hosts', help="destroy only the named hosts", multiple=True, type=click.STRING)
@click.option('--include-host', 'include_hosts', help="Peform this operation on only the named hosts", multiple=True, type=click.STRING)
def remove(namespace, network, include_hosts):
"""Removes managed resources for the given network/namespace"""

Expand Down
19 changes: 17 additions & 2 deletions nucypher_ops/cli/ursula.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def deploy(nucypher_image, namespace, network, include_hosts, envvars, cliargs):
@click.option('--fast', help="Only call blockchain and http methods, skip ssh into each node", default=None, is_flag=True)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--include-host', 'include_hosts', help="Query status on only the named hosts", multiple=True, type=click.STRING)
@click.option('--include-host', 'include_hosts', help="Peform this operation on only the named hosts", multiple=True, type=click.STRING)
def status(fast, namespace, network, include_hosts):
"""Displays ursula status and updates worker data in stakeholder config"""

Expand All @@ -100,7 +100,7 @@ def status(fast, namespace, network, include_hosts):
@click.option('--amount', help="The amount to fund each node. Default is .003", type=click.FLOAT, default=.003)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--include-host', 'include_hosts', help="Query status on only the named hosts", multiple=True, type=click.STRING)
@click.option('--include-host', 'include_hosts', help="Peform this operation on only the named hosts", multiple=True, type=click.STRING)
def fund(amount, namespace, network, include_hosts):
"""
fund remote nodes autmoatically using a locally managed burner wallet
Expand Down Expand Up @@ -139,3 +139,18 @@ def fund(amount, namespace, network, include_hosts):

deployer.fund_nodes(wallet, hostnames, amount)

@cli.command('defund')
@click.option('--amount', help="The amount to defund. Default is the entire balance of the node's wallet.", type=click.FLOAT, default=None)
@click.option('--to-address', help="To which ETH address are you sending the proceeds?", required=True)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default=DEFAULT_NAMESPACE)
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default=DEFAULT_NETWORK)
@click.option('--include-host', 'include_hosts', help="Peform this operation on only the named hosts", multiple=True, type=click.STRING)
def defund(amount, to_address, namespace, network, include_hosts):

deployer = CloudDeployers.get_deployer('generic')(emitter, namespace=namespace, network=network)

hostnames = deployer.config['instances'].keys()
if include_hosts:
hostnames = include_hosts

deployer.defund_nodes(hostnames, to=to_address, amount=amount)
48 changes: 47 additions & 1 deletion nucypher_ops/ops/fleet_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ def fund_nodes(self, web3, wallet, node_names, amount):
def send_eth(self, web3, wallet, destination_address, amount_eth):

transaction = {
'chainId': self.chain_id,
"nonce": web3.eth.getTransactionCount(wallet.address, 'pending'),
"from": wallet.address,
"to": destination_address,
Expand All @@ -903,7 +904,52 @@ def send_eth(self, web3, wallet, destination_address, amount_eth):
}
signed_tx = wallet.sign_transaction(transaction)
return web3.eth.send_raw_transaction(signed_tx.rawTransaction).hex()


def get_backup_path_by_nickname(self, nickname):
rootpath = os.path.join(self.config_dir, 'remote_worker_backups')
return os.path.join(rootpath,self.config['instances'][nickname]['publicaddress'])

def get_node_config(self, nickname):
return self.config['instances'][nickname]

@needs_provider
def defund_nodes(self, web3, hostnames, to=None, amount=None):
for hostname in hostnames:
amount_to_send = None
backuppath = self.get_backup_path_by_nickname(hostname)
nodeconfig = self.get_node_config(hostname)
for keystorepath in Path(backuppath).rglob('*UTC*'): # should only be one
ethpw = self.config['ethpassword']
with open(keystorepath) as keyfile:
encrypted_key = keyfile.read()
private_key = web3.eth.account.decrypt(encrypted_key, ethpw)
wallet = web3.eth.account.from_key(private_key)
balance = web3.eth.get_balance(wallet.address)
if not balance:
self.emitter.echo(f'{hostname} has no ETH')
continue
if amount:
amount_to_send = web3.toWei(amount, 'ether')
else:
# we are sending all of it
needed_gas = web3.eth.gasPrice * 21000 * 2
amount_minus_gas = balance - needed_gas
amount_to_send = amount_minus_gas

if amount_to_send < 0:
msg = f"amount to send, including transaction gas: {web3.fromWei(max(amount_to_send, needed_gas), 'ether')} is more than total available ETH ({web3.fromWei(balance, 'ether')})"
if len(hostnames) > 1:
# keep going but notify
self.emitter.echo(msg)
continue
else:
raise AttributeError(msg)
print (amount_to_send)
self.emitter.echo(f"Attempting to send {web3.fromWei(amount_to_send, 'ether')} ETH from {hostname} to {to} in 3 seconds.")
time.sleep(3)
result = self.send_eth(wallet, to, web3.fromWei(amount_to_send, 'ether'))
self.emitter.echo(f'Broadcast transaction: {result}')


class DigitalOceanConfigurator(BaseCloudNodeConfigurator):

Expand Down

0 comments on commit 34d2fa5

Please sign in to comment.