Skip to content

Commit

Permalink
Add ws api (#8)
Browse files Browse the repository at this point in the history
* add simple ws server

* feat: add websocket sub snapshot

* add simple emitter

* add simple block emitter

* refactor frontend to use websocket

* add tx type

* render tx type

* frontend api network switching

* build: ci tag with commid id
  • Loading branch information
RetricSu authored Jan 20, 2025
1 parent e359919 commit 97f12ff
Show file tree
Hide file tree
Showing 40 changed files with 2,415 additions and 1,166 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ TESTNET_WS_RPC_URL = "wss://testnet.ckb.dev/ws"
MAINNET_HTTP_RPC_URL = "https://mainnet.ckb.dev"
TESTNET_HTTP_RPC_URL = "https://testnet.ckb.dev"

API_HTTP_PORT = 3000
API_WS_PORT = 3001
API_TESTNET_PORT = 3000
API_MAINNET_PORT = 3001
ALLOW_ORIGIN = "*" # for multiple allow origins, separate with , eg: "domain1.com,domain2.com"
11 changes: 11 additions & 0 deletions .github/workflows/pkg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # get full history to create short-commit-ids

- name: Log in to the Container registry
uses: docker/login-action@v2
Expand All @@ -31,6 +33,15 @@ jobs:
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
${{ github.ref == 'refs/heads/master' && 'latest' || '' }}
${{ github.ref_type == 'tag' && github.ref_name || format('{0}-{1}', github.head_ref || github.ref_name, github.sha) }}
labels: |
org.label-schema.schema-version=1.0
org.label-schema.vcs-url=${{ github.server_url }}/${{ github.repository }}
org.label-schema.vcs-ref=${{ github.sha }}
org.label-schema.build-date=${{ github.event.push.head_commit.timestamp }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
Expand Down
4 changes: 4 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VITE_TESTNET_HTTP_API_URL = http://localhost:3000
VITE_TESTNET_WS_API_URL = ws://localhost:3000
VITE_MAINNET_HTTP_API_URL = http://localhost:3001
VITE_MAINNET_WS_API_URL = ws://localhost:3001
17 changes: 13 additions & 4 deletions frontend/src/components/elevator/car.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import Matter from "matter-js";
import { useEffect, useRef, useState } from "preact/hooks";
import { BlockHeader, Transaction } from "../../service/chain";
import {
BlockHeader,
Transaction,
TransactionType,
TransactionTypeEnum,
} from "../../service/type";
import {
boxSizeToMatterSize,
carBoxCenterPosX,
carBoxSize,
randomFillStyleColor,
transactionSquareSize,
} from "./util";
import { useAtomValue } from "jotai";
Expand All @@ -32,6 +36,7 @@ const ElevatorCar: React.FC<ElevatorCarProp> = (props) => {
setDoorClosing(false);
createScene();
}

function createScene() {
let Engine = Matter.Engine;
let Render = Matter.Render;
Expand All @@ -56,9 +61,13 @@ const ElevatorCar: React.FC<ElevatorCarProp> = (props) => {
// create two boxes and a ground
const txBoxes = transactions.map((tx, i) => {
const size = transactionSquareSize(tx.size);
const color =
tx.type != null
? TransactionType.toBgColor(+tx.type as TransactionTypeEnum)
: "black";
const box = Bodies.rectangle(carBoxCenterPosX, 80, size, size, {
render: {
fillStyle: randomFillStyleColor(),
fillStyle: color,
strokeStyle: "black",
lineWidth: 3,
},
Expand Down Expand Up @@ -137,7 +146,7 @@ const ElevatorCar: React.FC<ElevatorCarProp> = (props) => {

useEffect(() => {
run();
}, [transactions.length]);
}, [blockHeader?.block_number]);

useEffect(() => {
if (setFromDoorClosing) {
Expand Down
56 changes: 35 additions & 21 deletions frontend/src/components/elevator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import { useEffect, useState } from "preact/hooks";
import { ChainService } from "../../service/chain";
import { ChainService } from "../../service/api";
import ElevatorCar from "./car";
import ElevatorUpButton from "./up-btn";
import ElevatorPanel from "./panel";
import ElevatorHeader from "./header";
import { useAtomValue } from "jotai";
import { ChainTheme, chainThemeAtom } from "../../states/atoms";
import { Network, TipBlockResponse } from "../../service/type";

export default function Elevator() {
const chainTheme = useAtomValue(chainThemeAtom);
const [proposedTxs, setProposedTxs] = useState([]);
const [committedTxs, setCommittedTxs] = useState([]);
const [blockHeader, setBlockHeader] = useState(undefined);
const [currentTipNumber, setCurrentTipNumber] = useState(undefined);
const [tipBlock, setTipBlock] = useState<TipBlockResponse>(undefined);
const [doorClosing, setDoorClosing] = useState(false);

// Update effect to fetch all data
// subscribe to new block
// todo: need unscribe when component unmount
const subNewBlock = async () => {
const network =
chainTheme === ChainTheme.mainnet
? Network.Mainnet
: Network.Testnet;
const chainService = new ChainService(network);
chainService.wsClient.connect(() => {
chainService.subscribeNewBlock((newBlock) => {
if (newBlock.blockHeader) {
setTipBlock((prev) => {
if (
prev == null ||
prev?.blockHeader?.block_number <
newBlock.blockHeader.block_number
) {
return newBlock || undefined;
} else {
return prev;
}
});
}
});
});
};
useEffect(() => {
const fetchData = async () => {
const [tipBlockTxs] = await Promise.all([
ChainService.getTipBlockTransactions(),
]);
setProposedTxs(tipBlockTxs.proposedTransactions);
setCommittedTxs(tipBlockTxs.committedTransactions);
setBlockHeader(tipBlockTxs.blockHeader);
};
const task = setInterval(fetchData, 3000);
return () => clearInterval(task);
}, []);
subNewBlock();
}, [chainTheme]);

const bgElevatorFrame =
chainTheme === ChainTheme.mainnet
Expand All @@ -49,20 +63,20 @@ export default function Elevator() {
className={`${bgElevatorFrame} flex flex-col justify-center w-min mx-auto rounded-lg border-[20px] ${borderBlack}`}
>
<ElevatorHeader
blockNumber={+blockHeader?.block_number}
blockNumber={+tipBlock?.blockHeader.block_number}
doorClosing={doorClosing}
/>
<div className={"px-20"}>
<ElevatorCar
transactions={committedTxs}
blockHeader={blockHeader}
blockHeader={tipBlock?.blockHeader}
transactions={tipBlock?.committedTransactions}
setFromDoorClosing={setDoorClosing}
/>
</div>
</div>

<ElevatorPanel
transactionNumber={committedTxs.length}
transactionNumber={tipBlock?.committedTransactions.length}
sizeBytes={20}
occupationPercentage={20}
/>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Ground: FunctionComponent<GroundProps> = ({}) => {
{Object.values(TransactionTypeEnum)
.filter((t) => typeof t === "number")
.map((type) => {
const bgColor = `${TransactionType.toBgColor(type)}`;
const bgColor = `${TransactionType.toBgTailwindCSS(type)}`;
return (
<div
class={`flex justify-start items-center align-middle gap-1`}
Expand Down
125 changes: 65 additions & 60 deletions frontend/src/components/pool/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Matter from "matter-js";
import { FunctionalComponent } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { ChainService, Transaction } from "../../service/chain";
import PoolScene from "./scene";
import QueueComponent from "./motion";
import { useEffect, useState } from "preact/hooks";
import { ChainService } from "../../service/api";
import QueueComponent from "./queue";
import { Network, Transaction } from "../../service/type";
import { useAtomValue } from "jotai";
import { ChainTheme, chainThemeAtom } from "../../states/atoms";

type TxStatus = "pending" | "proposing" | "proposed" | "committed" | "none";

Expand All @@ -14,7 +15,7 @@ interface TxChange {
}

const Pool: FunctionalComponent = () => {
const [blockHeader, setBlockHeader] = useState(undefined);
const chainTheme = useAtomValue(chainThemeAtom);

const [initProposedTxs, setInitProposedTxs] = useState<
Transaction[] | null
Expand Down Expand Up @@ -43,65 +44,69 @@ const Pool: FunctionalComponent = () => {

const [stateChanges, setStateChanges] = useState<TxChange[]>([]); // 存储状态变化信息

const fetchData = async () => {
const [tipBlockTxs, pendingTxs, proposingTxs, proposedTransactions] =
await Promise.all([
ChainService.getTipBlockTransactions(),
ChainService.getPendingTransactions(),
ChainService.getProposingTransactions(),
ChainService.getProposedTransactions(),
]);

if (initCommittedTxs == null) {
setInitCommittedTxs(tipBlockTxs.committedTransactions);
}
if (initProposedTxs == null) {
setInitProposedTxs(proposedTransactions);
}
if (initPendingTxs == null) {
setInitPendingTxs(pendingTxs);
}
if (initProposingTxs == null) {
setInitProposingTxs(proposingTxs);
}

// 拿到所有tx
const newTxs = {
pending: pendingTxs,
proposing: proposingTxs,
proposed: proposedTransactions,
committed: tipBlockTxs.committedTransactions,
};
const subNewSnapshot = async () => {
const network =
chainTheme === ChainTheme.mainnet
? Network.Mainnet
: Network.Testnet;
const chainService = new ChainService(network);
chainService.wsClient.connect(() => {
chainService.subscribeNewSnapshot((newSnapshot) => {
const {
tipCommittedTransactions,
pendingTransactions,
proposingTransactions,
proposedTransactions,
} = newSnapshot;

if (initCommittedTxs == null) {
setInitCommittedTxs(tipCommittedTransactions);
}
if (initProposedTxs == null) {
setInitProposedTxs(proposedTransactions);
}
if (initPendingTxs == null) {
setInitPendingTxs(pendingTransactions);
}
if (initProposingTxs == null) {
setInitProposingTxs(proposingTransactions);
}

// 拿到所有tx
const newTxs = {
pending: pendingTransactions,
proposing: proposingTransactions,
proposed: proposedTransactions,
committed: tipCommittedTransactions,
};

// diff 数据
if (previousTxs) {
const changes = detectStateChanges(previousTxs, newTxs);
setStateChanges(changes);
}

// 更新历史记录
// 使用函数式更新
setPreviousTxs((prev) => {
if (prev) {
const changes = detectStateChanges(prev, newTxs);
setStateChanges(changes);
}
return newTxs;
});

// diff 数据
if (previousTxs) {
const changes = detectStateChanges(previousTxs, newTxs);
setStateChanges(changes);
}

// 更新历史记录
// 使用函数式更新
setPreviousTxs((prev) => {
if (prev) {
const changes = detectStateChanges(prev, newTxs);
setStateChanges(changes);
}
return newTxs;
setPendingTxs(pendingTransactions);
setProposingTxs(proposingTransactions);
setProposedTxs(proposedTransactions);
setCommittedTxs(tipCommittedTransactions);
});
});

setPendingTxs(pendingTxs);

setProposingTxs(proposingTxs);
setProposedTxs(proposedTransactions);

setCommittedTxs(tipBlockTxs.committedTransactions);
setBlockHeader(tipBlockTxs.blockHeader);
};

useEffect(() => {
const task = setInterval(fetchData, 1000);
return () => clearInterval(task);
}, []);
subNewSnapshot();
}, [chainTheme]);

// 状态变化检测
const detectStateChanges = (
Expand Down
Loading

0 comments on commit 97f12ff

Please sign in to comment.