Skip to content

Commit cf815c9

Browse files
committed
move light node to webapi
1 parent 9218364 commit cf815c9

File tree

5 files changed

+126
-85
lines changed

5 files changed

+126
-85
lines changed

webapi/chain_routes.py

+81-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import copy
2+
import functools
13
import math
4+
from collections import OrderedDict
25
from decimal import Decimal
36
from io import BytesIO
47
from typing import Any, Optional
@@ -7,12 +10,13 @@
710
from starlette.requests import Request
811

912
from aleo_types import u64, DeployTransaction, ExecuteTransaction, FeeTransaction, RejectedDeploy, RejectedExecute, Fee, \
10-
FinalizeOperation, UpdateKeyValue, RemoveKeyValue, Value, Plaintext, Address
13+
FinalizeOperation, UpdateKeyValue, RemoveKeyValue, Value, Plaintext, Address, NodeType
1114
from aleo_types.cached import cached_get_mapping_id, cached_get_key_id
1215
from aleo_types.vm_block import AcceptedDeploy, AcceptedExecute
1316
from db import Database
17+
from node.light_node import LightNodeState
1418
from util import arc0137
15-
from webapi.utils import CJSONResponse, public_cache_seconds, function_definition
19+
from webapi.utils import CJSONResponse, public_cache_seconds, function_definition, get_relative_time
1620
from webui.classes import UIAddress
1721

1822

@@ -556,4 +560,78 @@ async def solution_route(request: Request):
556560
height = await db.get_solution_block_height(solution_id)
557561
if height is None:
558562
return CJSONResponse({"error": "Solution not found"}, status_code=404)
559-
return CJSONResponse(height)
563+
return CJSONResponse(height)
564+
565+
566+
async def nodes_route(request: Request):
567+
lns: LightNodeState = request.app.state.lns
568+
db: Database = request.app.state.db
569+
lns.cleanup()
570+
nodes = lns.states
571+
data: dict[str, dict[str, Any]] = {}
572+
for k, v in nodes.items():
573+
if k.startswith("127.0.0.1"):
574+
continue
575+
data[k] = copy.deepcopy(v)
576+
data[k]["last_ping"] = get_relative_time(v["last_ping"])
577+
validators = 0
578+
clients = 0
579+
provers = 0
580+
unknowns = 0
581+
connected = 0
582+
def sort_cmp(a: tuple[str, dict[str, Any]], b: tuple[str, dict[str, Any]]) -> int:
583+
# sort by: height, address, node type, ip address
584+
a_height = a[1].get("height", None)
585+
b_height = b[1].get("height", None)
586+
if a_height is not None and b_height is not None:
587+
return int(b_height) - int(a_height)
588+
if a_height is None and b_height is not None:
589+
return 1
590+
if a_height is not None and b_height is None:
591+
return -1
592+
a_address = a[1].get("address", None)
593+
b_address = b[1].get("address", None)
594+
if a_address is not None and b_address is not None:
595+
if a_address == b_address:
596+
return 0
597+
return 1 if a_address > b_address else -1
598+
if a_address is None and b_address is not None:
599+
return 1
600+
if a_address is not None and b_address is None:
601+
return -1
602+
a_type = a[1].get("node_type", None)
603+
b_type = b[1].get("node_type", None)
604+
if a_type is None and b_type is not None:
605+
return 1
606+
if a_type is not None and b_type is None:
607+
return -1
608+
if a_type == b_type:
609+
if a[0] == b[0]:
610+
return 0
611+
return 1 if a[0] > b[0] else -1
612+
return a_type.value - b_type.value
613+
614+
res: OrderedDict[str, dict[str, Any]] = OrderedDict(sorted(data.items(), key=functools.cmp_to_key(sort_cmp)))
615+
for node in res.values():
616+
node_type = node.get("node_type", None)
617+
if node_type is None:
618+
unknowns += 1
619+
elif node_type == NodeType.Validator:
620+
validators += 1
621+
elif node_type == NodeType.Client:
622+
clients += 1
623+
elif node_type == NodeType.Prover:
624+
provers += 1
625+
if node.get("direction", "") != "disconnected":
626+
connected += 1
627+
result: dict[str, Any] = {
628+
"nodes": [[k, v] for k, v in res.items()],
629+
"validators": validators,
630+
"clients": clients,
631+
"provers": provers,
632+
"unknowns": unknowns,
633+
"connected": connected,
634+
}
635+
result["resolved_addresses"] = await UIAddress.resolve_recursive_detached(result, db, {})
636+
637+
return CJSONResponse(result)

