تخطٍ إلى المحتوى الرئيسي

مكونات الخادم والوكلاء لتطبيقات ويب3

وكيل
خادم
خارج السلسلة
المستوى المبتدئ
أوري بوميرانتز
15 يوليو 2024
8 دقيقة قراءة

مقدمة

في معظم الحالات، يستخدم التطبيق اللامركزي خادمًا لتوزيع البرنامج، ولكن كل التفاعل الفعلي يحدث بين العميل (عادةً، متصفح الويب) والبلوكتشين.

التفاعل العادي بين خادم الويب والعميل والبلوكتشين

ومع ذلك، هناك بعض الحالات التي قد يستفيد فيها التطبيق من وجود مكون خادم يعمل بشكل مستقل. سيكون هذا الخادم قادرًا على الاستجابة للأحداث، والطلبات التي تأتي من مصادر أخرى، مثل واجهة برمجة التطبيقات (API)، عن طريق إصدار المعاملات.

التفاعل مع إضافة خادم

هناك العديد من المهام المحتملة التي يمكن لمثل هذا الخادم أن يفي بها.

  • حامل الحالة السرية. في الألعاب، غالبًا ما يكون من المفيد عدم إتاحة جميع المعلومات التي تعرفها اللعبة للاعبين. ومع ذلك، لا توجد أسرار على البلوكتشين، وأي معلومات موجودة في البلوكتشين من السهل على أي شخص معرفتها. لذلك، إذا كان من المقرر الحفاظ على سرية جزء من حالة اللعبة، فيجب تخزينه في مكان آخر (وربما يتم التحقق من تأثيرات تلك الحالة باستخدام إثباتات المعرفة الصفرية).

  • أوراكل مركزي. إذا كانت الحصص منخفضة بما فيه الكفاية، فقد يكون الخادم الخارجي الذي يقرأ بعض المعلومات عبر الإنترنت ثم ينشرها على السلسلة جيدًا بما يكفي لاستخدامه كـ أوراكل.

  • وكيل. لا يحدث شيء على البلوكتشين بدون معاملة لتفعيله. يمكن للخادم أن يعمل نيابة عن المستخدم لتنفيذ إجراءات مثل المراجحة عندما تتاح الفرصة.

برنامج نموذجي

يمكنك رؤية خادم نموذجي على github (opens in a new tab). يستمع هذا الخادم إلى الأحداث الواردة من هذا العقد (opens in a new tab)، وهو إصدار معدل من Greeter الخاص بـ هارد هات. عندما يتم تغيير التحية، يقوم بتغييرها مرة أخرى.

لتشغيله:

  1. استنسخ المستودع.

    1git clone https://github.com/qbzzt/20240715-server-component.git
    2cd 20240715-server-component
  2. ثبّت الحزم اللازمة. إذا لم يكن لديك بالفعل، قم بتثبيت Node أولاً (opens in a new tab).

    1npm install
  3. حرر .env لتحديد المفتاح الخاص لحساب يحتوي على ETH على شبكة اختبار هوليسكي. إذا لم يكن لديك ETH على هوليسكي، فيمكنك استخدام هذا السبيل (opens in a new tab).

    1PRIVATE_KEY=0x <المفتاح الخاص يوضع هنا>
  4. ابدأ الخادم.

    1npm start
  5. انتقل إلى مستكشف الكتل (opens in a new tab)، وباستخدام عنوان مختلف عن العنوان الذي يحتوي على المفتاح الخاص، قم بتعديل التحية. لاحظ أن التحية يتم تعديلها تلقائيًا مرة أخرى.

How does it work؟

أسهل طريقة لفهم كيفية كتابة مكون خادم هي مراجعة العينة سطرًا بسطر.

src/app.ts

الغالبية العظمى من البرنامج موجودة في src/app.ts (opens in a new tab).

إنشاء الكائنات الأساسية
1import {
2 createPublicClient,
3 createWalletClient,
4 getContract,
5 http,
6 Address,
7} from "viem"

هذه هي كيانات فيم (opens in a new tab) التي نحتاجها، والوظائف ونوع العنوان (opens in a new tab). هذا الخادم مكتوب بلغة تايب سكريبت (opens in a new tab)، وهي امتداد لـ جافا سكريبت يجعلها مكتوبة بقوة (opens in a new tab).

1import { privateKeyToAccount } from "viem/accounts"

