Skip to content

Instantly share code, notes, and snippets.

@homakov
Last active July 13, 2025 13:21
Show Gist options
  • Save homakov/0678179ec5bbb123cf77befd26bb1cb8 to your computer and use it in GitHub Desktop.
Save homakov/0678179ec5bbb123cf77befd26bb1cb8 to your computer and use it in GitHub Desktop.
// === TYPES ===
declare const console: any;
let DEBUG = true;
interface ServerInput {
serverTxs: ServerTx[];
entityInputs: EntityInput[];
}
interface ServerTx {
type: 'importReplica';
entityId: string;
signerId: string;
data: {
validators: string[];
threshold: number;
isProposer: boolean;
};
}
interface EntityInput {
entityId: string;
signerId: string;
entityTxs?: EntityTx[];
precommits?: string[];
proposedFrame?: string;
}
interface EntityTx {
type: string;
data: any;
}
// === STATE ===
interface EntityState {
height: number;
nonces: Map<string, number>;
messages: string[];
validators: string[];
threshold: number;
}
interface ProposedEntityFrame {
height: number;
txs: EntityTx[];
hash: string;
newState: EntityState;
signatures: Set<string>;
}
interface EntityReplica {
entityId: string;
signerId: string;
state: EntityState;
mempool: EntityTx[];
proposal?: ProposedEntityFrame;
isProposer: boolean;
}
interface ServerState {
replicas: Map<string, EntityReplica>;
height: number;
timestamp: number;
}
// === ENTITY PROCESSING ===
const applyEntityTx = (entityState: EntityState, entityTx: EntityTx): EntityState => {
if (entityTx.type === 'chat') {
const { from, message } = entityTx.data;
const currentNonce = entityState.nonces.get(from) || 0;
// Create new state (immutable at transaction level)
const newEntityState = {
...entityState,
nonces: new Map(entityState.nonces),
messages: [...entityState.messages]
};
newEntityState.nonces.set(from, currentNonce + 1);
newEntityState.messages.push(`${from}: ${message}`);
return newEntityState;
}
return entityState;
};
const applyEntityFrame = (entityState: EntityState, entityTxs: EntityTx[]): EntityState => {
return entityTxs.reduce((currentEntityState, entityTx) => applyEntityTx(currentEntityState, entityTx), entityState);
};
// === PROCESSING ===
const processEntityInput = (entityReplica: EntityReplica, entityInput: EntityInput): EntityInput[] => {
const entityOutbox: EntityInput[] = [];
// Add transactions to mempool (mutable for performance)
if (entityInput.entityTxs?.length) {
entityReplica.mempool.push(...entityInput.entityTxs);
if (DEBUG) console.log(` → Added ${entityInput.entityTxs.length} txs to mempool (total: ${entityReplica.mempool.length})`);
}
// Handle proposed frame (PROPOSE phase)
if (entityInput.proposedFrame && !entityReplica.proposal) {
const frameSignature = `sig_${entityReplica.signerId}_${entityInput.proposedFrame}`;
const proposerId = entityReplica.state.validators[0]; // First validator is proposer
entityOutbox.push({
entityId: entityInput.entityId,
signerId: proposerId,
precommits: [frameSignature]
});
if (DEBUG) console.log(` → Signed proposal, sending precommit to ${proposerId}`);
}
// Handle precommits (SIGN phase)
if (entityInput.precommits?.length && entityReplica.proposal) {
// Collect signatures (mutable for performance)
entityInput.precommits.forEach(frameSignature => entityReplica.proposal!.signatures.add(frameSignature));
if (DEBUG) console.log(` → Collected ${entityInput.precommits.length} signatures (total: ${entityReplica.proposal.signatures.size}/${entityReplica.state.threshold})`);
// Check threshold
if (entityReplica.proposal.signatures.size >= entityReplica.state.threshold) {
// Commit phase - use pre-computed state
entityReplica.state = entityReplica.proposal.newState;
if (DEBUG) console.log(` → Threshold reached! Committing frame, height: ${entityReplica.state.height}`);
// Save proposal data before clearing
const committedSignatures = Array.from(entityReplica.proposal.signatures);
const committedHash = entityReplica.proposal.hash;
// Clear state (mutable)
entityReplica.mempool.length = 0;
entityReplica.proposal = undefined;
// Notify all validators
entityReplica.state.validators.forEach(validatorId => {
entityOutbox.push({
entityId: entityInput.entityId,
signerId: validatorId,
precommits: committedSignatures,
proposedFrame: committedHash
});
});
if (DEBUG) console.log(` → Sending commit notifications to ${entityReplica.state.validators.length} validators`);
}
}
// Handle commit notifications (when receiving finalized frame from proposer)
if (entityInput.precommits?.length && entityInput.proposedFrame && !entityReplica.proposal && entityInput.precommits.length >= entityReplica.state.threshold) {
// This is a commit notification from proposer, apply the frame
// We need to reconstruct the frame from the hash (simplified approach)
// In real implementation, we'd validate the frame content
if (DEBUG) console.log(` → Received commit notification with ${entityInput.precommits.length} signatures`);
// For now, just apply the mempool that should match the committed frame
if (entityReplica.mempool.length > 0) {
const newEntityState = applyEntityFrame(entityReplica.state, entityReplica.mempool);
entityReplica.state = newEntityState;
entityReplica.mempool.length = 0;
if (DEBUG) console.log(` → Applied commit, new state: ${entityReplica.state.messages.length} messages`);
}
}
// Auto-propose if mempool not empty and we're proposer
if (entityReplica.isProposer && entityReplica.mempool.length > 0 && !entityReplica.proposal) {
// Compute new state once during proposal
const newEntityState = applyEntityFrame(entityReplica.state, entityReplica.mempool);
entityReplica.proposal = {
height: entityReplica.state.height + 1,
txs: [...entityReplica.mempool],
hash: `frame_${entityReplica.state.height + 1}_${Date.now()}`,
newState: newEntityState,
signatures: new Set<string>()
};
if (DEBUG) console.log(` → Auto-proposing frame ${entityReplica.proposal.hash} with ${entityReplica.proposal.txs.length} txs`);
// Send proposal to all validators
entityReplica.state.validators.forEach(validatorId => {
entityOutbox.push({
entityId: entityInput.entityId,
signerId: validatorId,
proposedFrame: entityReplica.proposal!.hash,
entityTxs: entityReplica.proposal!.txs
});
});
}
return entityOutbox;
};
const processServerInput = (serverState: ServerState, serverInput: ServerInput): EntityInput[] => {
const entityOutbox: EntityInput[] = [];
if (DEBUG) {
console.log(`\n=== TICK ${serverState.height} ===`);
console.log(`Server inputs: ${serverInput.serverTxs.length} serverTxs, ${serverInput.entityInputs.length} entityInputs`);
}
// Process server transactions (replica imports)
serverInput.serverTxs.forEach(serverTx => {
if (serverTx.type === 'importReplica') {
if (DEBUG) console.log(`Importing replica ${serverTx.entityId}:${serverTx.signerId} (proposer: ${serverTx.data.isProposer})`);
const replicaKey = `${serverTx.entityId}:${serverTx.signerId}`;
serverState.replicas.set(replicaKey, {
entityId: serverTx.entityId,
signerId: serverTx.signerId,
state: {
height: 0,
nonces: new Map(),
messages: [],
validators: serverTx.data.validators,
threshold: serverTx.data.threshold
},
mempool: [],
isProposer: serverTx.data.isProposer
});
}
});
// Process entity inputs
serverInput.entityInputs.forEach(entityInput => {
const replicaKey = `${entityInput.entityId}:${entityInput.signerId}`;
const entityReplica = serverState.replicas.get(replicaKey);
if (entityReplica) {
if (DEBUG) {
console.log(`Processing input for ${replicaKey}:`);
if (entityInput.entityTxs?.length) console.log(` → ${entityInput.entityTxs.length} transactions`);
if (entityInput.proposedFrame) console.log(` → Proposed frame: ${entityInput.proposedFrame}`);
if (entityInput.precommits?.length) console.log(` → ${entityInput.precommits.length} precommits`);
}
const entityOutputs = processEntityInput(entityReplica, entityInput);
entityOutbox.push(...entityOutputs);
}
});
// Update server state (mutable)
serverState.height++;
serverState.timestamp = Date.now();
if (DEBUG && entityOutbox.length > 0) {
console.log(`Outputs: ${entityOutbox.length} messages`);
entityOutbox.forEach((output, i) => {
console.log(` ${i+1}. → ${output.signerId} (${output.entityTxs ? `${output.entityTxs.length} txs` : ''}${output.proposedFrame ? ` proposal: ${output.proposedFrame.slice(0,10)}...` : ''}${output.precommits ? ` ${output.precommits.length} precommits` : ''})`);
});
}
if (DEBUG) {
console.log(`Replica states:`);
serverState.replicas.forEach((replica, key) => {
console.log(` ${key}: mempool=${replica.mempool.length}, messages=${replica.state.messages.length}, proposal=${replica.proposal ? '✓' : '✗'}`);
});
}
return entityOutbox;
};
// === DEMO ===
const processUntilEmpty = (serverState: ServerState, inputs: EntityInput[]) => {
let outputs = inputs;
while (outputs.length > 0) {
outputs = processServerInput(serverState, { serverTxs: [], entityInputs: outputs });
}
};
const runDemo = () => {
const serverState: ServerState = { replicas: new Map(), height: 0, timestamp: Date.now() };
if (DEBUG) console.log('🚀 Starting XLN Consensus Demo');
// Import replicas (separate serverTx for each, like in real distributed servers)
const validators = ['alice', 'bob', 'carol'];
processServerInput(serverState, {
serverTxs: validators.map((signerId, index) => ({
type: 'importReplica' as const,
entityId: 'chat',
signerId,
data: {
validators,
threshold: 2,
isProposer: index === 0
}
})),
entityInputs: []
});
// Send multiple chat messages and process consensus
processUntilEmpty(serverState, [{
entityId: 'chat',
signerId: 'alice',
entityTxs: [{ type: 'chat', data: { from: 'alice', message: 'Hello world!' } }]
}]);
// All transactions must go through proposer (alice) for consensus
processUntilEmpty(serverState, [{
entityId: 'chat',
signerId: 'alice', // proposer receives transaction from bob
entityTxs: [{ type: 'chat', data: { from: 'bob', message: 'Hi Alice! Bob here.' } }]
}]);
processUntilEmpty(serverState, [{
entityId: 'chat',
signerId: 'alice', // proposer receives transaction from carol
entityTxs: [{ type: 'chat', data: { from: 'carol', message: 'Hey everyone! Carol joining.' } }]
}]);
processUntilEmpty(serverState, [{
entityId: 'chat',
signerId: 'alice',
entityTxs: [
{ type: 'chat', data: { from: 'alice', message: 'Nice to see you both!' } },
{ type: 'chat', data: { from: 'alice', message: 'How is the consensus working?' } }
]
}]);
if (DEBUG) {
console.log('\n✅ Final State:');
const allMessages: string[][] = [];
serverState.replicas.forEach((replica, key) => {
console.log(`${key}: ${replica.state.messages.length} messages, height ${replica.state.height}`);
if (replica.state.messages.length > 0) {
replica.state.messages.forEach((msg, i) => console.log(` ${i+1}. ${msg}`));
}
allMessages.push([...replica.state.messages]);
});
// Check consensus - all replicas should have identical message history
const firstMessages = allMessages[0];
const allIdentical = allMessages.every(messages =>
messages.length === firstMessages.length &&
messages.every((msg, i) => msg === firstMessages[i])
);
console.log(`\n🔍 Consensus Check: ${allIdentical ? '✅ SUCCESS' : '❌ FAILED'} - All replicas have ${allIdentical ? 'identical' : 'different'} chat history`);
if (allIdentical) {
console.log(`📊 Total messages agreed upon: ${firstMessages.length}`);
}
}
// Return immutable snapshot for API boundary
return {
replicas: new Map(serverState.replicas),
height: serverState.height,
timestamp: serverState.timestamp
};
};
const main = () => {
const finalState = runDemo();
return finalState;
};
// Auto-run demo
main();
export { runDemo, processServerInput, main };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment