auto broadcast pending txns, show net info
This commit is contained in:
@@ -140,13 +140,26 @@ def get_network_info():
|
||||
mempool = rpc_call("getrawmempool")
|
||||
blockchain_info = rpc_call("getblockchaininfo")
|
||||
|
||||
# Format peer information
|
||||
peers = []
|
||||
for peer in peer_info:
|
||||
peers.append({
|
||||
"addr": peer.get("addr", ""),
|
||||
"services": peer.get("servicesnames", []),
|
||||
"relaytxes": peer.get("relaytxes", False),
|
||||
"synced_blocks": peer.get("synced_blocks", 0),
|
||||
"last_transaction": peer.get("last_transaction", 0),
|
||||
"version": peer.get("subver", ""),
|
||||
"pingtime": round(peer.get("pingtime", 0) * 1000, 1),
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"network": {
|
||||
"connections": network_info.get("connections", 0),
|
||||
"networkactive": network_info.get("networkactive", False),
|
||||
"relayfee": network_info.get("relayfee", 0),
|
||||
},
|
||||
"peers": len(peer_info),
|
||||
"peers": peers,
|
||||
"mempool_size": len(mempool),
|
||||
"blockchain": {
|
||||
"blocks": blockchain_info.get("blocks", 0),
|
||||
@@ -159,6 +172,34 @@ def get_network_info():
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
|
||||
|
||||
@app.route("/api/rebroadcast", methods=["POST"])
|
||||
@require_token
|
||||
def rebroadcast_pending():
|
||||
try:
|
||||
# Get all pending transactions
|
||||
txs = rpc_call("listtransactions", "*", 100, 0, True)
|
||||
pending = [tx for tx in txs if tx.get("confirmations", 0) == 0]
|
||||
|
||||
rebroadcasted = []
|
||||
for tx in pending:
|
||||
txid = tx.get("txid")
|
||||
if txid:
|
||||
try:
|
||||
# Get raw transaction
|
||||
tx_data = rpc_call("gettransaction", txid, True)
|
||||
raw_hex = tx_data.get("hex")
|
||||
if raw_hex:
|
||||
# Rebroadcast
|
||||
rpc_call("sendrawtransaction", raw_hex)
|
||||
rebroadcasted.append(txid)
|
||||
except Exception:
|
||||
pass # Already in mempool or other error
|
||||
|
||||
return jsonify({"rebroadcasted": rebroadcasted, "count": len(rebroadcasted)})
|
||||
except RuntimeError as exc:
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
@@ -280,15 +280,39 @@
|
||||
|
||||
function renderTx(tx) {
|
||||
const li = document.createElement('li');
|
||||
const amount = Number(tx.amount).toFixed(8);
|
||||
const amount = Math.abs(Number(tx.amount)).toFixed(8);
|
||||
const type = tx.category === 'send' ? 'Sent' : 'Received';
|
||||
const confirmations = tx.confirmations || 0;
|
||||
|
||||
let status, statusClass, tooltip = '';
|
||||
let ageInfo = '';
|
||||
|
||||
if (confirmations === 0) {
|
||||
status = 'Pending';
|
||||
statusClass = 'status-pending';
|
||||
tooltip = 'title="0 confirmations - waiting for network confirmation"';
|
||||
|
||||
// Calculate transaction age
|
||||
const txTime = tx.time || tx.timereceived;
|
||||
if (txTime) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const ageSeconds = now - txTime;
|
||||
const ageMinutes = Math.floor(ageSeconds / 60);
|
||||
const ageHours = Math.floor(ageMinutes / 60);
|
||||
|
||||
let ageText = '';
|
||||
if (ageHours > 0) {
|
||||
ageText = `${ageHours}h ${ageMinutes % 60}m ago`;
|
||||
} else if (ageMinutes > 0) {
|
||||
ageText = `${ageMinutes}m ago`;
|
||||
} else {
|
||||
ageText = `${ageSeconds}s ago`;
|
||||
}
|
||||
|
||||
ageInfo = `<div class="muted" style="font-size: 11px;">Age: ${ageText}</div>`;
|
||||
tooltip = `title="0 confirmations - waiting for network confirmation (${ageText})"`;
|
||||
} else {
|
||||
tooltip = 'title="0 confirmations - waiting for network confirmation"';
|
||||
}
|
||||
} else if (confirmations < 20) {
|
||||
status = `Immature (${confirmations}/20)`;
|
||||
statusClass = 'status-immature';
|
||||
@@ -302,6 +326,7 @@
|
||||
li.innerHTML = `
|
||||
<div><strong>${type}</strong> ${amount} RIN <span class="status-badge ${statusClass}" ${tooltip}>${status}</span></div>
|
||||
<div class="muted">${tx.time ? new Date(tx.time * 1000).toLocaleString() : ''}</div>
|
||||
${ageInfo}
|
||||
<div class="muted"><a href="https://explorer.rin.so/tx/${tx.txid}" target="_blank" class="tx-link">${tx.txid}</a></div>
|
||||
`;
|
||||
return li;
|
||||
@@ -403,55 +428,94 @@
|
||||
}
|
||||
|
||||
async function fetchNetworkInfo() {
|
||||
let res = await fetch('/api/network', { headers: authHeaders() });
|
||||
let data = await res.json();
|
||||
try {
|
||||
let res = await fetch('/api/network', { headers: authHeaders() });
|
||||
let data = await res.json();
|
||||
|
||||
// If token is invalid, refresh and retry
|
||||
if (data.error === 'invalid_token') {
|
||||
await refreshToken();
|
||||
res = await fetch('/api/network', { headers: authHeaders() });
|
||||
data = await res.json();
|
||||
// If token is invalid, refresh and retry
|
||||
if (data.error === 'invalid_token') {
|
||||
await refreshToken();
|
||||
res = await fetch('/api/network', { headers: authHeaders() });
|
||||
data = await res.json();
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const network = data.network;
|
||||
const blockchain = data.blockchain;
|
||||
|
||||
// Build peers list
|
||||
let peersHtml = '';
|
||||
if (data.peers && data.peers.length > 0) {
|
||||
peersHtml = '<div style="margin-top: 12px;"><div class="muted" style="font-size: 12px; margin-bottom: 8px;">Connected Peers</div><div style="max-height: 200px; overflow-y: auto;">';
|
||||
data.peers.forEach(peer => {
|
||||
const relayStatus = peer.relaytxes ? '✓ Relay' : '✗ No Relay';
|
||||
const syncStatus = peer.synced_blocks === blockchain.blocks ? '✓ Synced' : `${peer.synced_blocks}`;
|
||||
peersHtml += `
|
||||
<div style="padding: 6px; margin-bottom: 4px; background: rgba(9, 19, 30, 0.6); border-radius: 4px; font-size: 11px;">
|
||||
<div style="font-weight: 600;">${peer.addr}</div>
|
||||
<div class="muted">${relayStatus} | ${peer.version} | Ping: ${peer.pingtime}ms | Blocks: ${syncStatus}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
peersHtml += '</div></div>';
|
||||
}
|
||||
|
||||
networkInfo.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
||||
<div>
|
||||
<div class="muted">Network Connections</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${network.connections}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Mempool Size</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${data.mempool_size}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Block Height</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.blocks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Chain</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.chain}</div>
|
||||
</div>
|
||||
</div>
|
||||
${peersHtml}
|
||||
<div class="muted" style="font-size: 12px; margin-top: 12px;">
|
||||
Network Active: ${network.networkactive ? 'Yes' : 'No'} |
|
||||
Relay Fee: ${network.relayfee} RIN |
|
||||
Difficulty: ${Number(blockchain.difficulty).toFixed(2)}
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error('Network info fetch failed:', err);
|
||||
networkInfo.innerHTML = `<div class="muted">Error loading network info: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
|
||||
return;
|
||||
async function rebroadcastPending() {
|
||||
try {
|
||||
const res = await fetch('/api/rebroadcast', {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log(`Rebroadcasted ${data.count} pending transactions`);
|
||||
} catch (err) {
|
||||
console.error('Rebroadcast failed:', err);
|
||||
}
|
||||
|
||||
const network = data.network;
|
||||
const blockchain = data.blockchain;
|
||||
|
||||
networkInfo.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
||||
<div>
|
||||
<div class="muted">Network Connections</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${network.connections}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Mempool Size</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${data.mempool_size}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Block Height</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.blocks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Chain</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.chain}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted" style="font-size: 12px;">
|
||||
Network Active: ${network.networkactive ? 'Yes' : 'No'} |
|
||||
Relay Fee: ${network.relayfee} RIN |
|
||||
Difficulty: ${blockchain.difficulty.toFixed(2)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await Promise.all([fetchBalances(), fetchTransactions(), fetchNetworkInfo()]);
|
||||
}
|
||||
|
||||
// Auto-rebroadcast pending transactions every minute
|
||||
setInterval(rebroadcastPending, 60000);
|
||||
|
||||
document.getElementById('refreshButton').addEventListener('click', refresh);
|
||||
document.getElementById('generateButton').addEventListener('click', generateAddress);
|
||||
document.getElementById('sendButton').addEventListener('click', sendCoins);
|
||||
|
||||
Reference in New Issue
Block a user