webapi/utils.py

+20
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,26 @@ class CJSONResponse(Response):
9898
def render(self, content: Any):
9999
return json.dumps(content, cls=CustomEncoder).encode("utf-8")
100100

101+
102+
def get_relative_time(timestamp: int):
103+
now = time.time()
104+
delta = int(now - timestamp)
105+
if delta == 0:
106+
return "just now"
107+
elif delta == 1:
108+
return "1 second ago"
109+
elif delta < 60:
110+
return f"{int(delta)} seconds ago"
111+
delta = delta // 60
112+
if delta == 1:
113+
return "1 minute ago"
114+
elif delta < 60:
115+
return f"{int(delta)} minutes ago"
116+
delta = delta // 60
117+
if delta == 1:
118+
return "1 hour ago"
119+
return f"{int(delta)} hours ago"
120+
101121
async def get_remote_height(session: aiohttp.ClientSession, rpc_root: str) -> str:
102122
try:
103123
async with session.get(f"{rpc_root}/{os.environ.get('NETWORK')}/block/height/latest") as resp:

webapi/webapi.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
from middleware.asgi_logger import AccessLoggerMiddleware
1717
from middleware.auth import AuthMiddleware
1818
from middleware.server_timing import ServerTimingMiddleware
19+
from node.light_node import LightNodeState
1920
from util.set_proc_title import set_proc_title
2021
from .address_routes import address_route, ans_route
2122
from .chain_routes import blocks_route, get_summary, recent_blocks_route, index_update_route, block_route, search_route, \
2223
transaction_route, \
23-
validators_route, transition_route, solution_route
24+
validators_route, transition_route, solution_route, nodes_route
2425
from .error_routes import bad_request, not_found, internal_error
2526
from .program_routes import programs_route, program_route
2627
from .utils import public_cache_seconds, out_of_sync_check, CJSONResponse
@@ -69,6 +70,7 @@ async def summary_route(request: Request):
6970
Route("/transition/{id}", transition_route),
7071
Route("/solution/{id}", solution_route),
7172
Route("/search", search_route),
73+
Route("/nodes", nodes_route),
7274

7375
Route("/programs", programs_route),
7476
Route("/program/{id}", program_route),
@@ -94,7 +96,8 @@ async def noop(_: Any): pass
9496
# noinspection PyUnresolvedReferences
9597
app.state.db = db
9698
# noinspection PyUnresolvedReferences
97-
# app.state.lns.connect(os.environ.get("P2P_NODE_HOST", "127.0.0.1"), int(os.environ.get("P2P_NODE_PORT", "4130")), None)
99+
app.state.lns.connect(os.environ.get("P2P_NODE_HOST", "127.0.0.1"), int(os.environ.get("P2P_NODE_PORT", "4133")), None)
100+
app.state.lns.start_listener()
98101
app.state.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
99102
set_proc_title("aleo-explorer: webapi")
100103

@@ -122,7 +125,7 @@ async def run():
122125
logging.getLogger("uvicorn.access").handlers = []
123126
server = UvicornServer(config=config)
124127
# noinspection PyUnresolvedReferences
125-
# app.state.lns = LightNodeState()
128+
app.state.lns = LightNodeState()
126129

127130
server.start()
128131
while True:

webui/chain_routes.py

+19-72
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import copy
2-
import functools
3-
from collections import OrderedDict
1+
import os
42
from io import BytesIO
53
from typing import Any, cast, Optional, ParamSpec, TypeVar, Callable, Awaitable
64

5+
import aiohttp
76
import aleo_explorer_rust
87
from starlette.exceptions import HTTPException
98
from starlette.requests import Request
@@ -20,13 +19,12 @@
2019
NodeType, FeeComponent, Fee, Option, Address
2120
from aleo_types.cached import cached_get_key_id, cached_get_mapping_id
2221
from db import Database
23-
from node.light_node import LightNodeState
2422
from util import arc0021
2523
from util.global_cache import get_program
2624
from util.typing_exc import Unreachable
2725
from .classes import UIAddress
2826
from .template import htmx_template
29-
from .utils import function_signature, out_of_sync_check, function_definition, get_relative_time
27+
from .utils import function_signature, out_of_sync_check, function_definition
3028

3129
try:
3230
from line_profiler import profile
@@ -966,73 +964,22 @@ async def solution_route(request: Request):
966964

