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

ذا غراف: إصلاح استعلام بيانات ويب3

Solidity
العقود الذكيه
استفسار
the graph
react
المستوى المتوسط
ماركوس واس
6 سبتمبر 2020
7 دقيقة قراءة

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

بدون ذا غراف...

لذا دعنا نستخدم مثالًا بسيطًا لأغراض التوضيح. كلنا نحب الألعاب، لذا تخيل لعبة بسيطة يضع فيها المستخدمون رهانات:

1pragma solidity 0.7.1;
2
3contract Game {
4 uint256 totalGamesPlayerWon = 0;
5 uint256 totalGamesPlayerLost = 0;
6 event BetPlaced(address player, uint256 value, bool hasWon);
7
8 function placeBet() external payable {
9 bool hasWon = evaluateBetForPlayer(msg.sender);
10
11 if (hasWon) {
12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
13 require(success, "Transfer failed");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
إظهار الكل

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

  1. جلب totalGamesPlayerWon.
  2. جلب totalGamesPlayerLost.
  3. الاشتراك في أحداث BetPlaced.

يمكننا الاستماع إلى الحدث في ويب3 (opens in a new tab) كما هو موضح على اليمين، ولكنه يتطلب معالجة عدد غير قليل من الحالات.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // تم إطلاق الحدث
6})
7.on('changed', function(event) {
8 // تمت إزالة الحدث مرة أخرى
9})
10.on('error', function(error, receipt) {
11 // تم رفض المعاملة
12});
إظهار الكل

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

One Does Not Simply Query

يمكنك أن ترى كيف أن هذا ليس الأمثل:

  • لا يعمل مع العقود المنشورة بالفعل.
  • تكاليف غاز إضافية لتخزين هذه القيم.
  • يتطلب استدعاء آخر لجلب البيانات من عقدة إيثريوم.

Thats not good enough

الآن دعونا نلقي نظرة على حل أفضل.

دعني أقدم لكم جراف كيو إل

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

جراف كيو إل API vs. REST API

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

الآن مع هذه المعرفة، دعنا ننتقل أخيرًا إلى فضاء البلوكتشين وذا غراف.

ما هو ذا غراف؟

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

ذا غراف

الأمثلة هي دائمًا الأفضل لفهم شيء ما، لذلك دعونا نستخدم ذا غراف لمثال GameContract الخاص بنا.

كيفية إنشاء Subgraph

يُطلق على تعريف كيفية فهرسة البيانات اسم subgraph. يتطلب ثلاثة مكونات:

  1. البيان (Manifest) (subgraph.yaml)
  2. المخطط (Schema) (schema.graphql)
  3. الربط (Mapping) (mapping.ts)

البيان (Manifest) (subgraph.yaml)

البيان هو ملف التكوين الخاص بنا ويحدد:

  • أي العقود الذكية يجب فهرستها (العنوان، الشبكة، واجهة التطبيق الثنائية...)
  • أي الأحداث التي يجب الاستماع إليها
  • أشياء أخرى للاستماع إليها مثل استدعاءات الوظائف أو الكتل
  • دوال الربط التي يتم استدعاؤها (انظر mapping.ts أدناه)

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

لأسباب تتعلق بالراحة، قد ترغب أيضًا في استخدام أداة قوالب مثل mustache. ثم تقوم بإنشاء subgraph.template.yaml وإدراج العناوين بناءً على أحدث عمليات النشر. لإعداد مثال أكثر تقدمًا، انظر على سبيل المثال مستودع آفي subgraph (opens in a new tab).

ويمكن الاطلاع على التوثيق الكامل هنا (opens in a new tab).

1specVersion: 0.0.1
2description: Placing Bets on Ethereum
3repository: - GitHub link -
4schema:
5 file: ./schema.graphql
6dataSources:
7 - kind: ethereum/contract
8 name: GameContract
9 network: mainnet
10 source:
11 address: '0x2E6454...cf77eC'
12 abi: GameContract
13 startBlock: 6175244
14 mapping:
15 kind: ethereum/events
16 apiVersion: 0.0.1
17 language: wasm/assemblyscript
18 entities:
19 - GameContract
20 abis:
21 - name: GameContract
22 file: ../build/contracts/GameContract.json
23 eventHandlers:
24 - event: PlacedBet(address,uint256,bool)
25 handler: handleNewBet
26 file: ./src/mapping.ts
إظهار الكل

المخطط (Schema) (schema.graphql)

المخطط هو تعريف بيانات جراف كيو إل. سيسمح لك بتحديد الكيانات الموجودة وأنواعها. الأنواع المدعومة من ذا غراف هي

  • بايت
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

يمكنك أيضًا استخدام الكيانات كنوع لتعريف العلاقات. في مثالنا، نحدد علاقة 1 إلى متعدد من اللاعب إلى الرهانات. الرمز ! يعني أن القيمة لا يمكن أن تكون فارغة. يمكن الاطلاع على التوثيق الكامل هنا (opens in a new tab).

1type Bet @entity {
2 id: ID!
3 player: Player!
4 playerHasWon: Boolean!
5 time: Int!
6}
7
8type Player @entity {
9 id: ID!
10 totalPlayedCount: Int
11 hasWonCount: Int
12 hasLostCount: Int
13 bets: [Bet]!
14}
إظهار الكل

الربط (Mapping) (mapping.ts)

يحدد ملف الربط في ذا غراف وظائفنا التي تحول الأحداث الواردة إلى كيانات. إنه مكتوب بلغة AssemblyScript، وهي مجموعة فرعية من Typescript. وهذا يعني أنه يمكن تجميعه في WASM (ويب أسيمبلي) لتنفيذ الربط بشكل أكثر كفاءة وسهولة.

ستحتاج إلى تحديد كل دالة مسماة في ملف subgraph.yaml، لذا في حالتنا نحتاج فقط إلى دالة واحدة: handleNewBet. نحاول أولاً تحميل كيان اللاعب من عنوان المرسل كمعرّف. إذا لم يكن موجودًا، فإننا ننشئ كيانًا جديدًا ونملأه بالقيم الأولية.

ثم ننشئ كيان Bet جديد. سيكون المعرّف لهذا هو event.transaction.hash.toHex() + "-" + event.logIndex.toString() مما يضمن دائمًا قيمة فريدة. استخدام التجزئة (هاش) فقط لا يكفي حيث قد يقوم شخص ما باستدعاء دالة placeBet عدة مرات في معاملة واحدة عبر عقد ذكي.

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

يمكن الاطلاع على التوثيق الكامل هنا: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). يمكنك أيضًا إضافة مخرجات التسجيل إلى ملف الربط، انظر هنا (opens in a new tab).

