Přeskočit na hlavní obsah

Serverové komponenty a agenti pro web3 aplikace

agent
server
offchain
Začátečník
Ori Pomerantz
15. července 2024
8 minuta čtení

Úvod

Ve většině případů decentralizovaná aplikace používá server k distribuci softwaru, ale veškerá skutečná interakce probíhá mezi klientem (obvykle webovým prohlížečem) a blockchainem.

Normální interakce mezi webovým serverem, klientem a blockchainem

Existují však případy, kdy by aplikaci prospěla serverová komponenta, která běží nezávisle. Takový server by byl schopen reagovat na události a na požadavky, které přicházejí z jiných zdrojů, jako je API, vydáváním transakcí.

Interakce s přidaným serverem

Existuje několik možných úkolů, které by takový server mohl plnit.

  • Držitel tajného stavu. Při hraní je často užitečné, aby hráči neměli k dispozici všechny informace, které hra zná. Nicméně, na blockchainu neexistují žádná tajemství, jakoukoli informaci, která je v blockchainu, může kdokoli snadno zjistit. Proto, pokud má být část stavu hry utajena, musí být uložena jinde (a případně nechat účinky tohoto stavu ověřit pomocí důkazů s nulovou znalostí).

  • Centralizované orákulum. Pokud jsou sázky dostatečně nízké, externí server, který čte některé informace online a poté je zveřejňuje na řetězci, může být dostatečně dobrý na to, aby byl použit jako orákulum.

  • Agent. Na blockchainu se nic nestane bez transakce, která by to aktivovala. Server může jednat jménem uživatele a provádět akce, jako je arbitráž, když se naskytne příležitost.

Ukázkový program

Ukázkový server si můžete prohlédnout na GitHubuopens in a new tab. Tento server naslouchá událostem pocházejícím z tohoto kontraktuopens in a new tab, upravené verze Hardhat Greeteru. Když se pozdrav změní, změní ho zpět.

Spustíte ho takto:

  1. Naklonujte repozitář.

    1git clone https://github.com/qbzzt/20240715-server-component.git
    2cd 20240715-server-component
  2. Nainstalujte potřebné balíčky. Pokud jej ještě nemáte, nainstalujte nejprve Nodeopens in a new tab.

    1npm install
  3. Upravte soubor .env a zadejte soukromý klíč účtu, který má ETH na testnetu Holesky. Pokud nemáte ETH na Holesky, můžete použít tento faucetopens in a new tab.

    1PRIVATE_KEY=0x <zde vložte soukromý klíč>
  4. Spusťte server.

    1npm start
  5. Přejděte do prohlížeče blokůopens in a new tab a pomocí jiné adresy, než je ta, ke které máte soukromý klíč, upravte pozdrav. Uvidíte, že pozdrav je automaticky změněn zpět.

Jak to funguje? Jak to funguje

Nejjednodušší způsob, jak pochopit, jak napsat serverovou komponentu, je projít si ukázku řádek po řádku.

src/app.ts

Drtivá většina programu je obsažena v src/app.tsopens in a new tab.

Vytvoření nezbytných objektů
1import {
2 createPublicClient,
3 createWalletClient,
4 getContract,
5 http,
6 Address,
7} from "viem"

Toto jsou entity Viemopens in a new tab, které potřebujeme, funkce a typ Addressopens in a new tab. Tento server je napsán v TypeScriptuopens in a new tab, což je rozšíření JavaScriptu, které ho činí silně typovanýmopens in a new tab.

1import { privateKeyToAccount } from "viem/accounts"

Tato funkceopens in a new tab nám umožňuje generovat informace o peněžence, včetně adresy, odpovídající soukromému klíči.

1import { holesky } from "viem/chains"

Abyste mohli v Viem používat blockchain, musíte importovat jeho definici. V tomto případě se chceme připojit k testovacímu blockchainu Holeskyopens in a new tab.

1// Takto přidáváme definice z .env do process.env.
2import * as dotenv from "dotenv"
3dotenv.config()

Takto načítáme .env do prostředí. Potřebujeme to pro soukromý klíč (viz dále).

