Skip to content

Commit

Permalink
Merge pull request #76 from cardano-scaling/pi/new-game-hooks
Browse files Browse the repository at this point in the history
Add on new game and on player joined hooks
  • Loading branch information
Quantumplation authored Nov 27, 2024
2 parents e59c875 + e654901 commit 97f766f
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 50 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@noble/hashes": "^1.5.0",
"@tanstack/react-query": "^5.55.4",
"bech32-buffer": "^0.2.1",
"cbor-x": "^1.6.0",
"classnames": "^2.5.1",
"lodash": "^4.17.21",
"lucid-cardano": "^0.10.10",
Expand Down
1 change: 1 addition & 0 deletions referee/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@noble/ed25519": "^2.1.0",
"@noble/hashes": "^1.5.0",
"bech32-buffer": "^0.2.1",
"cbor-x": "^1.6.0",
"lucid-cardano": "^0.10.10",
"prom-client": "^15.1.3",
"readline": "^1.3.0"
Expand Down
14 changes: 12 additions & 2 deletions referee/referee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,14 @@ const module = await createModule({
},
});
global.Module = module;
global.HydraMultiplayer = new HydraMultiplayerServer({

const hydra = new HydraMultiplayerServer({
key: keys,
address: keys.address,
url: HYDRA_NODE,
module,
});
global.HydraMultiplayer = hydra;

let playerCount = 0;
global.gameStarted = async () => {
Expand Down Expand Up @@ -208,6 +210,14 @@ try {
console.warn("Failed to fetch and parse node utxos: ", e);
}

// Log a new game or player joined transaction if we see it
hydra.onNewGame = (gameId, players, bots, ephemeralKey) => {
console.log("New game: ", gameId, players, bots, ephemeralKey);
};
hydra.onPlayerJoin = (gameId, ephemeralKeys) => {
console.log("Join: ", gameId, ephemeralKeys);
};

const args = [
"-server",
"-altdeath",
Expand All @@ -220,7 +230,7 @@ const args = [
"-extratics",
"1",
"-nodes",
"2",
"3",
"-nodraw",
"-nomouse",
"-nograbmouse",
Expand Down
63 changes: 63 additions & 0 deletions referee/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,36 @@
"@smithy/types" "^3.7.1"
tslib "^2.6.2"

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz#8d65cb861a99622e1b4a268e2d522d2ec6137338"
integrity sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz#9fbec199c888c5ec485a1839f4fad0485ab6c40a"
integrity sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz#bf77e0db4a1d2200a5aa072e02210d5043e953ae"
integrity sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz#491335037eb8533ed8e21b139c59f6df04e39709"
integrity sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz#672574485ccd24759bf8fb8eab9dbca517d35b97"
integrity sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==

"@cbor-extract/[email protected]":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz#4b3f07af047f984c082de34b116e765cb9af975f"
integrity sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==

"@noble/ed25519@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-2.1.0.tgz#4bf661de9ee0ad775d41fcacbfc9aeec491f459c"
Expand Down Expand Up @@ -976,11 +1006,37 @@ bowser@^2.11.0:
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f"
integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==

cbor-extract@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.2.0.tgz#cee78e630cbeae3918d1e2e58e0cebaf3a3be840"
integrity sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==
dependencies:
node-gyp-build-optional-packages "5.1.1"
optionalDependencies:
"@cbor-extract/cbor-extract-darwin-arm64" "2.2.0"
"@cbor-extract/cbor-extract-darwin-x64" "2.2.0"
"@cbor-extract/cbor-extract-linux-arm" "2.2.0"
"@cbor-extract/cbor-extract-linux-arm64" "2.2.0"
"@cbor-extract/cbor-extract-linux-x64" "2.2.0"
"@cbor-extract/cbor-extract-win32-x64" "2.2.0"

cbor-x@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.6.0.tgz#89c35d2d805efc30e09a28349425cc05d57aacd7"
integrity sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==
optionalDependencies:
cbor-extract "^2.2.0"

data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==

detect-libc@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==

[email protected]:
version "4.4.1"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f"
Expand Down Expand Up @@ -1026,6 +1082,13 @@ node-fetch@^3.2.3:
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"

[email protected]:
version "5.1.1"
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c"
integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==
dependencies:
detect-libc "^2.0.1"

prom-client@^15.1.3:
version "15.1.3"
resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.3.tgz#69fa8de93a88bc9783173db5f758dc1c69fa8fc2"
Expand Down
129 changes: 104 additions & 25 deletions src/utils/HydraMultiplayer/base.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import {
Constr,
Data,
fromHex,
toHex,
TxComplete,
TxHash,
UTxO,
} from "lucid-cardano";
import { Constr, Data, fromHex, toHex, TxHash, UTxO } from "lucid-cardano";
import { Hydra } from ".././hydra";

import * as ed25519 from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";
import { EmscriptenModule } from "../../types";
import { Keys } from "../../hooks/useKeys";
import { Keys } from "../../types";
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));

export abstract class HydraMultiplayer {
Expand All @@ -24,6 +16,16 @@ export abstract class HydraMultiplayer {
module: EmscriptenModule;
networkId: number;

gameId?: string;

onNewGame?: (
gameId: string,
players: number,
bots: number,
ephemeralKey: string,
) => void;
onPlayerJoin?: (gameId: string, ephemeralKeys: string[]) => void;

constructor({
key,
url,
Expand Down Expand Up @@ -74,15 +76,32 @@ export abstract class HydraMultiplayer {
this.packetQueue = [];
}

public onTxSeen(_txId: TxHash, tx: TxComplete): void {
// TODO: tolerate other txs here
public onTxSeen(txId: TxHash, tx: any): void {
try {
const output = tx.txComplete.body().outputs().get(0);
const packetsRaw = output?.datum()?.as_data()?.get().to_bytes();
if (!packetsRaw) {
const body = tx[0];
const outputs = body["1"];
const output = outputs[0];
const datumRaw: Uint8Array = output["2"][1].value;
if (!datumRaw) {
return;
}
const packets = decodePackets(datumRaw);
if (!packets) {
// We failed to decode packets, so this might be a new game or join game tx
const game = decodeGame(datumRaw);
if (this.gameId) {
this.onPlayerJoin?.(this.gameId, game.players);
} else {
this.gameId = txId;
this.onNewGame?.(
txId,
Number(game.playerCount),
Number(game.botCount),
game.players[0],
);
}
return;
}
const packets = decodePackets(packetsRaw);
for (const packet of packets) {
if (packet.to == this.myIP) {
const buf = this.module._malloc!(packet.data.length);
Expand Down Expand Up @@ -118,14 +137,74 @@ function encodePackets(packets: Packet[]): string {
);
}

function decodePackets(raw: Uint8Array): Packet[] {
function decodePackets(raw: Uint8Array): Packet[] | undefined {
const packets = Data.from(toHex(raw)) as Constr<Data>[];
return packets.map((packet) => {
const [to, from, data] = packet.fields;
return {
to: Number(to),
from: Number(from),
data: fromHex(data as string),
};
});
return packets instanceof Array
? packets.map((packet) => {
const [to, from, data] = packet.fields;
return {
to: Number(to),
from: Number(from),
data: fromHex(data as string),
};
})
: undefined;
}

interface Game {
referee_key_hash: string;
playerCount: bigint;
botCount: bigint;
players: string[];
state: "Lobby" | "Running" | "Cheated" | "Finished" | "Aborted";
winner?: string;
cheater?: string;
}

function decodeGame(raw: Uint8Array): Game {
const game = Data.from(toHex(raw)) as Constr<Data>;
const [
referee_payment,
playerCountRaw,
botCountRaw,
player_payments,
stateTag,
winnerRaw,
cheaterRaw,
] = game.fields;
const referee_key_hash = (referee_payment as Constr<Data>)
.fields[0] as string;
const playerCount = playerCountRaw as bigint;
const botCount = botCountRaw as bigint;
const players = (player_payments as Constr<Data>[]).map(
(player) => player.fields[0] as string,
);
let state: Game["state"] = "Aborted";
switch ((stateTag as Constr<Data>).index) {
case 0:
state = "Lobby";
break;
case 1:
state = "Running";
break;
case 2:
state = "Cheated";
break;
case 3:
state = "Finished";
break;
default:
state = "Aborted";
}
const winner = winnerRaw as Constr<Data>;
const cheater = cheaterRaw as Constr<Data>;
return {
referee_key_hash: referee_key_hash,
playerCount,
botCount,
players,
state: state,
winner: winner.index == 0 ? (winner.fields[0] as string) : undefined,
cheater: cheater.index == 0 ? (cheater.fields[0] as string) : undefined,
};
}
28 changes: 5 additions & 23 deletions src/utils/hydra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OutRef,
ProtocolParameters,
toHex,
fromHex,
Transaction,
TxHash,
Unit,
Expand All @@ -15,6 +16,7 @@ import {
TxComplete,
Credential as Cred,
} from "lucid-cardano";
import { decode } from "cbor-x";

const NETWORK_ID =
typeof process !== "undefined"
Expand Down Expand Up @@ -45,7 +47,7 @@ export class Hydra {
utxos: { [txRef: string]: UTxO };
tombstones: { [txRef: string]: boolean };

onTxSeen?: (txId: TxHash, tx: TxComplete) => void;
onTxSeen?: (txId: TxHash, tx: any) => void;
onTxConfirmed?: (txid: TxHash) => void;
onTxInvalid?: (txid: TxHash) => void;

Expand Down Expand Up @@ -119,29 +121,9 @@ export class Hydra {
this.tx_timings[txid].seen = seenTime;
// console.log(`seen ${txid} after ${seenTime}ms`);
}
const tx = tx_parser.fromTx(data.transaction.cborHex);
for (const input of tx.txComplete.body().inputs().to_js_value()) {
const ref = `${input.transaction_id}#${input.index}`;
if (this.utxos[ref]) {
delete this.utxos[ref];
} else {
this.tombstones[ref] = true;
}
}
let idx = 0;
for (const output of tx.txComplete.body().outputs().to_js_value()) {
const ref = `${tx.toHash()}#${idx}`;
if (!this.tombstones[ref]) {
this.utxos[ref] = hydraUtxoToLucidUtxo(
tx.toHash(),
idx,
output,
);
}
idx++;
}
const cbor = fromHex(data.transaction.cborHex);
const tx = decode(cbor);
this.onTxSeen?.(txid, tx);
tx.txComplete.free();
}
break;
case "TxInvalid":
Expand Down
Loading

0 comments on commit 97f766f

Please sign in to comment.