تتيح لنا هذه الوظيفة (opens in a new tab) إنشاء معلومات المحفظة، بما في ذلك العنوان، المطابق للمفتاح الخاص.

1import { holesky } from "viem/chains"

لاستخدام بلوكتشين في فيم، تحتاج إلى استيراد تعريفه. في هذه الحالة، نريد الاتصال ببلوكتشين اختبار هوليسكي (opens in a new tab).

1// هكذا نضيف التعريفات الموجودة في .env إلى process.env.
2import * as dotenv from "dotenv"
3dotenv.config()

هكذا نقرأ .env في البيئة. نحن بحاجة إليه من أجل المفتاح الخاص (انظر لاحقًا).

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

لاستخدام عقد، نحتاج إلى عنوانه وواجهة التطبيق الثنائية الخاصة به. نحن نقدم كليهما هنا.

في جافا سكريبت (وبالتالي تايب سكريبت) لا يمكنك تعيين قيمة جديدة لثابت، ولكن يمكنك تعديل الكائن المخزن فيه. باستخدام اللاحقة as const، فإننا نخبر تايب سكريبت بأن القائمة نفسها ثابتة ولا يمكن تغييرها.

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

أنشئ عميلًا عامًا (opens in a new tab) من فيم. لا تحتوي العملاء العموميون على مفتاح خاص مرفق، وبالتالي لا يمكنهم إرسال معاملات. يمكنهم استدعاء وظائف العرض (opens in a new tab)، وقراءة أرصدة الحسابات، وما إلى ذلك.

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

متغيرات البيئة متاحة في process.env (opens in a new tab). ومع ذلك، فإن تايب سكريبت مكتوبة بقوة. يمكن أن يكون متغير البيئة أي سلسلة نصية، أو فارغًا، لذا فإن نوع متغير البيئة هو string | undefined. ومع ذلك، يتم تعريف المفتاح في فيم على أنه 0x${string} (0x متبوعًا بسلسلة نصية). هنا نخبر تايب سكريبت بأن متغير البيئة PRIVATE_KEY سيكون من هذا النوع. إذا لم يكن كذلك، فسنحصل على خطأ في وقت التشغيل.

تستخدم وظيفة privateKeyToAccount (opens in a new tab) هذا المفتاح الخاص لإنشاء كائن حساب كامل.

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

بعد ذلك، نستخدم كائن الحساب لإنشاء عميل محفظة (opens in a new tab). يحتوي هذا العميل على مفتاح خاص وعنوان، لذلك يمكن استخدامه لإرسال المعاملات.

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

الآن بعد أن أصبح لدينا كل المتطلبات الأساسية، يمكننا أخيرًا إنشاء مثيل عقد (opens in a new tab). سنستخدم مثيل العقد هذا للتواصل مع العقد على السلسلة.

القراءة من البلوكتشين
1console.log(`Current greeting:`, await greeter.read.greet())

وظائف العقد المخصصة للقراءة فقط (view (opens in a new tab) و pure (opens in a new tab)) متاحة ضمن read. في هذه الحالة، نستخدمها للوصول إلى وظيفة greet (opens in a new tab)، التي تُرجع التحية.

جافا سكريبت هي أحادية الخيط، لذلك عندما نبدأ عملية طويلة الأمد، نحتاج إلى تحديد أننا نفعل ذلك بشكل غير متزامن (opens in a new tab). يتطلب استدعاء البلوكتشين، حتى لعملية قراءة فقط، رحلة ذهابًا وإيابًا بين الكمبيوتر وعقدة بلوكتشين. هذا هو السبب في أننا نحدد هنا أن النص البرمجي يحتاج إلى await للنتيجة.

إذا كنت مهتمًا بكيفية عمل ذلك، يمكنك القراءة عنه هنا (opens in a new tab)، ولكن من الناحية العملية كل ما تحتاج إلى معرفته هو أنك تنتظر النتائج إذا بدأت عملية تستغرق وقتًا طويلاً، وأن أي وظيفة تقوم بذلك يجب أن تُعلن على أنها async.

