def get_lighthouse_installed_version():
    # Get the installed version for Lighthouse

    log.info('Getting Lighthouse installed version...')

    process_result = subprocess.run([LIGHTHOUSE_INSTALLED_PATH, '--version'],
                                    capture_output=True,
                                    text=True)

    if process_result.returncode != 0:
        log.error(f'Unexpected return code from Lighthouse. Return code: '
                  f'{process_result.returncode}')
        return UNKNOWN_VALUE

    process_output = process_result.stdout
    result = re.search(r'Lighthouse v?(?P<version>[^-]+)', process_output)
    if not result:
        log.error(
            f'Cannot parse {process_output} for Lighthouse installed version.')
        return UNKNOWN_VALUE

    installed_version = result.group('version')

    log.info(f'Lighthouse installed version is {installed_version}')

    return installed_version
def get_geth_available_version():
    # Get the available version for Geth, potentially for update

    log.info('Getting Geth available version...')

    subprocess.run(['apt', '-y', 'update'])
    process_result = subprocess.run(['apt-cache', 'policy', 'geth'],
                                    capture_output=True,
                                    text=True)

    if process_result.returncode != 0:
        log.error(f'Unexpected return code from apt-cache. Return code: '
                  f'{process_result.returncode}')
        return UNKNOWN_VALUE

    process_output = process_result.stdout
    result = re.search(r'Candidate: (?P<version>[^\+]+)', process_output)
    if not result:
        log.error(f'Cannot parse {process_output} for Geth candidate version.')
        return UNKNOWN_VALUE

    available_version = result.group('version')

    log.info(f'Geth available version is {available_version}')

    return available_version
def get_geth_installed_version():
    # Get the installed version for Geth

    log.info('Getting Geth installed version...')

    process_result = subprocess.run(['geth', 'version'],
                                    capture_output=True,
                                    text=True)

    if process_result.returncode != 0:
        log.error(f'Unexpected return code from geth. Return code: '
                  f'{process_result.returncode}')
        return UNKNOWN_VALUE

    process_output = process_result.stdout
    result = re.search(r'Version: (?P<version>[^-]+)', process_output)
    if not result:
        log.error(f'Cannot parse {process_output} for Geth installed version.')
        return UNKNOWN_VALUE

    installed_version = result.group('version')

    log.info(f'Geth installed version is {installed_version}')

    return installed_version
def upgrade_geth():
    # Upgrade the Geth client
    log.info('Upgrading Geth client...')

    subprocess.run(['apt', '-y', 'update'])
    subprocess.run(['apt', '-y', 'install', 'geth'])

    log.info('Restarting Geth service...')
    subprocess.run(['systemctl', 'restart', GETH_SYSTEMD_SERVICE_NAME])

    return True
def enter_maintenance(context):
    # Maintenance entry point for Ubuntu.
    # Maintenance is started after the wizard has completed.

    log.info(f'Entering maintenance mode. To be implemented.')

    if context is None:
        log.error('Missing context.')
        return False

    context = use_default_client(context)

    if context is None:
        log.error('Missing context.')
        return False

    return show_dashboard(context)
def get_geth_running_version():
    # Get the running version for Geth

    log.info('Getting Geth running version...')

    local_geth_jsonrpc_url = 'http://127.0.0.1:8545'
    request_json = {'jsonrpc': '2.0', 'method': 'web3_clientVersion', 'id': 67}
    headers = {'Content-Type': 'application/json'}
    try:
        response = httpx.post(local_geth_jsonrpc_url,
                              json=request_json,
                              headers=headers)
    except httpx.RequestError as exception:
        log.error(f'Cannot connect to Geth. Exception: {exception}')
        return UNKNOWN_VALUE

    if response.status_code != 200:
        log.error(
            f'Unexpected status code from {local_geth_jsonrpc_url}. Status code: '
            f'{response.status_code}')
        return UNKNOWN_VALUE

    response_json = response.json()

    if 'result' not in response_json:
        log.error(
            f'Unexpected JSON response from {local_geth_jsonrpc_url}. result not found.'
        )
        return UNKNOWN_VALUE

    version_agent = response_json['result']

    # Version agent should look like: Geth/v1.10.12-stable-6c4dc6c3/linux-amd64/go1.17.2
    result = re.search(
        r'Geth/v(?P<version>[^-/]+)(-(?P<stable>[^-/]+))?(-(?P<commit>[^-/]+))?',
        version_agent)
    if not result:
        log.error(f'Cannot parse {version_agent} for Geth version.')
        return UNKNOWN_VALUE

    running_version = result.group('version')

    log.info(f'Geth running version is {running_version}')

    return running_version
