Wire new matrix placements in ~60s, not once a day
The live tail wired a new member into its parent's matrix slots with a guess-based fill and silently gave up on any fetchMember hiccup, deferring to the once-daily full snapshot — so a placement could take up to 24h to appear in the tree (observed: #119 qualified #38 on-chain but the dashboard card still showed 1/2 with an open slot). Now placement reads the parent's two slots straight from the contract (getMatrixChildren) — authoritative and order-independent — and any member that can't wire on its first pass is queued and retried every tick via drainPendingWire(). Trees reflect real placements within one ~60s poll. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,39 @@ function genBetween(fromId, toId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pull a parent's two matrix slots straight from contract storage — authoritative
|
||||
// and order-independent, so a new placement shows in the tree within one poll (~60s)
|
||||
// instead of waiting for the once-a-day full snapshot. Returns true on success.
|
||||
async function refreshChildren(pid) {
|
||||
if (!pid || !state.members[pid]) return false;
|
||||
try {
|
||||
const r = await ethCall(SEL.getMatrixChildren + encU(pid));
|
||||
state.members[pid].l = wInt(r, 0) || 0;
|
||||
state.members[pid].r = wInt(r, 1) || 0;
|
||||
return true;
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
// Queue a just-registered member whose placement couldn't be wired this pass
|
||||
// (e.g. a transient fetchMember failure) so the next tick retries it — never
|
||||
// silently deferring to the daily snapshot again.
|
||||
function enqueueWire(id) {
|
||||
if (!state.pendingWire) state.pendingWire = [];
|
||||
if (!state.pendingWire.includes(id)) state.pendingWire.push(id);
|
||||
}
|
||||
// Retry all queued placements; drop each one that successfully wires.
|
||||
async function drainPendingWire() {
|
||||
if (!state.pendingWire || !state.pendingWire.length) return;
|
||||
const still = [];
|
||||
for (const id of state.pendingWire) {
|
||||
try {
|
||||
const m = await fetchMember(id);
|
||||
if (m) { Object.assign(state.members[id], { uplineId: m.uplineId, level: m.level }); await refreshChildren(m.uplineId); }
|
||||
else still.push(id);
|
||||
} catch (e) { still.push(id); }
|
||||
}
|
||||
state.pendingWire = still;
|
||||
}
|
||||
|
||||
async function processRange(fromBlock, toBlock) {
|
||||
const logs = await rpc('eth_getLogs', [{
|
||||
address: CONTRACT, fromBlock: '0x' + fromBlock.toString(16), toBlock: '0x' + toBlock.toString(16),
|
||||
@@ -287,11 +320,12 @@ async function processRange(fromBlock, toBlock) {
|
||||
const m = await fetchMember(id);
|
||||
if (m) {
|
||||
Object.assign(state.members[id], { uplineId: m.uplineId, level: m.level });
|
||||
// wire the new member into the parent's matrix slots so trees update live
|
||||
const par = state.members[m.uplineId];
|
||||
if (par) { if (!par.l) par.l = id; else if (!par.r && par.l !== id) par.r = id; }
|
||||
}
|
||||
} catch (e) { /* picked up by next snapshot */ }
|
||||
// wire the new member in by reading the parent's slots straight from the
|
||||
// contract — authoritative and order-independent (fixes both l and r even
|
||||
// if an earlier sibling's wiring was missed)
|
||||
await refreshChildren(m.uplineId);
|
||||
} else { enqueueWire(id); }
|
||||
} catch (e) { enqueueWire(id); } // retry next tick, don't wait for the daily snapshot
|
||||
}
|
||||
state.payouts.sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
||||
if (state.payouts.length > KEEP_PAYOUTS) state.payouts = state.payouts.slice(-KEEP_PAYOUTS);
|
||||
@@ -320,6 +354,7 @@ async function tick() {
|
||||
from = to + 1;
|
||||
if (from <= latest) await new Promise(r => setTimeout(r, 150));
|
||||
}
|
||||
await drainPendingWire(); // reconcile any placements that couldn't wire on their first pass
|
||||
state.updatedAt = new Date().toISOString();
|
||||
saveState();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user