diff --git a/depin/services/roles/ethereum/defaults/main.yml b/depin/services/roles/ethereum/defaults/main.yml new file mode 100644 index 00000000..f8d87e5e --- /dev/null +++ b/depin/services/roles/ethereum/defaults/main.yml @@ -0,0 +1,20 @@ +--- + +ethereum_cmd: "{{ depin_cmd | default('install') }}" +ethereum_besu_version: 24.9.1 +ethereum_teku_version: 24.8.0 +ethereum_custom_metrics_port: 33006 +ethereum_besu_download_url: "https://github.com/hyperledger/besu/releases/download/24.9.1/besu-24.9.1.zip" +ethereum_teku_download_url: "https://artifacts.consensys.net/public/teku/raw/names/teku.zip/versions/24.8.0/teku-24.8.0.zip" + +# Node configuration + +ethereum_network: holesky +ethereum_besu_rpc_http_host: 0.0.0.0 +ethereum_public_ip: "" +ethereum_besu_rpc_ws_host: 0.0.0.0 +ethereum_besu_metrics_host: 0.0.0.0 +ethereum_besu_metrics_port: 9545 + +ethereum_teku_ee_endpoint: http://localhost:8551 +ethereum_teku_checkpoint_sync_url: https://checkpoint-sync.holesky.ethpandaops.io diff --git a/depin/services/roles/ethereum/tasks/commands/install.yml b/depin/services/roles/ethereum/tasks/commands/install.yml new file mode 100644 index 00000000..c12a6510 --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/install.yml @@ -0,0 +1,162 @@ +--- +- name: Install dependencies + become: true + ansible.builtin.apt: + name: + - openjdk-21-jdk + state: present + update_cache: true + +- name: Create program folder for besu + become: true + ansible.builtin.file: + state: directory + owner: root + group: root + mode: '0755' + dest: /opt/ethereum_besu/{{ ethereum_besu_version }} + +- name: Create program folder for teku + become: true + ansible.builtin.file: + state: directory + owner: root + group: root + mode: '0755' + dest: /opt/ethereum_teku/{{ ethereum_teku_version }} + +- name: Download besu binary to tmp + become: true + ansible.builtin.unarchive: + remote_src: true + src: "{{ ethereum_besu_download_url }}" + dest: /tmp + failed_when: false + +- name: Download teku binary to tmp + become: true + ansible.builtin.unarchive: + remote_src: true + src: "{{ ethereum_teku_download_url }}" + dest: /tmp + failed_when: false + +- name: Find besu binary + become: true + ansible.builtin.find: + paths: + - /tmp + - "{{ ethereum_besu_download_path | default('/tmp') }}" + patterns: 'besu$' + use_regex: true + get_checksum: true + recurse: true + register: _download_besu + +- name: Find teku binary + become: true + ansible.builtin.find: + paths: + - /tmp + - "{{ ethereum_teku_download_path | default('/tmp') }}" + patterns: 'teku$' + use_regex: true + get_checksum: true + recurse: true + register: _download_teku + +- name: Generate the shared secret + become: true + ansible.builtin.shell: openssl rand -hex 32 | tr -d "\n" > /tmp/jwtsecret.hex + +- name: Copy the shared secret to execution client + become: true + ansible.builtin.copy: + remote_src: true + src: /tmp/jwtsecret.hex + dest: /opt/ethereum_besu/{{ ethereum_besu_version }}/jwtsecret.hex + +- name: Copy the shared secret to consensus client + become: true + ansible.builtin.copy: + remote_src: true + src: /tmp/jwtsecret.hex + dest: /opt/ethereum_teku/{{ ethereum_teku_version }}/jwtsecret.hex + +- name: Get public IP + uri: + url: https://ipecho.net/plain + method: GET + return_content: yes + status_code: 200 + register: public_ip + ignore_errors: True + +- name: Set the public ip + set_fact: + ethereum_public_ip: "{{ public_ip.content }}" + when: public_ip.status == 200 + +- name: Install execution client + when: _download_besu['files'] | length > 0 + become: true + vars: + _files: | + {{ + _download_besu['files'] | + selectattr('checksum', 'equalto', ethereum_besu_checksum | default('')) + }} + block: + - name: Copy required files + ansible.builtin.copy: + remote_src: true + src: /tmp/besu-{{ ethereum_besu_version }}/ + dest: /opt/ethereum_besu/{{ ethereum_besu_version }} + mode: '0755' + + - name: Copy besu config file + become: true + ansible.builtin.template: + src: templates/config_besu.toml.j2 + dest: /opt/ethereum_besu/{{ ethereum_besu_version }}/config_besu.toml + mode: '0777' + + - name: Create besu systemd file + ansible.builtin.template: + src: templates/systemd.besu_service.j2 + dest: /etc/systemd/system/ethereum_besu.service + mode: '0644' + +- name: Install consensus client + when: _download_teku['files'] | length > 0 + become: true + vars: + _files: | + {{ + _download_teku['files'] | + selectattr('checksum', 'equalto', ethereum_teku_checksum | default('')) + }} + block: + - name: Copy required files + ansible.builtin.copy: + remote_src: true + src: /tmp/teku-{{ ethereum_teku_version }}/ + dest: /opt/ethereum_teku/{{ ethereum_teku_version }} + mode: '0755' + + - name: Copy teku config file + become: true + ansible.builtin.template: + src: templates/config_teku.yaml.j2 + dest: /opt/ethereum_teku/{{ ethereum_teku_version }}/config_teku.yaml + mode: '0777' + + - name: Create teku systemd file + ansible.builtin.template: + src: templates/systemd.teku_service.j2 + dest: /etc/systemd/system/ethereum_teku.service + mode: '0644' + +- name: Start node + ansible.builtin.include_tasks: + file: commands/start.yml \ No newline at end of file diff --git a/depin/services/roles/ethereum/tasks/commands/reset.yml b/depin/services/roles/ethereum/tasks/commands/reset.yml new file mode 100644 index 00000000..d967402b --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/reset.yml @@ -0,0 +1,7 @@ +--- +- name: Uninstall + ansible.builtin.include_tasks: commands/uninstall.yml + +- name: Install + ansible.builtin.include_tasks: commands/install.yml + diff --git a/depin/services/roles/ethereum/tasks/commands/restart.yml b/depin/services/roles/ethereum/tasks/commands/restart.yml new file mode 100644 index 00000000..8705cfba --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/restart.yml @@ -0,0 +1,7 @@ +--- +## stop and start service +- name: Stop + ansible.builtin.include_tasks: commands/stop.yml + +- name: Start + ansible.builtin.include_tasks: commands/start.yml diff --git a/depin/services/roles/ethereum/tasks/commands/start.yml b/depin/services/roles/ethereum/tasks/commands/start.yml new file mode 100644 index 00000000..80e0373a --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/start.yml @@ -0,0 +1,62 @@ +--- + # Run the ethereum_besu +- name: Start execution client + become: true + ansible.builtin.service: + name: ethereum_besu + enabled: true + daemon_reload: true + state: started + +- name: Pause for 1 minute for ensure execution client + ansible.builtin.pause: + minutes: 1 + +- name: Start consensus client + become: true + ansible.builtin.service: + name: ethereum_teku + enabled: true + daemon_reload: true + state: started + +- name: Assert + when: "'molecule' in groups" + block: + - name: Get service info + become: true + ansible.builtin.systemd: + name: "ethereum_besu" + register: _ethereum_besu + + - debug: + var: _ethereum_besu.status.ActiveState + + - name: Ensure besu is active state + ansible.builtin.assert: + that: + - _ethereum_besu['status']['ActiveState']=='active' + quiet: true + + - name: Set pid + ansible.builtin.set_fact: + _presearch_pid: "{{ _ethereum_besu['status']['ExecMainPID'] }}" + + - name: Get service info + become: true + ansible.builtin.systemd: + name: "ethereum_teku" + register: _ethereum_teku + + - debug: + var: _ethereum_teku.status.ActiveState + + - name: Ensure teku is active state + ansible.builtin.assert: + that: + - _ethereum_teku['status']['ActiveState']=='active' + quiet: true + + - name: Set pid + ansible.builtin.set_fact: + _presearch_pid: "{{ _ethereum_teku['status']['ExecMainPID'] }}" \ No newline at end of file diff --git a/depin/services/roles/ethereum/tasks/commands/stop.yml b/depin/services/roles/ethereum/tasks/commands/stop.yml new file mode 100644 index 00000000..9f46dcf9 --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/stop.yml @@ -0,0 +1,48 @@ +--- +# Stop ethereum_besu +- name: Stop ethereum_besu + become: true + ansible.builtin.systemd: + name: ethereum_besu + state: stopped + +- name: Stop ethereum_besu + become: true + ansible.builtin.systemd: + name: ethereum_teku + state: stopped + +- name: Assert + when: "'molecule' in groups" + block: + - name: Get service info + become: true + ansible.builtin.systemd: + name: "ethereum_besu" + register: _ethereum_besu + + - name: Debug service status + debug: + var: _ethereum_besu.status + + - name: Ensure besu is inactive state + ansible.builtin.assert: + that: + - _ethereum_besu['status']['ActiveState']=='inactive' + quiet: true + + - name: Get teku service info + become: true + ansible.builtin.systemd: + name: "ethereum_teku" + register: ethereum_teku + + - name: Debug teku service status + debug: + var: ethereum_teku.status + + - name: Ensure teku is inactive state + ansible.builtin.assert: + that: + - ethereum_teku['status']['ActiveState']=='inactive' + quiet: true \ No newline at end of file diff --git a/depin/services/roles/ethereum/tasks/commands/uninstall.yml b/depin/services/roles/ethereum/tasks/commands/uninstall.yml new file mode 100644 index 00000000..299c9da1 --- /dev/null +++ b/depin/services/roles/ethereum/tasks/commands/uninstall.yml @@ -0,0 +1,49 @@ +--- +# Stop the service before uninstalling +- name: Stop + ansible.builtin.include_tasks: commands/stop.yml + +# Remove the binary file for ethereum_besu +- name: Remove systemd service for ethereum_besu + become: true + file: + path: /opt/ethereum_besu + state: absent + +- name: Remove systemd service for ethereum_besu + become: true + file: + path: /opt/ethereum_teku + state: absent + +# Remove the systemd service for ethereum_besu +- name: Delete content & directory + become: true + ansible.builtin.file: + state: absent + path: "{{ item }}" + loop: + - /etc/systemd/system/ethereum_besu.service + - /opt/ethereum/{{ ethereum_besu_version }} + loop_control: + loop_var: item + +- name: Verify that the ethereum service file is removed + ansible.builtin.stat: + path: /etc/systemd/system/ethereum.service + register: ethereum_service_file + +- name: Assert that the ethereumGo service file does not exist + ansible.builtin.assert: + that: + - not ethereum_service_file.stat.exists + +- name: Verify that the program directory is removed + ansible.builtin.stat: + path: /opt/ethereum/{{ ethereum_besu_version }} + register: ethereum_directory + +- name: Assert that the program directory does not exist + ansible.builtin.assert: + that: + - not ethereum_directory.stat.exists \ No newline at end of file diff --git a/depin/services/roles/ethereum/tasks/main.yml b/depin/services/roles/ethereum/tasks/main.yml new file mode 100644 index 00000000..f299493b --- /dev/null +++ b/depin/services/roles/ethereum/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- name: Ethereum {{ ethereum_cmd }} + vars: + cmd_file: "{{ role_path }}/tasks/commands/{{ ethereum_cmd }}.yml" + when: cmd_file is file + ansible.builtin.include_tasks: + file: "{{ cmd_file }}" \ No newline at end of file diff --git a/depin/services/roles/ethereum/templates/collector.py.j2 b/depin/services/roles/ethereum/templates/collector.py.j2 new file mode 100644 index 00000000..9629c206 --- /dev/null +++ b/depin/services/roles/ethereum/templates/collector.py.j2 @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +import os +import socket +import time +import os +import time +import subprocess +from subprocess import check_output +import json +import re + +from prometheus_client import start_http_server, REGISTRY, GC_COLLECTOR, PLATFORM_COLLECTOR, PROCESS_COLLECTOR +from prometheus_client.core import GaugeMetricFamily +from prometheus_client.registry import Collector + +try: + from utils_lxd import lxd_get +except ImportError as imp_exc: + LXD_UTILS_IMPORT_ERROR = imp_exc +else: + LXD_UTILS_IMPORT_ERROR = None + +class MetricsCollector(Collector): + """Collector for Gala Node information""" + def total_uptime_count(self, besu_service_date): + total_uptime=0 + for x in besu_service_date: + state_uptime_list = besu_service_date[x].split(":") + hours=0 + if len(state_uptime_list) <= 2: + minutes = int(state_uptime_list[0]) + seconds = int(state_uptime_list[1]) + else: + hours = int(state_uptime_list[0]) + minutes = int(state_uptime_list[1]) + seconds = int(state_uptime_list[2]) + total_uptime=total_uptime+seconds+minutes*60+hours*3600 + return total_uptime + + def collect(self): + result = [] + service_metrics_collected = 1 + + service_name = "ethereum_besu" + try: + p = subprocess.Popen(["systemctl", "status", service_name], stdout=subprocess.PIPE) + (output, err) = p.communicate() + output = output.decode('utf-8') + print() + y= re.search("Active: .*active", output) + status= y.group().split(" ") + if status[1] == 'active': + state = 1 + else: + print("exit") + state = 0 + sys.exit(1) + + x = re.search("Main PID: [0-9]+", output) + pid= x.group().split(" ") + pid_id=pid[2] + proc = subprocess.Popen( + ["ps", "-eo", "pid,comm,lstart,etime,time"], stdout=subprocess.PIPE + ) + proc.wait() + services_pids = proc.stdout.readlines() + + try: + f = open("ethereum_besu_total_uptime.txt", "r") + besu_service_date = json.loads(f.read().replace("'", '"')) + os.remove("ethereum_besu_total_uptime.txt") + except: + print("No file") + besu_service_date={} + + for pid_decription in services_pids: + pid_decription = pid_decription.decode("utf-8") + p = pid_decription.split()[0] + if p == pid_id: + print(pid_decription) + month = pid_decription.split()[3] + day = pid_decription.split()[4] + time_str = pid_decription.split()[5] + years = pid_decription.split()[6] + run_uptime = pid_decription.split()[7] + state_uptime_dic=run_uptime.split(":") + if len(state_uptime_dic)<=2: + hours=0 + minutes = int(state_uptime_dic[0]) + seconds = int(state_uptime_dic[1]) + else: + hours = int(state_uptime_dic[0]) + minutes = int(state_uptime_dic[1]) + seconds = int(state_uptime_dic[2]) + state_uptime=seconds+minutes*60+hours*3600 + date_str = month + "-" + day + "-" + years + " " + time_str + besu_service_date[date_str] = run_uptime + total_uptime=self.total_uptime_count(besu_service_date) + f = open("ethereum_besu_total_uptime.txt", "w") + f.write(str(besu_service_date)) + f.close() + + except: + + print("Service: "+ service_name + " wasn't found") + state = 0 + state_uptime =0 + try: + if os.path.isfile('./ethereum_besu_total_uptime.txt'): + f = open("ethereum_besu_total_uptime.txt", "r") + besu_service_date = json.loads(f.read().replace("'", '"')) + total_uptime=self.total_uptime_count(besu_service_date) + print(total_uptime) + else: + print("No file") + total_uptime=0 + except: + print("The service has never been launched") + total_uptime=0 + service_metrics_collected = 0 + besu_service_date + + + #Mark for tests + device = 'molecule' + service = '{{ _name }}' + hostname = socket.gethostname() + _labels = ['device', 'instance', 'service'] + _label_values = [device, hostname, service] + + g1 = GaugeMetricFamily('state','current state', labels=_labels) + g1.add_metric(_label_values, value=state) + + g2 = GaugeMetricFamily('current_state_uptime','seconds in current state', labels=_labels) + g2.add_metric(_label_values, value=state_uptime) + + g3 = GaugeMetricFamily('total_uptime','seconds in last 24 hours', labels=_labels) + g3.add_metric(_label_values, value=total_uptime) + + result.extend([g1, g2, g3]) + + success = GaugeMetricFamily('service_metrics_collected','service metrics collected successfully', labels=_labels) + success.add_metric(_label_values, value=service_metrics_collected) + + result.extend([success]) + return result + +if __name__ == "__main__": + REGISTRY.unregister(GC_COLLECTOR) + REGISTRY.unregister(PLATFORM_COLLECTOR) + REGISTRY.unregister(PROCESS_COLLECTOR) + REGISTRY.register(MetricsCollector()) + + start_http_server({{ vars[role_name + '_custom_metrics_port'] | int }}) + while True: + time.sleep(30) \ No newline at end of file diff --git a/depin/services/roles/ethereum/templates/config_besu.toml.j2 b/depin/services/roles/ethereum/templates/config_besu.toml.j2 new file mode 100644 index 00000000..6b7d03e0 --- /dev/null +++ b/depin/services/roles/ethereum/templates/config_besu.toml.j2 @@ -0,0 +1,13 @@ +network="{{ ethereum_network }}" +rpc-http-enabled=true +rpc-http-host="{{ ethereum_besu_rpc_http_host }}" +rpc-http-cors-origins=["*"] +rpc-ws-enabled=true +rpc-ws-host="{{ ethereum_besu_rpc_ws_host }}" +host-allowlist=["*"] +engine-host-allowlist=["*"] +engine-rpc-enabled=true +engine-jwt-secret="/opt/ethereum_besu/{{ ethereum_besu_version }}/jwtsecret.hex" +metrics-enabled=true +metrics-host="{{ ethereum_besu_metrics_host }}" +metrics-port={{ ethereum_besu_metrics_port }} \ No newline at end of file diff --git a/depin/services/roles/ethereum/templates/config_teku.yaml.j2 b/depin/services/roles/ethereum/templates/config_teku.yaml.j2 new file mode 100644 index 00000000..5872ae81 --- /dev/null +++ b/depin/services/roles/ethereum/templates/config_teku.yaml.j2 @@ -0,0 +1,10 @@ +network: {{ ethereum_network }} +ee-endpoint: "{{ ethereum_teku_ee_endpoint }}" +ee-jwt-secret-file: "/opt/ethereum_teku/{{ ethereum_teku_version }}/jwtsecret.hex" +metrics-enabled: true +rest-api-enabled: true +checkpoint-sync-url: "{{ ethereum_teku_checkpoint_sync_url }}" +p2p-advertised-ip: "{{ ethereum_public_ip }}" +metrics-enabled: true +metrics-host-allowlist: ["*"] +metrics-port: 6174 \ No newline at end of file diff --git a/depin/services/roles/ethereum/templates/systemd.besu_service.j2 b/depin/services/roles/ethereum/templates/systemd.besu_service.j2 new file mode 100644 index 00000000..9f3de0e8 --- /dev/null +++ b/depin/services/roles/ethereum/templates/systemd.besu_service.j2 @@ -0,0 +1,17 @@ +[Unit] +Description=ethereum_besu service +After=network.target + +[Service] +User=root +Group=root +Type=simple +WorkingDirectory=/opt/ethereum_besu/{{ ethereum_besu_version }} +ExecStart=/opt/ethereum_besu/{{ ethereum_besu_version }}/bin/besu --config-file=/opt/ethereum_besu/{{ ethereum_besu_version }}/config_besu.toml +SuccessExitStatus=143 +Restart=on-failure +RestartSec=20s +PIDFile=/var/run/ethereum_besu.pid + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/depin/services/roles/ethereum/templates/systemd.teku_service.j2 b/depin/services/roles/ethereum/templates/systemd.teku_service.j2 new file mode 100644 index 00000000..533b2228 --- /dev/null +++ b/depin/services/roles/ethereum/templates/systemd.teku_service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=ethereum_teku service +After=network.target + +[Service] +User=root +Group=root +Type=simple +WorkingDirectory=/opt/ethereum_teku/{{ ethereum_teku_version }} +ExecStart=/opt/ethereum_teku/{{ ethereum_teku_version }}/bin/teku --config-file=/opt/ethereum_teku/{{ ethereum_teku_version }}/config_teku.yaml +RestartForceExitStatus=1 +RestartPreventExitStatus=2 +Restart=on-failure +RestartSec=20s + + +[Install] +WantedBy=multi-user.target \ No newline at end of file