Web3 uygulamaları için sunucu bileşenleri ve aracılar
Giriş
Çoğu durumda, merkeziyetsiz bir uygulama yazılımı dağıtmak için bir sunucu kullanır, ancak tüm gerçek etkileşim istemci (genellikle web tarayıcısı) ve blokzincir arasında gerçekleşir.
Ancak, bir uygulamanın bağımsız olarak çalışan bir sunucu bileşenine sahip olmaktan fayda sağlayacağı bazı durumlar vardır. Böyle bir sunucu, işlemler yayınlayarak olaylara ve API gibi diğer kaynaklardan gelen isteklere yanıt verebilir.
Böyle bir sunucunun yerine getirebileceği birkaç olası görev vardır.
-
Gizli durum sahibi. Oyunlarda, oyunun bildiği tüm bilgilerin oyunculara açık olmaması genellikle faydalıdır. Ancak, blokzincirde sır yoktur, blokzincirdeki herhangi bir bilgiyi herkesin anlaması kolaydır. Bu nedenle, oyun durumunun bir kısmı gizli tutulacaksa, başka bir yerde saklanması gerekir (ve muhtemelen bu durumun etkilerinin sıfır bilgi ispatları kullanılarak doğrulanması gerekir).
-
Merkezi kâhin. Riskler yeterince düşükse, çevrimiçi olarak bazı bilgileri okuyan ve sonra bunu zincire gönderen harici bir sunucu, kâhin olarak kullanılmaya yeterli olabilir.
-
Aracı. Onu etkinleştirecek bir işlem olmadan blokzincirde hiçbir şey olmaz. Bir sunucu, fırsat ortaya çıktığında bir kullanıcı adına arbitraj gibi eylemleri gerçekleştirmek için hareket edebilir.
Örnek program
Örnek bir sunucuyu github'da (opens in a new tab) görebilirsiniz. Bu sunucu, Hardhat'in Greeter'ının değiştirilmiş bir sürümü olan bu sözleşmeden (opens in a new tab) gelen olayları dinler. Selamlama değiştirildiğinde, onu geri değiştirir.
Çalıştırmak için:
-
Depoyu klonlayın.
1git clone https://github.com/qbzzt/20240715-server-component.git2cd 20240715-server-component -
Gerekli paketleri yükleyin. Eğer zaten kurulu değilse, önce Node'u yükleyin (opens in a new tab).
1npm install -
Holesky test ağında ETH'si olan bir hesabın özel anahtarını belirtmek için
.envdosyasını düzenleyin. Holesky'de ETH'niz yoksa bu musluğu kullanabilirsiniz (opens in a new tab).1PRIVATE_KEY=0x <özel anahtar buraya gelecek> -
Sunucuyu başlatın.
1npm start -
Bir blok gezginine (opens in a new tab) gidin ve özel anahtara sahip olandan farklı bir adres kullanarak selamlamayı değiştirin. Selamlamanın otomatik olarak geri değiştirildiğini görün.
Nasıl çalışır?
Bir sunucu bileşeninin nasıl yazılacağını anlamanın en kolay yolu, örneği satır satır incelemektir.
src/app.ts
Programın büyük çoğunluğu src/app.ts (opens in a new tab) dosyasında yer almaktadır.
Önkoşul nesnelerini oluşturma
1import {2 createPublicClient,3 createWalletClient,4 getContract,5 http,6 Address,7} from "viem"Bunlar, ihtiyacımız olan Viem (opens in a new tab) varlıkları, işlevler ve Address türüdür (opens in a new tab). Bu sunucu, JavaScript'in onu güçlü tipli (opens in a new tab) yapan bir uzantısı olan TypeScript (opens in a new tab) ile yazılmıştır.
1import { privateKeyToAccount } from "viem/accounts"Bu işlev (opens in a new tab), bir özel anahtara karşılık gelen adres de dahil olmak üzere cüzdan bilgilerini oluşturmamızı sağlar.
1import { holesky } from "viem/chains"Viem'de bir blokzincir kullanmak için tanımını içe aktarmanız gerekir. Bu durumda Holesky (opens in a new tab) test blokzincirine bağlanmak istiyoruz.
1// .env içindeki tanımları process.env'ye bu şekilde ekleriz.2import * as dotenv from "dotenv"3dotenv.config().env dosyasını ortama bu şekilde okuruz. Özel anahtar için buna ihtiyacımız var (daha sonrasına bakın).
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 constTümünü gösterBir sözleşmeyi kullanmak için adresine ve onun bilgisine ihtiyacımız var. İkisini de burada sağlıyoruz.
JavaScript'te (ve dolayısıyla TypeScript'te) bir sabite yeni bir değer atayamazsınız, ancak içinde depolanan nesneyi değiştirebilirsiniz. as const son ekini kullanarak, TypeScript'e listenin kendisinin sabit olduğunu ve değiştirilemeyeceğini söylüyoruz.
1const publicClient = createPublicClient({2 chain: holesky,3 transport: http(),4})Bir Viem genel istemcisi (opens in a new tab) oluşturun. Genel istemcilerin ekli bir özel anahtarı yoktur ve bu nedenle işlem gönderemezler. view işlevlerini (opens in a new tab) çağırabilir, hesap bakiyelerini okuyabilirler vb.
1const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)Ortam değişkenleri process.env (opens in a new tab) içinde mevcuttur. Ancak, TypeScript güçlü tiplidir. Bir ortam değişkeni herhangi bir dize olabilir veya boş olabilir, bu nedenle bir ortam değişkeninin türü string | undefined'dır. Ancak, Viem'de bir anahtar 0x${string} (0x ve ardından gelen bir dize) olarak tanımlanır. Burada TypeScript'e PRIVATE_KEY ortam değişkeninin bu türde olacağını söylüyoruz. Eğer değilse, bir çalışma zamanı hatası alırız.
privateKeyToAccount (opens in a new tab) işlevi daha sonra bu özel anahtarı tam bir hesap nesnesi oluşturmak için kullanır.
1const walletClient = createWalletClient({2 account,3 chain: holesky,4 transport: http(),5})Ardından, bir cüzdan istemcisi (opens in a new tab) oluşturmak için hesap nesnesini kullanırız. Bu istemcinin bir özel anahtarı ve adresi vardır, bu yüzden işlem göndermek için kullanılabilir.
1const greeter = getContract({2 address: greeterAddress,3 abi: greeterABI,4 client: { public: publicClient, wallet: walletClient },5})Artık tüm önkoşullara sahip olduğumuza göre, sonunda bir sözleşme örneği (opens in a new tab) oluşturabiliriz. Zincir üstündeki sözleşmeyle iletişim kurmak için bu sözleşme örneğini kullanacağız.
Blokzincirden okuma
1console.log(`Current greeting:`, await greeter.read.greet())Salt okunur olan sözleşme işlevleri (view (opens in a new tab) ve pure (opens in a new tab)) read altında mevcuttur. Bu durumda, selamlamayı döndüren greet (opens in a new tab) işlevine erişmek için bunu kullanırız.
JavaScript tek iş parçacıklıdır, bu nedenle uzun süren bir işlemi başlattığımızda bunu eşzamansız olarak yaptığımızı belirtmemiz gerekir (opens in a new tab). Salt okunur bir işlem için bile olsa blokzinciri çağırmak, bilgisayar ile bir blokzincir düğümü arasında bir gidiş-dönüş gerektirir. İşte bu yüzden burada kodun sonucu await etmesi gerektiğini belirtiyoruz.
Bunun nasıl çalıştığıyla ilgileniyorsanız buradan okuyabilirsiniz (opens in a new tab), ancak pratik anlamda bilmeniz gereken tek şey, uzun süren bir işlem başlatırsanız sonuçları await etmeniz gerektiği ve bunu yapan herhangi bir işlevin async olarak bildirilmesi gerektiğidir.
İşlemleri yayınlama
1const setGreeting = async (greeting: string): Promise<any> => {Bu, selamlamayı değiştiren bir işlemi yayınlamak için çağırdığınız işlevdir. Bu uzun bir işlem olduğundan, işlev async olarak bildirilmiştir. Dahili uygulama nedeniyle, herhangi bir async işlevinin bir Promise nesnesi döndürmesi gerekir. Bu durumda, Promise<any> ifadesi Promise içinde tam olarak neyin döndürüleceğini belirtmediğimiz anlamına gelir.
1const txHash = await greeter.write.setGreeting([greeting])Sözleşme örneğinin write alanı, setGreeting (opens in a new tab) gibi blokzincir durumuna yazan tüm işlevlere (yani bir işlem göndermeyi gerektirenlere) sahiptir. Parametreler, varsa, bir liste olarak sağlanır ve işlev, işlemin karmasını döndürür.
1 console.log(`Bir düzeltme üzerinde çalışılıyor, bakın https://eth-holesky.blockscout.com/tx/${txHash}`)23 return txHash4}İşlemin karmasını (görüntülemek için blok gezginine yönlendiren bir URL'nin parçası olarak) bildirin ve döndürün.
Olaylara yanıt verme
1greeter.watchEvent.SetGreeting({watchEvent işlevi (opens in a new tab), bir olay yayınlandığında bir işlevin çalıştırılacağını belirtmenize olanak tanır. Yalnızca tek bir olay türüyle ilgileniyorsanız (bu durumda, SetGreeting), kendinizi bu olay türüyle sınırlamak için bu sözdizimini kullanabilirsiniz.
1 onLogs: logs => {onLogs işlevi, günlük girdileri olduğunda çağrılır. Ethereum'da "log" ve "olay" genellikle birbirinin yerine kullanılabilir.
1console.log(2 `${logs[0].args.sender} adresi selamlamayı ${logs[0].args.greeting} olarak değiştirdi`3)Birden fazla olay olabilir, ancak basitlik adına yalnızca ilkiyle ilgileniyoruz. logs[0].args, olayın argümanlarıdır, bu durumda sender ve greeting.
1 if (logs[0].args.sender != account.address)2 setGreeting(`${account.address} bunun Hello! olmasında ısrar ediyor`)3 }4})Gönderici bu sunucu değilse, selamlamayı değiştirmek için setGreeting kullanın.
package.json
Bu dosya (opens in a new tab) Node.js (opens in a new tab) yapılandırmasını kontrol eder. Bu makale yalnızca önemli tanımları açıklamaktadır.
1{2 "main": "dist/index.js",Bu tanım, hangi JavaScript dosyasının çalıştırılacağını belirtir.
1 "scripts": {2 "start": "tsc && node dist/app.js",3 },Betikler çeşitli uygulama eylemleridir. Bu durumda, sahip olduğumuz tek betik, sunucuyu derleyip ardından çalıştıran start'tır. tsc komutu typescript paketinin bir parçasıdır ve TypeScript'i JavaScript'e derler. Manuel olarak çalıştırmak isterseniz, node_modules/.bin konumunda bulunur. İkinci komut sunucuyu çalıştırır.
1 "type": "module",Birden fazla JavaScript Node uygulaması türü vardır. module türü, en üst düzey kodda await kullanmamızı sağlar; bu, yavaş (ve dolayısıyla eşzamansız) işlemler yaptığınızda önemlidir.
1 "devDependencies": {2 "@types/node": "^20.14.2",3 "typescript": "^5.4.5"4 },Bunlar yalnızca geliştirme için gerekli olan paketlerdir. Burada typescript'e ihtiyacımız var ve bunu Node.js ile kullandığımız için, process gibi Node değişkenleri ve nesneleri için türleri de alıyoruz. ^<sürüm> gösterimi (opens in a new tab), bu sürümün veya bozucu değişiklikler içermeyen daha yüksek bir sürümün kullanılacağı anlamına gelir. Sürüm numaralarının anlamı hakkında daha fazla bilgi için buraya (opens in a new tab) bakın.
1 "dependencies": {2 "dotenv": "^16.4.5",3 "viem": "2.14.1"4 }5}Bunlar, dist/app.js çalıştırılırken çalışma zamanında gerekli olan paketlerdir.
Sonuç
Burada oluşturduğumuz merkezi sunucu, bir kullanıcı için aracı olarak hareket etme işini yapıyor. Merkeziyetsiz uygulamanın çalışmaya devam etmesini isteyen ve gaz harcamaya razı olan herkes, sunucunun yeni bir örneğini kendi adresiyle çalıştırabilir.
Ancak bu, yalnızca merkezi sunucunun eylemleri kolayca doğrulanabildiğinde işe yarar. Merkezi sunucunun herhangi bir gizli durum bilgisi varsa veya zor hesaplamalar yapıyorsa, uygulamayı kullanmak için güvenmeniz gereken merkezi bir varlık hâline gelir ki bu, blokzincirlerin tam olarak kaçınmaya çalıştığı şeydir. Gelecekteki bir makalede, bu sorunun üstesinden gelmek için sıfır bilgi ispatlarının nasıl kullanılacağını göstermeyi planlıyorum.
Çalışmalarımdan daha fazlası için buraya bakın (opens in a new tab).
Sayfanın son güncellenmesi: 25 Şubat 2026