إصدار المعاملات
1const setGreeting = async (greeting: string): Promise<any> => {

هذه هي الوظيفة التي تستدعيها لإصدار معاملة تغير التحية. نظرًا لأن هذه عملية طويلة، يتم الإعلان عن الوظيفة على أنها async. بسبب التنفيذ الداخلي، تحتاج أي وظيفة async إلى إرجاع كائن Promise. في هذه الحالة، يعني Promise<any> أننا لا نحدد بالضبط ما سيتم إرجاعه في Promise.

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

يحتوي حقل write لمثيل العقد على جميع الوظائف التي تكتب إلى حالة البلوكتشين (تلك التي تتطلب إرسال معاملة)، مثل setGreeting (opens in a new tab). يتم توفير المعلمات، إن وجدت، كقائمة، وتُرجع الوظيفة تجزئة (هاش) المعاملة.

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

أبلغ عن تجزئة (هاش) المعاملة (كجزء من عنوان URL إلى مستكشف الكتل لعرضها) وأعدها.

الاستجابة للأحداث
1greeter.watchEvent.SetGreeting({

تتيح لك وظيفة watchEvent (opens in a new tab) تحديد تشغيل وظيفة عند إصدار حدث. إذا كنت تهتم فقط بنوع واحد من الأحداث (في هذه الحالة، SetGreeting)، فيمكنك استخدام هذه الصيغة لتقييد نفسك بهذا النوع من الأحداث.

1 onLogs: logs => {

يتم استدعاء وظيفة onLogs عندما تكون هناك إدخالات سجل. في إيثريوم، عادة ما يكون "السجل" و"الحدث" قابلين للتبادل.

1console.log(
2 `Address ${logs[0].args.sender} changed the greeting to ${logs[0].args.greeting}`
3)

قد تكون هناك أحداث متعددة، ولكن للتبسيط نحن نهتم فقط بالحدث الأول. logs[0].args هي وسيطات الحدث، في هذه الحالة sender وgreeting.

1 if (logs[0].args.sender != account.address)
2 setGreeting(`${account.address} insists on it being Hello!`)
3 }
4})

إذا كان المرسل ليس هذا الخادم، فاستخدم setGreeting لتغيير التحية.

package.json

هذا الملف (opens in a new tab) يتحكم في تكوين نود.جي إس (opens in a new tab). يشرح هذا المقال التعريفات المهمة فقط.

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

يحدد هذا التعريف ملف جافا سكريبت الذي سيتم تشغيله.

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

البرامج النصية هي إجراءات تطبيق مختلفة. في هذه الحالة، الوحيد الذي لدينا هو start، الذي يجمع ثم يشغل الخادم. الأمر tsc هو جزء من حزمة typescript ويجمع تايب سكريبت إلى جافا سكريبت. إذا كنت تريد تشغيله يدويًا، فهو موجود في node_modules/.bin. الأمر الثاني يشغل الخادم.

1 "type": "module",

هناك أنواع متعددة من تطبيقات عقدة جافا سكريبت. يتيح لنا نوع الوحدة الحصول على await في النص البرمجي ذي المستوى الأعلى، وهو أمر مهم عند القيام بعمليات بطيئة (وهناك غير متزامنة).

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

هذه حزم مطلوبة فقط للتطوير. هنا نحتاج إلى typescript ولأننا نستخدمه مع نود.جي إس، فإننا نحصل أيضًا على أنواع متغيرات وكائنات العقدة، مثل process. يعني تدوين ^<الإصدار> (opens in a new tab) هذا الإصدار أو إصدارًا أعلى لا يحتوي على تغييرات جذرية. انظر هنا (opens in a new tab) لمزيد من المعلومات حول معنى أرقام الإصدارات.

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

هذه حزم مطلوبة في وقت التشغيل، عند تشغيل dist/app.js.

الخلاصة

يقوم الخادم المركزي الذي أنشأناه هنا بعمله، وهو العمل كوكيل للمستخدم. يمكن لأي شخص آخر يريد أن يستمر التطبيق اللامركزي في العمل ومستعد لإنفاق الغاز تشغيل مثيل جديد من الخادم بعنوانه الخاص.

ومع ذلك، لا يعمل هذا إلا عندما يمكن التحقق بسهولة من إجراءات الخادم المركزي. إذا كان الخادم المركزي يحتوي على أي معلومات حالة سرية، أو يجري حسابات صعبة، فهو كيان مركزي تحتاج إلى الثقة به لاستخدام التطبيق، وهو بالضبط ما تحاول شبكات البلوكتشين تجنبه. في مقال مستقبلي، أخطط لإظهار كيفية استخدام إثباتات المعرفة الصفرية للتغلب على هذه المشكلة.

انظر هنا لمزيد من أعمالي (opens in a new tab).

آخر تحديث للصفحة: 3 مارس 2026

هل كانت تعليمات الاستخدام هذه مفيدة؟