1import { Bet, Player } from "../generated/schema"
2import { PlacedBet } from "../generated/GameContract/GameContract"
3
4export function handleNewBet(event: PlacedBet): void {
5 let player = Player.load(event.transaction.from.toHex())
6
7 if (player == null) {
8 // أنشئ إذا لم يكن موجودًا بعد
9 player = new Player(event.transaction.from.toHex())
10 player.bets = new Array<string>(0)
11 player.totalPlayedCount = 0
12 player.hasWonCount = 0
13 player.hasLostCount = 0
14 }
15
16 let bet = new Bet(
17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()
18 )
19 bet.player = player.id
20 bet.playerHasWon = event.params.hasWon
21 bet.time = event.block.timestamp
22 bet.save()
23
24 player.totalPlayedCount++
25 if (event.params.hasWon) {
26 player.hasWonCount++
27 } else {
28 player.hasLostCount++
29 }
30
31 // حدّث المصفوفة هكذا
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
إظهار الكل

استخدامه في واجهة التطبيق

باستخدام شيء مثل Apollo Boost، يمكنك بسهولة دمج ذا غراف في تطبيق رياكت اللامركزي (أو Apollo-Vue). خاصة عند استخدام رياكت hooks وApollo، فإن جلب البيانات بسيط مثل كتابة استعلام جراف كيو إل واحد في المكون الخاص بك. قد يبدو الإعداد النموذجي كما يلي:

1// انظر جميع الرسوم البيانية الفرعية: https://thegraph.com/explorer/
2const client = new ApolloClient({
3 uri: "{{ subgraphUrl }}",
4})
5
6ReactDOM.render(
7 <ApolloProvider client={client}>
8 <App />
9 </ApolloProvider>,
10 document.getElementById("root")
11)
إظهار الكل

والآن يمكننا كتابة استعلام مثل هذا على سبيل المثال. سيؤدي هذا إلى جلب

  • كم مرة فاز المستخدم الحالي
  • كم مرة خسر المستخدم الحالي
  • قائمة من الطوابع الزمنية مع جميع رهاناته السابقة

كل ذلك في طلب واحد إلى خادم جراف كيو إل.

1const myGraphQlQuery = gql`
2 players(where: { id: $currentUser }) {
3 totalPlayedCount
4 hasWonCount
5 hasLostCount
6 bets {
7 time
8 }
9 }
10`
11
12const { loading, error, data } = useQuery(myGraphQlQuery)
13
14React.useEffect(() => {
15 if (!loading && !error && data) {
16 console.log({ data })
17 }
18}, [loading, error, data])
إظهار الكل

Magic

لكننا نفتقد قطعة أخيرة من اللغز وهي الخادم. يمكنك إما تشغيله بنفسك أو استخدام الخدمة المستضافة.

خادم ذا غراف

مستكشف ذا غراف: الخدمة المستضافة

أسهل طريقة هي استخدام الخدمة المستضافة. اتبع التعليمات هنا (opens in a new tab) لنشر subgraph. بالنسبة للعديد من المشاريع، يمكنك في الواقع العثور على subgraphs موجودة في المستكشف (opens in a new tab).

ذا غراف-Explorer

تشغيل عقدتك الخاصة

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

المستقبل اللامركزي

يدعم جراف كيو إل التدفقات أيضًا للأحداث الواردة حديثًا. هذه مدعومة على the graph من خلال Substreams (opens in a new tab) وهي حاليًا في إصدار تجريبي مفتوح.

في 2021 (opens in a new tab) بدأ ذا غراف انتقاله إلى شبكة فهرسة لامركزية. يمكنك قراءة المزيد حول بنية شبكة الفهرسة اللامركزية هذه هنا (opens in a new tab).

هناك جانبان رئيسيان هما:

  1. يدفع المستخدمون للمفهرسين مقابل الاستعلامات.
  2. يقوم المفهرسون بتخزين حصص رموز ذا غراف (GRT).

آخر تحديث للصفحة: 26 فبراير 2026

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