Skip to content

Commit c36eaa5

Browse files
Copilotdfeen87
andcommitted
fix: correctness, CORS/auth handler, bare except, timeout, tx client bypass
Co-authored-by: dfeen87 <158860247+dfeen87@users.noreply.github.com>
1 parent 3c442cd commit c36eaa5

3 files changed

Lines changed: 92 additions & 46 deletions

File tree

api/l2_client.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,61 @@ async def get_latest_anchor(self) -> Optional[Dict[str, Any]]:
240240
self._last_check_time = time.time()
241241
return None
242242

243+
async def submit_transaction(self, tx_payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
244+
"""
245+
Submit a transaction to the C++ node's mempool.
246+
247+
Uses the shared httpx client so that SSL verification settings and
248+
timeout configuration are consistent with all other requests made
249+
through this client.
250+
251+
Args:
252+
tx_payload: Transaction fields expected by the C++ node
253+
(from_address, to_address, amount, data, tx_hash).
254+
255+
Returns:
256+
Parsed JSON object returned by the C++ endpoint on success
257+
(e.g. ``{"status": "accepted", "tx_hash": "...", "message": "..."}``),
258+
or None on any error or when the node is unavailable.
259+
"""
260+
if not await self._check_and_update_availability():
261+
return None
262+
263+
try:
264+
response = await self.client.post(
265+
f"{self.base_url}/api/transactions/submit",
266+
json=tx_payload,
267+
)
268+
response.raise_for_status()
269+
self._consecutive_failures = 0
270+
return response.json()
271+
except httpx.HTTPError as e:
272+
self._consecutive_failures += 1
273+
if self._consecutive_failures <= self._max_log_failures:
274+
logger.error(f"Failed to submit transaction to C++ node: {e}")
275+
elif self._consecutive_failures == self._max_log_failures + 1:
276+
logger.warning("Suppressing further C++ node connection errors (node appears unavailable)")
277+
self._node_available = False
278+
self._last_check_time = time.time()
279+
return None
280+
except Exception as e:
281+
self._consecutive_failures += 1
282+
if self._consecutive_failures <= self._max_log_failures:
283+
logger.error(f"Unexpected error submitting transaction: {e}")
284+
elif self._consecutive_failures == self._max_log_failures + 1:
285+
logger.warning("Suppressing further C++ node connection errors (node appears unavailable)")
286+
self._node_available = False
287+
self._last_check_time = time.time()
288+
return None
289+
243290
async def health_check(self) -> bool:
244291
"""Check if C++ node is healthy"""
245292
try:
246-
response = await self.client.get(f"{self.base_url}/api/health", timeout=2.0)
293+
response = await self.client.get(
294+
f"{self.base_url}/api/health", timeout=self._health_check_timeout
295+
)
247296
return response.status_code == 200
248-
except:
297+
except Exception:
249298
return False
250299

251300
async def close(self):

api/routers/transactions.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,26 +138,21 @@ async def submit_transaction(
138138
tx_hash = compute_transaction_hash(tx_data)
139139
tx_data["tx_hash"] = tx_hash
140140

141-
# Submit transaction to C++ node's mempool
141+
# Submit transaction to C++ node's mempool via the shared client so
142+
# that SSL verification settings and availability tracking are applied.
142143
client = get_ailee_client()
143144
try:
144-
# Send transaction to C++ mempool endpoint
145-
import httpx
146-
async with httpx.AsyncClient(timeout=5.0) as http_client:
147-
cpp_response = await http_client.post(
148-
f"{client.base_url}/api/transactions/submit",
149-
json={
150-
"from_address": tx.from_address,
151-
"to_address": tx.to_address,
152-
"amount": tx.amount,
153-
"data": tx.data or "",
154-
"tx_hash": tx_hash
155-
}
156-
)
157-
if cpp_response.status_code == 202:
158-
logger.info(f"Transaction submitted to C++ mempool: {tx_hash[:16]}...")
159-
else:
160-
logger.warning(f"C++ mempool returned status {cpp_response.status_code}")
145+
cpp_response = await client.submit_transaction({
146+
"from_address": tx.from_address,
147+
"to_address": tx.to_address,
148+
"amount": tx.amount,
149+
"data": tx.data or "",
150+
"tx_hash": tx_hash,
151+
})
152+
if cpp_response is not None:
153+
logger.info(f"Transaction submitted to C++ mempool: {tx_hash[:16]}...")
154+
else:
155+
logger.warning(f"C++ mempool unavailable; transaction stored locally only")
161156
except Exception as e:
162157
logger.warning(f"Failed to submit to C++ mempool (will retry): {e}")
163158

src/AILEEWebServer.cpp

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,39 +91,40 @@ class AILEEWebServer::Impl {
9191

9292
private:
9393
void setupRoutes() {
94-
// CORS middleware
95-
if (config_.enable_cors) {
96-
server_->set_pre_routing_handler([](const httplib::Request& req, httplib::Response& res) {
94+
// Combined pre-routing handler: CORS headers first, then API key auth.
95+
// Both are applied in one handler because httplib only keeps the last
96+
// set_pre_routing_handler registration; registering two handlers would
97+
// silently discard the first (CORS), leaving the browser unable to
98+
// reach the API when authentication is also enabled.
99+
server_->set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
100+
// Apply CORS headers on every response when CORS is enabled.
101+
if (config_.enable_cors) {
97102
res.set_header("Access-Control-Allow-Origin", "*");
98103
res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
99104
res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key");
100-
105+
101106
if (req.method == "OPTIONS") {
102107
res.status = 200;
103108
return httplib::Server::HandlerResponse::Handled;
104109
}
105-
return httplib::Server::HandlerResponse::Unhandled;
106-
});
107-
}
110+
}
108111

109-
// API key authentication middleware
110-
if (!config_.api_key.empty()) {
111-
server_->set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
112-
if (req.path.find("/api/") == 0) {
113-
auto api_key = req.get_header_value("X-API-Key");
114-
if (api_key != config_.api_key) {
115-
res.status = 401;
116-
json error_response = {
117-
{"error", "Unauthorized"},
118-
{"message", "Invalid or missing API key"}
119-
};
120-
res.set_content(error_response.dump(), "application/json");
121-
return httplib::Server::HandlerResponse::Handled;
122-
}
112+
// Enforce API key on /api/* routes when a key is configured.
113+
if (!config_.api_key.empty() && req.path.find("/api/") == 0) {
114+
auto api_key = req.get_header_value("X-API-Key");
115+
if (api_key != config_.api_key) {
116+
res.status = 401;
117+
json error_response = {
118+
{"error", "Unauthorized"},
119+
{"message", "Invalid or missing API key"}
120+
};
121+
res.set_content(error_response.dump(), "application/json");
122+
return httplib::Server::HandlerResponse::Handled;
123123
}
124-
return httplib::Server::HandlerResponse::Unhandled;
125-
});
126-
}
124+
}
125+
126+
return httplib::Server::HandlerResponse::Unhandled;
127+
});
127128

128129
// Serve the web dashboard
129130
server_->Get("/", [](const httplib::Request&, httplib::Response& res) {
@@ -431,8 +432,9 @@ AILEEWebServer::AILEEWebServer(const WebServerConfig& config)
431432
AILEEWebServer::~AILEEWebServer() = default;
432433

433434
bool AILEEWebServer::start() {
434-
running_ = true;
435-
return pImpl->start();
435+
bool started = pImpl->start();
436+
if (started) running_ = true;
437+
return started;
436438
}
437439

438440
void AILEEWebServer::stop() {

0 commit comments

Comments
 (0)