getInfo(); * $block = $rpc->getBlockByHeight(12345); */ class DaemonRpc implements \Mod\Chain\Contracts\ChainDaemon, \Mod\Chain\Contracts\HealthCheckable { private string $endpoint; private int $cacheTtl; public function __construct() { $this->endpoint = config('chain.daemon_rpc', 'http://127.0.0.1:46941/json_rpc'); $this->cacheTtl = config('chain.cache_ttl', 10); } /** * $info = $rpc->getInfo(); * echo $info['height']; */ public function getInfo(): array { return Cache::remember('chain.info', $this->cacheTtl, function () { return $this->call('getinfo'); }); } /** * $header = $rpc->getBlockByHeight(12345); */ public function getBlockByHeight(int $height): array { // Blocks are immutable — cache for 1 hour return Cache::remember("chain.block.{$height}", 3600, function () use ($height) { return $this->call('getblockheaderbyheight', ['height' => $height]); }); } /** * $header = $rpc->getBlockByHash('abc123...'); */ public function getBlockByHash(string $hash): array { return $this->call('getblockheaderbyhash', ['hash' => $hash]); } /** * $header = $rpc->getLastBlockHeader(); */ public function getLastBlockHeader(): array { return Cache::remember('chain.lastblock', $this->cacheTtl, function () { return $this->call('getlastblockheader'); }); } /** * $aliases = $rpc->getAllAliases(); */ public function getAllAliases(): array { return Cache::remember('chain.aliases', 60, function () { return $this->call('get_all_alias_details'); }); } /** * $alias = $rpc->getAliasByName('charon'); */ public function getAliasByName(string $name): ?array { $result = $this->call('get_alias_details', ['alias' => $name]); if (($result['status'] ?? '') === 'NOT_FOUND' || empty($result['alias_details']['address'] ?? '')) { return null; } return $result['alias_details'] ?? null; } /** * $count = $rpc->getBlockCount(); */ public function getBlockCount(): int { $result = $this->call('getblockcount'); return $result['count'] ?? 0; } /** * $tx = $rpc->getTransaction('abc123...'); */ public function getTransaction(string $hash): array { return $this->call('get_tx_details', ['tx_hash' => $hash]); } /** * Raw JSON-RPC call. * * $result = $rpc->call('getinfo'); */ public function call(string $method, array $params = []): array { $payload = [ 'jsonrpc' => '2.0', 'id' => '0', 'method' => $method, ]; if ($params) { $payload['params'] = $params; } try { $response = Http::timeout(5) ->accept('application/json') ->post($this->endpoint, $payload); if ($response->failed()) { return ['error' => 'Daemon unreachable', '_offline' => true]; } $data = $response->json(); $result = $data["result"] ?? $data["error"] ?? []; // Only cache successful responses as stale fallback if (isset($data['result'])) { Cache::put("chain.stale.{$method}", $result, 3600); } return $result; } catch (\Throwable $e) { // Return stale cached data if daemon is down $stale = Cache::get("chain.stale.{$method}"); if ($stale) { $stale['_stale'] = true; return $stale; } return ['error' => 'Daemon unreachable', '_offline' => true]; } } public function healthCheck(): array { $info = $this->getInfo(); if (isset($info['_offline'])) { return ['status' => 'unhealthy', 'detail' => 'Unreachable']; } if (isset($info['_stale'])) { return [ 'status' => 'degraded', 'detail' => 'Stale data — height ' . number_format($info['height'] ?? 0), 'stale' => true, ]; } return [ 'status' => 'healthy', 'detail' => 'Height ' . number_format($info['height'] ?? 0) . ' — ' . ($info['status'] ?? 'OK'), ]; } }