From f410f09d6765cde65edbd057b8c5c2056f2471d1 Mon Sep 17 00:00:00 2001 From: Lu Jincheng Date: Sat, 6 Dec 2025 05:44:20 +0800 Subject: [PATCH] all core functions are working --- .../Contracts/ClearNet/ClearNet.json | 318 ++++++++++-------- app/Components/Contracts/Connections.tsx | 53 ++- app/Components/ServerList.tsx | 61 +++- app/Components/Util.tsx | 2 + 4 files changed, 283 insertions(+), 151 deletions(-) diff --git a/app/Components/Contracts/ClearNet/ClearNet.json b/app/Components/Contracts/ClearNet/ClearNet.json index ecb3f88..0b7cc81 100644 --- a/app/Components/Contracts/ClearNet/ClearNet.json +++ b/app/Components/Contracts/ClearNet/ClearNet.json @@ -48,6 +48,31 @@ "name": "SafeERC20FailedOperation", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "client", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "connectionStartTime", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "abortTime", + "type": "uint256" + } + ], + "name": "ConnectionAborted", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -211,6 +236,25 @@ "name": "NodeUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "ownerAccount", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "OwnerShareAccrued", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -348,39 +392,25 @@ { "indexed": false, "internalType": "uint256", - "name": "relayShare", + "name": "ownerShare", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "ratingProvided", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rating", "type": "uint256" } ], "name": "PaymentProcessed", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "relay", - "type": "address" - } - ], - "name": "RelayOperatorAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "relay", - "type": "address" - } - ], - "name": "RelayOperatorRemoved", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -413,6 +443,19 @@ "name": "Unpaused", "type": "event" }, + { + "inputs": [], + "name": "ABORT_WINDOW", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "INITIAL_REPUTATION", @@ -532,46 +575,7 @@ }, { "inputs": [], - "name": "RELAY_SHARE", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "REPUTATION_DECREMENT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "REPUTATION_INCREMENT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "REPUTATION_PRECISION", + "name": "OWNER_SHARE", "outputs": [ { "internalType": "uint256", @@ -608,6 +612,34 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "string", + "name": "vpnClientPublicKey", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_connectionStartTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_agreedPricePerMinute", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_clientSignature", + "type": "bytes" + } + ], + "name": "abortConnection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "acceptGovernanceTransfer", @@ -653,19 +685,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_relay", - "type": "address" - } - ], - "name": "addRelayOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -801,6 +820,34 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getContractStats", + "outputs": [ + { + "internalType": "uint256", + "name": "totalNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalChannels", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalMinutes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "treasuryBalance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -903,12 +950,12 @@ { "inputs": [ { - "internalType": "address", - "name": "_address", - "type": "address" + "internalType": "bytes32", + "name": "_messageHash", + "type": "bytes32" } ], - "name": "isRelayOperator", + "name": "isSignatureUsed", "outputs": [ { "internalType": "bool", @@ -973,6 +1020,16 @@ "internalType": "uint256", "name": "lastActivity", "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalRatingValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalRatingCount", + "type": "uint256" } ], "stateMutability": "view", @@ -1078,14 +1135,29 @@ "name": "_node", "type": "address" }, + { + "internalType": "string", + "name": "vpnClientPublicKey", + "type": "string" + }, { "internalType": "uint256", - "name": "_minutesUsed", + "name": "_connectionStartTime", "type": "uint256" }, { "internalType": "uint256", - "name": "_nonce", + "name": "_agreedPricePerMinute", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_ratingProvided", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_rating", "type": "uint256" }, { @@ -1097,11 +1169,6 @@ "internalType": "bytes", "name": "_nodeSignature", "type": "bytes" - }, - { - "internalType": "bytes", - "name": "_relaySignature", - "type": "bytes" } ], "name": "processPayment", @@ -1132,38 +1199,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "relayOperators", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_relay", - "type": "address" - } - ], - "name": "removeRelayOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "renounceOwnership", @@ -1241,6 +1276,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "uintToString", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, { "inputs": [], "name": "unpause", @@ -1279,24 +1333,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_nodeID", - "type": "address" - }, - { - "internalType": "bool", - "name": "_successfulSession", - "type": "bool" - } - ], - "name": "updateReputation", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { diff --git a/app/Components/Contracts/Connections.tsx b/app/Components/Contracts/Connections.tsx index 0ec52cd..6e0a098 100644 --- a/app/Components/Contracts/Connections.tsx +++ b/app/Components/Contracts/Connections.tsx @@ -1,12 +1,15 @@ -import { Web3 } from 'web3'; +import { Web3, type Bytes } from 'web3'; import type { Node, NodeInfo, PaymentChannelInfo } from '../Util'; import clearNetJson from './ClearNet/ClearNet.json'; import clrTokenJson from './CLRToken/CLRToken.json'; const clearNetABI = (clearNetJson as any).abi ?? clearNetJson; const clrTokenABI = (clrTokenJson as any).abi ?? clrTokenJson; -const clearNetAddress = "0xb6f537b38b82d08ff3ed796754d9d85b5cfe9cb5"; +const clearNetAddress = "0x265da498da1de3f22bb57c717d14806e4884cdda"; const clrTokenAddress = "0xf1664c17887767c8f58695846babb349ca61d2e9"; +// const clearNetAddress = "0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae"; +// const clrTokenAddress = "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707"; + const DEFAULT_MIN_STAKE = BigInt(10000) * BigInt(1e18); // 10000 CLR @@ -20,10 +23,12 @@ export const getActiveNodes = async (provider: any): Promise => { const nodes: Node[] = await Promise.all(activeNodes.map(async (node: string) => { const nodeInfo: NodeInfo = await clearnet_contract.methods.getNodeInfo(node).call(); return { + address: node, ip: nodeInfo.ipAddress, port: Number(nodeInfo.port), traffic: 0, price: Number(nodeInfo.pricePerMinute) / 1e18, + pricePerMinute: nodeInfo.pricePerMinute, rating: Number(nodeInfo.reputationScore) / 1000, }; })); @@ -72,6 +77,50 @@ export async function closePaymentChannel(provider: any,account: string) { } +export async function processPayment(provider: any,account: string,nodeAddress: string, + vpnClientPublicKey: string, connectionStartTime: BigInt, agreedPricePerMinute: BigInt, + isRatingProvided: boolean, rating: BigInt, clientSignature: string, nodeSignature: string) { + const web3 = new Web3(provider); + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const gasPrice = await web3.eth.getGasPrice(); + const gasLimit = 300000; + const tx = await clearnet_contract.methods.processPayment( + account, + nodeAddress, + vpnClientPublicKey, + connectionStartTime, + agreedPricePerMinute, + isRatingProvided, + rating, + clientSignature, + nodeSignature + ).send({ + from: account, + gas: String(gasLimit), + gasPrice: String(gasPrice), + }); + console.log(tx); +} + +export async function abortConnection(provider: any,account: string,vpnClientPublicKey: string, + connectionStartTime: BigInt,agreedPricePerMinute: BigInt,clientSignature: Bytes) { + const web3 = new Web3(provider); + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const gasPrice = await web3.eth.getGasPrice(); + const gasLimit = 300000; + const tx = await clearnet_contract.methods.abortConnection( + vpnClientPublicKey, + connectionStartTime, + agreedPricePerMinute, + clientSignature + ).send({ + from: account, + gas: String(gasLimit), + gasPrice: String(gasPrice), + }); + console.log(tx); +} + export async function getPaymentChannelInfo(provider: any,account: string) : Promise { if (!provider) { return { balance: BigInt(0), nonce: BigInt(0), isActive: false}; diff --git a/app/Components/ServerList.tsx b/app/Components/ServerList.tsx index 9e396db..1869e05 100644 --- a/app/Components/ServerList.tsx +++ b/app/Components/ServerList.tsx @@ -29,9 +29,10 @@ import type { AccountInfo } from '~/context/AuthProvider'; import { signMessage } from './Metamask/Connections'; import { generateWireguardKeyPair, downloadWireguardConfig } from './WireguardConfig'; import type { Node } from './Util'; -import { getActiveNodes, getNextNonce } from './Contracts/Connections'; +import { getActiveNodes, getNextNonce, processPayment, abortConnection } from './Contracts/Connections'; import axios from 'axios'; import { useEffect } from 'react'; +import { Web3 } from 'web3'; @@ -39,19 +40,46 @@ function NodeItem({node, auth}: {node: Node, auth: AccountInfo}) { const [ratingOpen, setRatingOpen] = React.useState(false); const [ratingValue, setRatingValue] = React.useState(null); const [submittingRating, setSubmittingRating] = React.useState(false); + let nodeSignature = ""; const getUrl = () => `http://${node.ip}:${node.port}`; const connect = async () => { try { const { privatekey: clientPrivateKey, publicKey: clientPublicKey } = generateWireguardKeyPair(); const nonce: BigInt = await getNextNonce(auth.providerWithInfo.provider, auth.accounts[0]); - const sig = await signMessage(nonce + clientPublicKey,auth.providerWithInfo.provider, auth.accounts[0]); - const res_string = nonce + '\n' + clientPublicKey + '\n' + sig; + const connectionStartTime = Math.floor(Date.now() / 1000); + console.log("Nonce:", nonce.toString()); + const sig = await signMessage(nonce + clientPublicKey + connectionStartTime + node.pricePerMinute,auth.providerWithInfo.provider, auth.accounts[0]); + const res_string = nonce + '\n' + clientPublicKey + '\n' + connectionStartTime + '\n' + node.pricePerMinute + '\n' + sig; let response = await axios.post(getUrl() + "/connect", res_string); const clientCIDR = response.data.WireguardClientCIDR; const serverPublicKey = response.data.WireguardServerPublicKey; const peerPort = response.data.WireguardPort; const dns = response.data.WireguardDNS; console.log(response.data); + if(response.data.NodeSignature){ + const provider = auth.providerWithInfo.provider; + const web3 = new Web3(provider); + const recoveredAddress = web3.eth.accounts.recover(nonce.toString() + connectionStartTime + node.pricePerMinute, response.data.NodeSignature); + if(recoveredAddress === node.address){ + nodeSignature = response.data.NodeSignature; + } + } + if(nodeSignature === ""){ + await abortConnection(auth.providerWithInfo.provider, auth.accounts[0], + clientPublicKey, + BigInt(connectionStartTime), + node.pricePerMinute, + String(sig) + ); + return; + } + localStorage.setItem(node.address, JSON.stringify({ + vpnClientPublicKey: clientPublicKey, + connectionStartTime: connectionStartTime, + agreedPricePerMinute: node.pricePerMinute.toString(), + clientSignature: String(sig), + nodeSignature: nodeSignature + })); downloadWireguardConfig(clientPrivateKey, serverPublicKey, clientCIDR, dns, node.ip, String(peerPort), "0.0.0.0/0"); } catch (error) { console.error('Error:', error); @@ -60,11 +88,28 @@ function NodeItem({node, auth}: {node: Node, auth: AccountInfo}) { const disconnect = async () => { try{ - const nonce: BigInt = await getNextNonce(auth.providerWithInfo.provider, auth.accounts[0]); - const sig = await signMessage(String(nonce),auth.providerWithInfo.provider, auth.accounts[0]); - const res_string = String(nonce) + '\n' + sig; - let response = await axios.post(getUrl() + "/disconnect", res_string); - console.log(response.data); + // const nonce: BigInt = await getNextNonce(auth.providerWithInfo.provider, auth.accounts[0]); + // const sig = await signMessage(String(nonce),auth.providerWithInfo.provider, auth.accounts[0]); + // const res_string = String(nonce) + '\n' + sig; + // let response = await axios.post(getUrl() + "/disconnect", res_string); + // console.log(response.data); + const storedInfo = localStorage.getItem(node.address); + if(!storedInfo){ + console.error("No active connection info"); + return; + } + const connectionInfo = JSON.parse(storedInfo); + await processPayment(auth.providerWithInfo.provider, auth.accounts[0], + node.address, + connectionInfo.vpnClientPublicKey, + BigInt(connectionInfo.connectionStartTime), + BigInt(connectionInfo.agreedPricePerMinute), + false, + BigInt(0), + connectionInfo.clientSignature, + connectionInfo.nodeSignature + ); + localStorage.removeItem(node.address); } catch (error) { console.error('Error:', error); } diff --git a/app/Components/Util.tsx b/app/Components/Util.tsx index ec3f248..9c2e22a 100644 --- a/app/Components/Util.tsx +++ b/app/Components/Util.tsx @@ -1,9 +1,11 @@ export interface Node { + address: string; ip: string; port: number; traffic: number; price : number; + pricePerMinute : BigInt; rating : number; }