967965
@htmx_template("nodes.jinja2")
968966
async def nodes_route(request: Request):
969-
lns: LightNodeState = request.app.state.lns
970-
lns.cleanup()
971-
nodes = lns.states
972-
data: dict[str, dict[str, Any]] = {}
973-
for k, v in nodes.items():
974-
if k.startswith("127.0.0.1"):
975-
continue
976-
data[k] = copy.deepcopy(v)
977-
data[k]["last_ping"] = get_relative_time(v["last_ping"])
978-
validators = 0
979-
clients = 0
980-
provers = 0
981-
unknowns = 0
982-
connected = 0
983-
def sort_cmp(a: tuple[str, dict[str, Any]], b: tuple[str, dict[str, Any]]) -> int:
984-
# sort by: height, address, node type, ip address
985-
a_height = a[1].get("height", None)
986-
b_height = b[1].get("height", None)
987-
if a_height is not None and b_height is not None:
988-
return int(b_height) - int(a_height)
989-
if a_height is None and b_height is not None:
990-
return 1
991-
if a_height is not None and b_height is None:
992-
return -1
993-
a_address = a[1].get("address", None)
994-
b_address = b[1].get("address", None)
995-
if a_address is not None and b_address is not None:
996-
if a_address == b_address:
997-
return 0
998-
return 1 if a_address > b_address else -1
999-
if a_address is None and b_address is not None:
1000-
return 1
1001-
if a_address is not None and b_address is None:
1002-
return -1
1003-
a_type = a[1].get("node_type", None)
1004-
b_type = b[1].get("node_type", None)
1005-
if a_type is None and b_type is not None:
1006-
return 1
1007-
if a_type is not None and b_type is None:
1008-
return -1
1009-
if a_type == b_type:
1010-
if a[0] == b[0]:
1011-
return 0
1012-
return 1 if a[0] > b[0] else -1
1013-
return a_type.value - b_type.value
1014-
1015-
res: OrderedDict[str, dict[str, Any]] = OrderedDict(sorted(data.items(), key=functools.cmp_to_key(sort_cmp)))
1016-
for node in res.values():
1017-
node_type = node.get("node_type", None)
1018-
if node_type is None:
1019-
unknowns += 1
1020-
elif node_type == NodeType.Validator:
1021-
validators += 1
1022-
elif node_type == NodeType.Client:
1023-
clients += 1
1024-
elif node_type == NodeType.Prover:
1025-
provers += 1
1026-
if node.get("direction", "") != "disconnected":
1027-
connected += 1
1028-
ctx = {
1029-
"nodes": res,
1030-
"validators": validators,
1031-
"clients": clients,
1032-
"provers": provers,
1033-
"unknowns": unknowns,
1034-
"connected": connected,
1035-
}
967+
host = os.environ.get("HOST", "127.0.0.1")
968+
port = int(os.environ.get("WEBAPI_PORT", 8002));
969+
session = aiohttp.ClientSession(headers={
970+
"Authorization": "Token " + os.environ.get("WEBAPI_TOKEN", "token"),
971+
})
972+
try:
973+
async with session.get(f"http://{host}:{port}/nodes") as response:
974+
ctx = await response.json()
975+
ctx["nodes"] = dict(ctx["nodes"])
976+
for node in ctx["nodes"].values():
977+
if (node_type := node.get("node_type", None)) is not None:
978+
node["node_type"] = NodeType(node_type)
979+
except:
980+
from traceback import print_exc
981+
print_exc()
982+
ctx = {}
1036983
return ctx, {'Cache-Control': 'no-cache'}
1037984

1038985

webui/webui.py

-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import asyncio
22
import logging
33
import multiprocessing
4-
import os
54

6-
import aiohttp
75
import uvicorn
86
from starlette.applications import Starlette
97
from starlette.middleware import Middleware
@@ -178,9 +176,6 @@ async def noop(_: Any): pass
178176
await db.connect()
179177
# noinspection PyUnresolvedReferences
180178
app.state.db = db
181-
# noinspection PyUnresolvedReferences
182-
app.state.lns.connect(os.environ.get("P2P_NODE_HOST", "127.0.0.1"), int(os.environ.get("P2P_NODE_PORT", "4133")), None)
183-
app.state.lns.start_listener()
184179
app.state.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
185180
set_proc_title("aleo-explorer: webui")
186181

@@ -209,8 +204,6 @@ async def run():
209204
)
210205
logging.getLogger("uvicorn.access").handlers = []
211206
server = UvicornServer(config=config)
212-
# noinspection PyUnresolvedReferences
213-
app.state.lns = LightNodeState()
214207

215208
server.start()
216209
while True:

0 commit comments

Comments
 (0)