Esempio n. 7
0
def has_su_perm(platform):
    # Check to see if the script has super user (root, sudo or elevated) permissions

    if platform == PLATFORM_UBUNTU:
        has_su = os.geteuid() == 0
        if not has_su:
            from ethwizard.platforms.ubuntu.common import log
            log.warning(
                'Running without super user (root or sudo) permissions')
        return has_su

    elif platform == PLATFORM_WINDOWS10:
        perform_elevation = False
        try:
            if ctypes.windll.shell32.IsUserAnAdmin():
                return True
            else:
                perform_elevation = True
        except:
            perform_elevation = True

        if perform_elevation:
            from ethwizard.platforms.windows.common import log
            log.info('Performing privilege elevation')

            pythonpath_env = rf'$env:PYTHONPATH = "{";".join(sys.path)}";'
            encoding_change = (
                '$OutputEncoding = [console]::InputEncoding = '
                '[console]::OutputEncoding = New-Object System.Text.UTF8Encoding;'
            )
            target_command = (encoding_change + pythonpath_env +
                              sys.executable + ' ' + " ".join(sys.argv))
            encoded_command = base64.b64encode(
                codecs.encode(target_command, 'utf_16_le'))
            encoded_command = codecs.decode(encoded_command, 'ascii')
            args = f'-NoProfile -EncodedCommand {encoded_command}'
            ctypes.windll.shell32.ShellExecuteW(None, 'runas', 'powershell',
                                                args, None, 1)

        # End the unprivileged process
        sys.exit()

    return False
def get_lighthouse_latest_version():
    # Get the latest version for Lighthouse

    log.info('Getting Lighthouse latest version...')

    lighthouse_gh_release_url = GITHUB_REST_API_URL + LIGHTHOUSE_LATEST_RELEASE
    headers = {'Accept': GITHUB_API_VERSION}
    try:
        response = httpx.get(lighthouse_gh_release_url,
                             headers=headers,
                             follow_redirects=True)
    except httpx.RequestError as exception:
        log.error(
            f'Exception while getting the latest stable version for Lighthouse. {exception}'
        )
        return UNKNOWN_VALUE

    if response.status_code != 200:
        log.error(
            f'HTTP error while getting the latest stable version for Lighthouse. '
            f'Status code {response.status_code}')
        return UNKNOWN_VALUE

    release_json = response.json()

    if 'tag_name' not in release_json or not isinstance(
            release_json['tag_name'], str):
        log.error(
            f'Unable to find tag name in Github response while getting the latest stable '
            f'version for Lighthouse.')
        return UNKNOWN_VALUE

    tag_name = release_json['tag_name']
    result = re.search(r'v?(?P<version>.+)', tag_name)
    if not result:
        log.error(f'Cannot parse tag name {tag_name} for Lighthouse version.')
        return UNKNOWN_VALUE

    latest_version = result.group('version')

    log.info(f'Lighthouse latest version is {latest_version}')

    return latest_version
def get_lighthouse_running_version():
    # Get the running version for Lighthouse

    log.info('Getting Lighthouse running version...')

    local_lighthouse_bn_version_url = 'http://127.0.0.1:5052' + BN_VERSION_EP

    try:
        response = httpx.get(local_lighthouse_bn_version_url)
    except httpx.RequestError as exception:
        log.error(f'Cannot connect to Lighthouse. Exception: {exception}')
        return UNKNOWN_VALUE

    if response.status_code != 200:
        log.error(
            f'Unexpected status code from {local_lighthouse_bn_version_url}. Status code: '
            f'{response.status_code}')
        return UNKNOWN_VALUE

    response_json = response.json()

    if 'data' not in response_json or 'version' not in response_json['data']:
        log.error(
            f'Unexpected JSON response from {local_lighthouse_bn_version_url}. result not found.'
        )
        return UNKNOWN_VALUE

    version_agent = response_json['data']['version']

    # Version agent should look like: Lighthouse/v2.0.1-aaa5344/x86_64-linux
    result = re.search(
        r'Lighthouse/v(?P<version>[^-/]+)(-(?P<commit>[^-/]+))?',
        version_agent)
    if not result:
        log.error(f'Cannot parse {version_agent} for Lighthouse version.')
        return UNKNOWN_VALUE

    running_version = result.group('version')

    log.info(f'Lighthouse running version is {running_version}')

    return running_version
