Initial push
This commit is contained in:
156
hubzilla/addon/cry01/cry01_chain.php
Normal file
156
hubzilla/addon/cry01/cry01_chain.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* cry01_chain.php — On-chain read layer.
|
||||
* Reads Ğ1 balances from the local Duniter node RPC over Wireguard.
|
||||
* Reads and writes the local balance cache.
|
||||
* This is the only file in the project that makes outbound network calls.
|
||||
* If the node infrastructure changes, only this file changes.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BALANCE READ
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function cry01_get_balance($g1_pubkey) {
|
||||
// Returns the current Ğ1 balance for the given public key.
|
||||
// Queries the local Duniter node RPC. Returns null on failure.
|
||||
$config = cry01_load_config();
|
||||
$rpc = $config['g1_rpc_endpoint'] ?? '';
|
||||
if (!$rpc || !$g1_pubkey) return null;
|
||||
|
||||
$payload = json_encode([
|
||||
'jsonrpc' => '2.0',
|
||||
'id' => 1,
|
||||
'method' => 'state_getStorage',
|
||||
'params' => [cry01_balance_storage_key($g1_pubkey)],
|
||||
]);
|
||||
|
||||
$result = cry01_rpc_post($rpc, $payload);
|
||||
if ($result === null) return null;
|
||||
|
||||
return cry01_decode_balance($result);
|
||||
}
|
||||
|
||||
function cry01_balance_storage_key($g1_pubkey) {
|
||||
// Builds the Substrate storage key for the account balance of the given Ğ1 public key.
|
||||
// The key format is defined by the Duniter v2 runtime (pallet_balances storage map).
|
||||
// Placeholder — requires Substrate storage key encoding (xxHash + Blake2).
|
||||
// TODO: implement full storage key derivation or use duniter-specific RPC method.
|
||||
return '0x' . bin2hex($g1_pubkey);
|
||||
}
|
||||
|
||||
function cry01_decode_balance($rpc_result) {
|
||||
// Decodes the raw RPC result into a human-readable Ğ1 balance string.
|
||||
// Duniter v2 balances are stored as u64 encoded in SCALE codec.
|
||||
// Returns formatted balance string or null if decoding fails.
|
||||
$hex = $rpc_result['result'] ?? null;
|
||||
if (!$hex || $hex === '0x') return '0';
|
||||
// Remove 0x prefix and decode little-endian u64.
|
||||
$hex = ltrim($hex, '0x');
|
||||
$bytes = array_reverse(str_split(str_pad($hex, 16, '0', STR_PAD_LEFT), 2));
|
||||
$val = 0;
|
||||
foreach ($bytes as $b) {
|
||||
$val = ($val << 8) | hexdec($b);
|
||||
}
|
||||
// Duniter uses centimes (hundredths of Ğ1). Divide by 100.
|
||||
return number_format($val / 100, 2) . ' Ğ1';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RPC TRANSPORT
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function cry01_rpc_post($endpoint, $payload) {
|
||||
// POSTs a JSON-RPC request to the Duniter node. Returns decoded response or null.
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\n",
|
||||
'content' => $payload,
|
||||
'timeout' => 5,
|
||||
],
|
||||
]);
|
||||
$raw = @file_get_contents($endpoint, false, $context);
|
||||
if ($raw === false) {
|
||||
logger('cry01_chain: RPC call failed to ' . $endpoint);
|
||||
return null;
|
||||
}
|
||||
$data = json_decode($raw, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
logger('cry01_chain: RPC response is not valid JSON');
|
||||
return null;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CACHE
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function cry01_read_cache() {
|
||||
// Returns the balance cache array. Returns empty array if cache does not exist or is unreadable.
|
||||
$config = cry01_load_config();
|
||||
$path = $config['cache_file'] ?? '';
|
||||
if (!$path) return [];
|
||||
$raw = @file_get_contents($path);
|
||||
if ($raw === false) return [];
|
||||
$data = json_decode($raw, true);
|
||||
return (json_last_error() === JSON_ERROR_NONE) ? $data : [];
|
||||
}
|
||||
|
||||
function cry01_write_cache($data) {
|
||||
// Writes the balance cache to disk. Returns true on success, false on failure.
|
||||
$config = cry01_load_config();
|
||||
$path = $config['cache_file'] ?? '';
|
||||
if (!$path) return false;
|
||||
$tmp = $path . '.tmp';
|
||||
$ok = @file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT));
|
||||
if ($ok === false) {
|
||||
logger('cry01_chain: could not write cache to ' . $tmp);
|
||||
return false;
|
||||
}
|
||||
return @rename($tmp, $path);
|
||||
}
|
||||
|
||||
function cry01_cache_is_stale() {
|
||||
// Returns true if the cache is older than cache_max_age_seconds or does not exist.
|
||||
$config = cry01_load_config();
|
||||
$path = $config['cache_file'] ?? '';
|
||||
$max_age = intval($config['cache_max_age_seconds'] ?? 3600);
|
||||
if (!$path || !file_exists($path)) return true;
|
||||
return (time() - filemtime($path)) > $max_age;
|
||||
}
|
||||
|
||||
function cry01_refresh_balance_cache() {
|
||||
// Refreshes the balance cache by querying the Duniter node.
|
||||
// Writes updated cache to disk. Called by the manage route.
|
||||
$config = cry01_load_config();
|
||||
$pubkey = $config['operator_g1_pubkey'] ?? '';
|
||||
if (!$pubkey) {
|
||||
logger('cry01_chain: operator_g1_pubkey not set in config');
|
||||
return false;
|
||||
}
|
||||
$balance = cry01_get_balance($pubkey);
|
||||
if ($balance === null) return false;
|
||||
|
||||
$cache = cry01_read_cache();
|
||||
$cache['operator_balance'] = $balance;
|
||||
$cache['operator_g1_pubkey'] = $pubkey;
|
||||
$cache['refreshed_at'] = date('c');
|
||||
return cry01_write_cache($cache);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CONFIG LOADER
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function cry01_load_config() {
|
||||
// Loads cry01 config.json from the addon directory. Returns empty array on failure.
|
||||
static $cfg = null;
|
||||
if ($cfg !== null) return $cfg;
|
||||
$raw = @file_get_contents('addon/cry01/config.json');
|
||||
if ($raw === false) return [];
|
||||
$cfg = json_decode($raw, true);
|
||||
return (json_last_error() === JSON_ERROR_NONE) ? $cfg : [];
|
||||
}
|
||||
Reference in New Issue
Block a user