Skip to content

Commit

Permalink
Merge pull request #38 from cardano-scaling/no-queue
Browse files Browse the repository at this point in the history
No queue
  • Loading branch information
Quantumplation authored Aug 12, 2024
2 parents 7ad3b79 + 51f3863 commit 5b92db2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 74 deletions.
38 changes: 26 additions & 12 deletions src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ let player_pkh: string;
let node = window.localStorage.getItem("hydra-doom-session-node");
let scriptRef = window.localStorage.getItem("hydra-doom-session-ref");
let gameData: GameData;
let stop = false;

let sessionStats = {
transactions: 0,
Expand Down Expand Up @@ -106,26 +107,36 @@ export async function fetchNewGame(region: string) {
// append some representation of the tx into the UI
appendTx(cmd);
if (cmdQueue.length > 1000) {
console.warn(
"Command queue grows big, should cleanup",
cmdQueue.length,
);
console.warn("Command queue grew big, purging 100 entries");
cmdQueue = cmdQueue.slice(-100);
}
});
};
hydra.onTxConfirmed = () => {
hydra.onTxSeen = (txId) => {
console.log("seen", txId);
};
hydra.onTxSeen = (txId) => {
console.log("seen", txId);
};
hydra.onTxConfirmed = (txId) => {
console.log("confirmed", txId);
// XXX: TPS only computed when tx confirmed -> does not go to 0 after some time
const now = performance.now();
let tps = 0;
for (const txid in hydra!.tx_timings) {
const timing = hydra!.tx_timings[txid];
const confirm_time = timing.sent + (timing?.confirmed ?? 0);
if (hydra!.tx_timings[txid]?.confirmed && confirm_time > now - 1000) {
const timing = hydra!.tx_timings[txid]!;
if (timing.confirmed && timing.sent + timing.confirmed > now - 1000) {
tps++;
}
}
// console.log("confirmed tps", tps);
setLocalSpeedometerValue(tps);
};
hydra.startEventLoop();
hydra.onTxInvalid = (txId) => {
console.error("invalid", txId);
setLocalSpeedometerValue(0);
stop = true;
};
latestUTxO = await hydra.awaitUtxo(newGameResponse.player_utxo, 5000);
// HACK: until hydra returns the datum bytes, all the datum bytes will be wrong
// so we return it from the newGameResponse and set it manually here
Expand Down Expand Up @@ -189,12 +200,14 @@ export async function hydraSend(
leveltime: number,
level: LevelId,
) {
if (stop) throw new Error("stop");

if (!gameData || !hydra) throw new Error("Game data not initialized");

if (gameState != GameState.GS_LEVEL) {
return;
}

// console.log("hydraSend", cmd);
let hydraSendStart = performance.now();
gameData.level = level;

Expand Down Expand Up @@ -244,7 +257,7 @@ export async function hydraSend(

redeemerQueue.push(cmd);

if (frameNumber % 4 == 0) {
if (frameNumber % 1 == 0) {
const [newUtxo, tx] = await buildTx(
latestUTxO!,
encodeRedeemer(redeemerQueue),
Expand All @@ -263,7 +276,7 @@ export async function hydraSend(
);
updateUI(session, sessionStats);

hydra.queueTx(tx.toString(), tx.toHash());
await hydra.submitTx(tx.toString());
latestUTxO = newUtxo;
redeemerQueue = [];
console.log(
Expand All @@ -274,6 +287,7 @@ export async function hydraSend(
}

export function hydraRecv(): Cmd {
console.log("hydraRecv", cmdQueue.length);
if (cmdQueue.length == 0) {
return {
forwardMove: 0,
Expand Down
94 changes: 32 additions & 62 deletions src/hydra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ const tx_parser = await Lucid.new(undefined, "Preprod");
const utils = new Utils(tx_parser);

export interface TransactionTiming {
// Monotonic time when sent
sent: number;
// Milliseconds after sent when seen as valid
seen?: number;
// Milliseconds after sent when seen as invalid
invalid?: number;
// Milliseconds after sent when confirmed
confirmed?: number;
}

Expand Down Expand Up @@ -80,58 +84,6 @@ export class Hydra {
}
}

public async startEventLoop() {
if (this.interval) {
return;
}
this.interval = setInterval(async () => await this.sendMessages());
}

async sendMessages() {
const now = performance.now();
while (true) {
if (this.outbound_transactions.length === 0) {
return;
}
const [next_tx, next_tx_id] = this.outbound_transactions[0];
if (!this.tx_timings[next_tx_id]) {
// If this transaction hasn't been sent, send it
this.tx_count++;
this.tx_timings[next_tx_id] = {
sent: now,
};
this.connection.send(
JSON.stringify({
tag: "NewTx",
transaction: {
type: "Tx BabbageEra",
cborHex: next_tx,
},
}),
);
// We don't want to risk sending another tx until that one is seen,
// so we return here and do things on the next scheduled event loop
return;
} else if (
this.tx_timings[next_tx_id].seen ||
this.tx_timings[next_tx_id].invalid
) {
// This transaction was either invalid or seen, so we can shift it off the outbound transactions;
this.outbound_transactions.shift();
// we can try to submit the next tx, so we can continue to the next one
continue;
} else if (this.tx_timings[next_tx_id]?.seen ?? now < now - 500) {
// We have been waiting a half second for this tx to be seen, so log a warning
console.warn(`Transaction not confirmed within 500ms: ${next_tx_id}`);
this.outbound_transactions.shift();
continue;
} else {
// We haven't seen it confirmed yet, so lets exit and wait for the next event loop
return;
}
}
}

async receiveMessage(message: MessageEvent) {
const now = performance.now();
const data = JSON.parse(message.data);
Expand Down Expand Up @@ -168,6 +120,7 @@ export class Hydra {
break;
case "TxInvalid":
{
console.error("TxInvalid", data);
const txid = data.transaction.txId;
if (this.tx_timings[txid]?.sent) {
const invTime = now - this.tx_timings[txid].sent;
Expand All @@ -178,6 +131,7 @@ export class Hydra {
break;
case "SnapshotConfirmed":
{
console.log("SnapshotConfirmed", data.snapshot.number);
for (const txid of data.snapshot.confirmedTransactions) {
if (!this.tx_timings[txid]?.sent) {
continue;
Expand Down Expand Up @@ -227,19 +181,35 @@ export class Hydra {
};
}

public queueTx(tx: Transaction, txId: TxHash) {
this.outbound_transactions.push([tx, txId]);
if (this.outbound_transactions.length > this.queue_length) {
console.warn(
`Outbound transaction queue (${this.outbound_transactions.length}) is above configured threshold (${this.queue_length})`,
);
}
}
public async submitTx(tx: Transaction): Promise<string> {
const txParsed = tx_parser.fromTx(tx);
const txId = txParsed.toHash();
this.queueTx(tx, txId);
await this.awaitTx(txId);
this.tx_timings[txId] = { sent: performance.now() };
this.connection.send(
JSON.stringify({
tag: "NewTx",
transaction: {
type: "Tx BabbageEra",
cborHex: tx,
},
}),
);
// FIXME: As we chain transactions, at some point we start seeing errors
// when spent txins are not there yet. The following block can be used to
// work around that, but obviously reduces performance to the network
// roundtrip.
//
// return new Promise((res, rej) => {
// const interval = setInterval(() => {
// if (this.tx_timings[txId]?.invalid) {
// clearInterval(interval);
// rej(txId);
// } else if (this.tx_timings[txId]?.seen) {
// clearInterval(interval);
// res(txId);
// }
// }, 10);
// });
return txId;
}
public async awaitTx(txId: TxHash, checkInterval?: number): Promise<boolean> {
Expand Down

0 comments on commit 5b92db2

Please sign in to comment.