def upgrade_lighthouse():
    # Upgrade the Lighthouse client
    log.info('Upgrading Lighthouse client...')

    # Getting latest Lighthouse release files
    lighthouse_gh_release_url = GITHUB_REST_API_URL + LIGHTHOUSE_LATEST_RELEASE
    headers = {'Accept': GITHUB_API_VERSION}
    try:
        response = httpx.get(lighthouse_gh_release_url,
                             headers=headers,
                             follow_redirects=True)
    except httpx.RequestError as exception:
        log.error(
            f'Exception while downloading lighthouse binary. {exception}')
        return False

    if response.status_code != 200:
        log.error(f'HTTP error while downloading lighthouse binary. '
                  f'Status code {response.status_code}')
        return False

    release_json = response.json()

    if 'assets' not in release_json:
        log.error('No assets in Github release for lighthouse.')
        return False

    binary_asset = None
    signature_asset = None

    for asset in release_json['assets']:
        if 'name' not in asset:
            continue
        if 'browser_download_url' not in asset:
            continue

        file_name = asset['name']
        file_url = asset['browser_download_url']

        if file_name.endswith('x86_64-unknown-linux-gnu.tar.gz'):
            binary_asset = {'file_name': file_name, 'file_url': file_url}
        elif file_name.endswith('x86_64-unknown-linux-gnu.tar.gz.asc'):
            signature_asset = {'file_name': file_name, 'file_url': file_url}

    if binary_asset is None or signature_asset is None:
        log.error(
            'Could not find binary or signature asset in Github release.')
        return False

    # Downloading latest Lighthouse release files
    download_path = Path(Path.home(), 'ethwizard', 'downloads')
    download_path.mkdir(parents=True, exist_ok=True)

    binary_path = Path(download_path, binary_asset['file_name'])

    try:
        with open(binary_path, 'wb') as binary_file:
            with httpx.stream('GET',
                              binary_asset['file_url'],
                              follow_redirects=True) as http_stream:
                if http_stream.status_code != 200:
                    log.error(
                        f'HTTP error while downloading Lighthouse binary from Github. '
                        f'Status code {http_stream.status_code}')
                    return False
                for data in http_stream.iter_bytes():
                    binary_file.write(data)
    except httpx.RequestError as exception:
        log.error(
            f'Exception while downloading Lighthouse binary from Github. {exception}'
        )
        return False

    signature_path = Path(download_path, signature_asset['file_name'])

    try:
        with open(signature_path, 'wb') as signature_file:
            with httpx.stream('GET',
                              signature_asset['file_url'],
                              follow_redirects=True) as http_stream:
                if http_stream.status_code != 200:
                    log.error(
                        f'HTTP error while downloading Lighthouse signature from Github. '
                        f'Status code {http_stream.status_code}')
                    return False
                for data in http_stream.iter_bytes():
                    signature_file.write(data)
    except httpx.RequestError as exception:
        log.error(
            f'Exception while downloading Lighthouse signature from Github. {exception}'
        )
        return False

    # Test if gpg is already installed
    gpg_is_installed = False
    try:
        gpg_is_installed = is_package_installed('gpg')
    except Exception:
        return False

    if not gpg_is_installed:
        # Install gpg using APT
        subprocess.run(['apt', '-y', 'update'])
        subprocess.run(['apt', '-y', 'install', 'gpg'])

    # Verify PGP signature

    command_line = [
        'gpg', '--list-keys', '--with-colons', LIGHTHOUSE_PRIME_PGP_KEY_ID
    ]
    process_result = subprocess.run(command_line)
    pgp_key_found = process_result.returncode == 0

    if not pgp_key_found:
        retry_index = 0
        retry_count = 15

        key_server = PGP_KEY_SERVERS[retry_index % len(PGP_KEY_SERVERS)]
        log.info(f'Downloading Sigma Prime\'s PGP key from {key_server} ...')
        command_line = [
            'gpg', '--keyserver', key_server, '--recv-keys',
            LIGHTHOUSE_PRIME_PGP_KEY_ID
        ]
        process_result = subprocess.run(command_line)

        if process_result.returncode != 0:
            # GPG failed to download Sigma Prime's PGP key, let's wait and retry a few times
            while process_result.returncode != 0 and retry_index < retry_count:
                retry_index = retry_index + 1
                delay = 5
                log.warning(
                    f'GPG failed to download the PGP key. We will wait {delay} seconds '
                    f'and try again from a different server.')
                time.sleep(delay)

                key_server = PGP_KEY_SERVERS[retry_index %
                                             len(PGP_KEY_SERVERS)]
                log.info(
                    f'Downloading Sigma Prime\'s PGP key from {key_server} ...'
                )
                command_line = [
                    'gpg', '--keyserver', key_server, '--recv-keys',
                    LIGHTHOUSE_PRIME_PGP_KEY_ID
                ]

                process_result = subprocess.run(command_line)

        if process_result.returncode != 0:
            log.error(f'''
We failed to download the Sigma Prime's PGP key to verify the lighthouse
binary after {retry_count} retries.
''')
            return False

    process_result = subprocess.run(['gpg', '--verify', signature_path])
    if process_result.returncode != 0:
        log.error('The lighthouse binary signature is wrong. '
                  'We will stop here to protect you.')
        return False

    # Stopping Lighthouse services before updating the binary
    log.info('Stopping Lighthouse services...')
    subprocess.run([
        'systemctl', 'stop', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME,
        LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME
    ])

    # Extracting the Lighthouse binary archive
    log.info('Updating Lighthouse binary...')
    subprocess.run([
        'tar', 'xvf', binary_path, '--directory',
        LIGHTHOUSE_INSTALLED_DIRECTORY
    ])

    # Restarting Lighthouse services after updating the binary
    log.info('Starting Lighthouse services...')
    subprocess.run([
        'systemctl', 'start', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME,
        LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME
    ])

    # Remove download leftovers
    binary_path.unlink()
    signature_path.unlink()

    return True