1const greeterAddress : Address = "0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6"
2const greeterABI = [
3 {
4 "inputs": [
5 {
6 "internalType": "string",
7 "name": "_greeting",
8 "type": "string"
9 }
10 ],
11 "stateMutability": "nonpayable",
12 "type": "constructor"
13 },
14 .
15 .
16 .
17 {
18 "inputs": [
19 {
20 "internalType": "string",
21 "name": "_greeting",
22 "type": "string"
23 }
24 ],
25 "name": "setGreeting",
26 "outputs": [],
27 "stateMutability": "nonpayable",
28 "type": "function"
29 }
30] as const
Zobrazit vše

Pro použití kontraktu potřebujeme jeho adresu a . Obojí zde uvádíme.

V JavaScriptu (a tedy i v TypeScriptu) nemůžete konstantě přiřadit novou hodnotu, ale můžete upravit objekt, který je v ní uložen. Použitím přípony as const říkáme TypeScriptu, že seznam samotný je konstantní a nesmí být změněn.

1const publicClient = createPublicClient({
2 chain: holesky,
3 transport: http(),
4})

Vytvořte Viem veřejného klientaopens in a new tab. Veřejní klienti nemají připojený soukromý klíč, a proto nemohou odesílat transakce. Mohou volat view funkceopens in a new tab, číst zůstatky na účtech atd.

1const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

Proměnné prostředí jsou k dispozici v process.envopens in a new tab. TypeScript je však silně typovaný. Proměnná prostředí může být jakýkoli řetězec, nebo prázdná, takže typem pro proměnnou prostředí je string | undefined. Klíč je však v Viem definován jako 0x${string} (0x následované řetězcem). Zde říkáme TypeScriptu, že proměnná prostředí PRIVATE_KEY bude tohoto typu. Pokud ne, dojde k chybě za běhu.

Funkce privateKeyToAccountopens in a new tab pak použije tento soukromý klíč k vytvoření úplného objektu účtu.

1const walletClient = createWalletClient({
2 account,
3 chain: holesky,
4 transport: http(),
5})

Dále použijeme objekt účtu k vytvoření klienta peněženkyopens in a new tab. Tento klient má soukromý klíč a adresu, takže jej lze použít k odesílání transakcí.

1const greeter = getContract({
2 address: greeterAddress,
3 abi: greeterABI,
4 client: { public: publicClient, wallet: walletClient },
5})

Nyní, když máme všechny předpoklady, můžeme konečně vytvořit instanci kontraktuopens in a new tab. Tuto instanci kontraktu použijeme ke komunikaci s on-chain kontraktem.

Čtení z blockchainu
1console.log(`Current greeting:`, await greeter.read.greet())

Funkce kontraktu, které jsou pouze pro čtení (viewopens in a new tab a pureopens in a new tab), jsou dostupné pod read. V tomto případě ji používáme pro přístup k funkci greetopens in a new tab, která vrací pozdrav.

JavaScript je jednovláknový, takže když spouštíme dlouhotrvající proces, musíme specifikovat, že to děláme asynchronněopens in a new tab. Volání blockchainu, i pro operaci pouze pro čtení, vyžaduje obousměrnou komunikaci mezi počítačem a uzlem blockchainu. To je důvod, proč zde specifikujeme, že kód musí na výsledek await (počkat).

Pokud vás zajímá, jak to funguje, můžete si o tom přečíst zdeopens in a new tab, ale v praxi vše, co potřebujete vědět, je, že await (čekáte na) výsledky, pokud spustíte operaci, která trvá dlouho, a že jakákoli funkce, která to dělá, musí být deklarována jako async.

Vydávání transakcí
1const setGreeting = async (greeting: string): Promise<any> => {

Toto je funkce, kterou voláte pro vydání transakce, která mění pozdrav. Jelikož se jedná o dlouhou operaci, funkce je deklarována jako async. Kvůli interní implementaci musí každá async funkce vracet objekt Promise. V tomto případě Promise<any> znamená, že nespecifikujeme, co přesně bude v Promise vráceno.

1const txHash = await greeter.write.setGreeting([greeting])

Pole write instance kontraktu obsahuje všechny funkce, které zapisují do stavu blockchainu (ty, které vyžadují odeslání transakce), jako je setGreetingopens in a new tab. Parametry, pokud nějaké jsou, jsou poskytnuty jako seznam a funkce vrací haš transakce.

1 console.log(`Working on a fix, see https://eth-holesky.blockscout.com/tx/${txHash}`)
2
3 return txHash
4}

Nahlaste haš transakce (jako součást adresy URL do prohlížeče bloků pro její zobrazení) a vraťte jej.

Reakce na události
1greeter.watchEvent.SetGreeting({

Funkce watchEventopens in a new tab umožňuje specifikovat, že se má funkce spustit, když je emitována událost. Pokud vás zajímá pouze jeden typ události (v tomto případě SetGreeting), můžete použít tuto syntaxi k omezení se na tento typ události.

1 onLogs: logs => {

Funkce onLogs je volána, když existují záznamy protokolu (logy). V Ethereu jsou pojmy „log“ a „událost“ obvykle zaměnitelné.

1console.log(
2 `Adresa ${logs[0].args.sender} změnila pozdrav na ${logs[0].args.greeting}`
3)

Mohlo by zde být více událostí, ale pro jednoduchost nás zajímá pouze ta první. logs[0].args jsou argumenty události, v tomto případě sender a greeting.

1 if (logs[0].args.sender != account.address)
2 setGreeting(`${account.address} trvá na tom, aby to bylo Hello!`)
3 }
4})

Pokud odesílatel není tento server, použijte setGreeting ke změně pozdravu.

package.json

Tento souboropens in a new tab řídí konfiguraci Node.jsopens in a new tab. Tento článek vysvětluje pouze důležité definice.

1{
2 "main": "dist/index.js",

Tato definice určuje, který soubor JavaScriptu se má spustit.

1 "scripts": {
2 "start": "tsc && node dist/app.js",
3 },

Skripty jsou různé akce aplikace. V tomto případě máme pouze jeden, start, který kompiluje a poté spouští server. Příkaz tsc je součástí balíčku typescript a kompiluje TypeScript do JavaScriptu. Pokud jej chcete spustit ručně, nachází se v node_modules/.bin. Druhý příkaz spouští server.

1 "type": "module",

Existuje více typů JavaScriptových node aplikací. Typ module nám umožňuje mít await v kódu nejvyšší úrovně, což je důležité, když provádíte pomalé (a tedy asynchronní) operace.

1 "devDependencies": {
2 "@types/node": "^20.14.2",
3 "typescript": "^5.4.5"
4 },

Toto jsou balíčky, které jsou vyžadovány pouze pro vývoj. Zde potřebujeme typescript a protože ho používáme s Node.js, získáváme také typy pro node proměnné a objekty, jako je process. Zápis ^<verze>opens in a new tab znamená danou verzi nebo vyšší verzi, která neobsahuje zásadní změny. Více informací o významu čísel verzí naleznete zdeopens in a new tab.

1 "dependencies": {
2 "dotenv": "^16.4.5",
3 "viem": "2.14.1"
4 }
5}

Toto jsou balíčky, které jsou vyžadovány za běhu, při spouštění dist/app.js.

Závěr

Centralizovaný server, který jsme zde vytvořili, plní svůj úkol, kterým je jednat jako agent pro uživatele. Kdokoli jiný, kdo chce, aby dapp nadále fungoval a je ochoten utratit palivo, může spustit novou instanci serveru s vlastní adresou.

To však funguje pouze tehdy, když lze akce centralizovaného serveru snadno ověřit. Pokud má centralizovaný server nějaké tajné stavové informace nebo provádí složité výpočty, je to centralizovaná entita, které musíte důvěřovat, abyste mohli aplikaci používat, což je přesně to, čemu se blockchainy snaží vyhnout. V budoucím článku plánuji ukázat, jak tento problém obejít pomocí důkazů s nulovou znalostí.

Více z mé práce najdete zdeopens in a new tab.

Stránka naposledy aktualizována: 14. února 2026

Byl tento tutoriál užitečný?