def perform_maintenance(execution_client, execution_client_details,
                        consensus_client, consensus_client_details):
    # Perform all the maintenance tasks

    if execution_client == EXECUTION_CLIENT_GETH:
        # Geth maintenance tasks

        if execution_client_details[
                'next_step'] == MAINTENANCE_RESTART_SERVICE:
            log.info('Restarting Geth service...')

            subprocess.run(['systemctl', 'restart', GETH_SYSTEMD_SERVICE_NAME])

        elif execution_client_details[
                'next_step'] == MAINTENANCE_UPGRADE_CLIENT:
            if not upgrade_geth():
                log.error('We could not upgrade the Geth client.')
                return False

        elif execution_client_details[
                'next_step'] == MAINTENANCE_START_SERVICE:
            log.info('Starting Geth service...')

            subprocess.run(['systemctl', 'start', GETH_SYSTEMD_SERVICE_NAME])

        elif execution_client_details[
                'next_step'] == MAINTENANCE_REINSTALL_CLIENT:
            log.warn('TODO: Reinstalling client is to be implemented.')
    else:
        log.error(f'Unknown execution client {execution_client}.')
        return False

    if consensus_client == CONSENSUS_CLIENT_LIGHTHOUSE:
        # Lighthouse maintenance tasks

        if consensus_client_details[
                'next_step'] == MAINTENANCE_RESTART_SERVICE:
            log.info('Restarting Lighthouse services...')

            subprocess.run([
                'systemctl', 'restart', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME,
                LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME
            ])

        elif consensus_client_details[
                'next_step'] == MAINTENANCE_UPGRADE_CLIENT:
            if not upgrade_lighthouse():
                log.error('We could not upgrade the Lighthouse client.')
                return False

        elif consensus_client_details[
                'next_step'] == MAINTENANCE_START_SERVICE:
            log.info('Starting Lighthouse services...')

            subprocess.run([
                'systemctl', 'start', LIGHTHOUSE_BN_SYSTEMD_SERVICE_NAME,
                LIGHTHOUSE_VC_SYSTEMD_SERVICE_NAME
            ])

        elif consensus_client_details[
                'next_step'] == MAINTENANCE_REINSTALL_CLIENT:
            log.warn('TODO: Reinstalling client is to be implemented.')
    else:
        log.error(f'Unknown consensus client {consensus_client}.')
        return False

    return True