Ana içeriğe geç

The Graph: Web3 veri sorgusunu düzeltme

solidity
akıllı kontratlar
sorgulama
the graph
react
Orta düzey
Markus Waas
6 Eylül 2020
7 dakikalık okuma

Bu kez, geçen yıl merkeziyetsiz uygulamalar geliştirmek için standart yığının esasen bir parçası haline gelen The Graph'e daha yakından bakacağız. Önce geleneksel yöntemlerle bunları nasıl yapacağımızı görelim...

The Graph olmasaydı...

O hâlde, göstermek amacıyla basit bir örnekle devam edelim. Hepimiz oyunları severiz, bu yüzden kullanıcıların bahis oynadığı basit bir oyun hayal edin:

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, "Aktarım başarısız");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Tümünü göster

Şimdi diyelim ki merkeziyetsiz uygulamamızda, toplam bahisleri, kaybedilen/kazanılan toplam oyunları görüntülemek ve birisi tekrar oynadığında bunu güncellemek istiyoruz. Yaklaşım şöyle olurdu:

  1. totalGamesPlayerWon'ı alın.
  2. totalGamesPlayerLost'u alın.
  3. BetPlaced olaylarına abone olun.

Sağda gösterildiği gibi Web3'teki olayı (opens in a new tab) dinleyebiliriz, ancak bu, epey bir durumu ele almayı gerektirir.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // olay tetiklendi
6})
7.on('changed', function(event) {
8 // olay tekrar kaldırıldı
9})
10.on('error', function(error, receipt) {
11 // işlem reddedildi
12});
Tümünü göster

Bu durum, basit örneğimiz için hâlâ bir dereceye kadar kabul edilebilir. Ama diyelim ki şimdi sadece mevcut oyuncu için kaybedilen/kazanılan bahis miktarlarını görüntülemek istiyoruz. Pekala, şansımız yaver gitmedi, bu değerleri depolayan ve onları çeken yeni bir sözleşme dağıtmanız daha iyi olur. Ve şimdi çok daha karmaşık bir akıllı sözleşme ve merkeziyetsiz uygulama hayal edin, işler çabucak karışabilir.

Öyle Basitçe Sorgulama Yapılmaz

Bunun neden optimal olmadığını görebilirsiniz:

  • Halihazırda dağıtılmış sözleşmeler için çalışmaz.
  • Bu değerleri depolamak için ek gaz maliyetleri.
  • Bir Ethereum düğümünden veri çekmek için başka bir çağrı gerektirir.

Bu yeterince iyi değil

Şimdi daha iyi bir çözüme bakalım.

GraphQL ile tanıştırayım

İlk olarak, Facebook tarafından tasarlanan ve uygulanan GraphQL'den bahsedelim. Geleneksel REST API modeline aşina olabilirsiniz. Şimdi bunun yerine, tam olarak istediğiniz veriler için bir sorgu yazabildiğinizi hayal edin:

GraphQL API ve REST API Karşılaştırması

Bu iki görsel, GraphQL'in özünü büyük ölçüde yansıtıyor. Sağdaki sorguyla tam olarak hangi verileri istediğimizi tanımlayabiliriz, böylece her şeyi tek bir istekte alırız ve ihtiyacımız olandan fazlasını da almayız. Bir GraphQL sunucusu, gerekli tüm verilerin çekilmesini yönetir, bu nedenle ön yüz tarafında kullanımı inanılmaz derecede kolaydır. İlgileniyorsanız, bu bağlantı (opens in a new tab) sunucunun bir sorguyu tam olarak nasıl ele aldığını güzel bir şekilde açıklıyor.

Şimdi bu bilgiyle, nihayet blokzincir alanına ve The Graph'e geçelim.

The Graph nedir?

Blokzincir merkeziyetsiz bir veritabanıdır, ancak alışılmışın aksine, bu veritabanı için bir sorgu dilimiz yoktur. Veri almak için çözümler ya zahmetlidir ya da tamamen imkansızdır. The Graph, blokzincir verilerini dizine eklemek ve sorgulamak için merkeziyetsiz bir protokoldür. Tahmin etmişsinizdir, sorgu dili olarak GraphQL kullanıyor.

The Graph

Bir şeyi anlamanın en iyi yolu her zaman örneklerdir, bu yüzden GameContract örneğimiz için The Graph'i kullanalım.

Subgraph nasıl oluşturulur

Verilerin nasıl dizine ekleneceğinin tanımına subgraph denir. Üç bileşen gerektirir:

  1. Manifest (subgraph.yaml)
  2. Şema (schema.graphql)
  3. Eşleme (mapping.ts)

Manifest (subgraph.yaml)

Manifest, yapılandırma dosyamızdır ve şunları tanımlar:

  • hangi akıllı sözleşmelerin dizine ekleneceği (adres, ağ, ABI...)
  • hangi olayların dinleneceği
  • fonksiyon çağrıları veya bloklar gibi dinlenecek diğer şeyler
  • çağrılan eşleme fonksiyonları (aşağıdaki mapping.ts'ye bakın)

Burada birden fazla sözleşme ve işleyici tanımlayabilirsiniz. Tipik bir kurulum, Hardhat projesi içinde kendi deposu olan bir subgraph klasörüne sahip olacaktır. Ardından ABI'ye kolayca başvurabilirsiniz.

Kolaylık sağlamak için mustache gibi bir şablon aracı da kullanmak isteyebilirsiniz. Ardından bir subgraph.template.yaml oluşturur ve en son dağıtımlara göre adresleri eklersiniz. Daha gelişmiş bir örnek kurulum için, örneğin Aave subgraph deposuna (opens in a new tab) bakın.

Tüm belgelere buradan (opens in a new tab) ulaşabilirsiniz.

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
Tümünü göster

Şema (schema.graphql)

Şema, GraphQL veri tanımıdır. Hangi varlıkların var olduğunu ve bunların türlerini tanımlamanıza olanak tanır. The Graph tarafından desteklenen türler şunlardır:

  • Bayt
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

İlişkileri tanımlamak için varlıkları tür olarak da kullanabilirsiniz. Örneğimizde, oyuncudan bahislere bire çok ilişki tanımlıyoruz. ! işareti, değerin boş olamayacağı anlamına gelir. Tüm belgelere buradan (opens in a new tab) ulaşabilirsiniz.

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}
Tümünü göster

Eşleme (mapping.ts)

The Graph'teki eşleme dosyası, gelen olayları varlıklara dönüştüren işlevlerimizi tanımlar. TypeScript'in bir alt kümesi olan AssemblyScript ile yazılmıştır. Bu, eşlemenin daha verimli ve taşınabilir yürütülmesi için WASM'de (WebAssembly) derlenebileceği anlamına gelir.

subgraph.yaml dosyasında adı geçen her fonksiyonu tanımlamanız gerekecek, bu nedenle bizim durumumuzda yalnızca bir taneye ihtiyacımız var: handleNewBet. İlk önce gönderici adresinden Player varlığını kimlik olarak yüklemeye çalışıyoruz. Eğer mevcut değilse, yeni bir varlık yaratır ve onu başlangıç değerleri ile doldururuz.

Sonrasında yeni bir Bet varlığı oluştururuz. Bunun kimliği, her zaman benzersiz bir değer sağlayan event.transaction.hash.toHex() + "-" + event.logIndex.toString() olacaktır. Birisi bir akıllı sözleşme aracılığıyla placeBet fonksiyonunu bir işlemde birkaç kez çağırıyor olabileceğinden, yalnızca hash değerini kullanmak yeterli değildir.

Son olarak Player varlığını tüm verilerle güncelleyebiliriz. Dizilere doğrudan gönderim yapılamaz, ancak burada gösterildiği gibi güncellenmesi gerekir. Bahse başvurmak için kimliği kullanırız. Ve bir varlığı saklamak için sonunda .save() gereklidir.

Tüm belgelere buradan erişilebilir: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). Ayrıca eşleme dosyasına günlük kaydı çıktısı da ekleyebilirsiniz, buraya (opens in a new tab) bakın.

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 // henüz mevcut değilse oluştur
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 // diziyi bu şekilde güncelle
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Tümünü göster

Ön Yüzde Kullanımı

Apollo Boost gibi bir şey kullanarak The Graph'i React merkeziyetsiz uygulamanıza (veya Apollo-Vue) kolayca entegre edebilirsiniz. Özellikle React kancaları ve Apollo kullanırken veri almak, bileşeninize tek bir GraphQL sorgusu yazmak kadar basittir. Tipik bir kurulum şöyle görünebilir:

1// Tüm subgraph'ları gör: 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)
Tümünü göster

Ve şimdi örneğin şöyle bir sorgu yazabiliriz. Bu bize şunları getirecektir:

  • mevcut kullanıcının kaç kez kazandığını
  • mevcut kullanıcının kaç kez kaybettiğini
  • önceki tüm bahisleriyle birlikte zaman damgalarının bir listesini

Hepsi GraphQL sunucusuna tek bir istekte.

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])
Tümünü göster

Sihir

Ama yapbozun son bir parçası eksik: sunucu. Kendiniz çalıştırabilir veya barındırılan hizmeti kullanabilirsiniz.

The Graph sunucusu

Graph Explorer: Barındırılan hizmet

En kolay yol, barındırılan hizmeti kullanmaktır. Bir subgraph dağıtmak için buradaki (opens in a new tab) talimatları izleyin. Birçok proje için, mevcut subgraph'ları explorer (opens in a new tab) içinde bulabilirsiniz.

The Graph-Explorer

Kendi düğümünüzü çalıştırma

Alternatif olarak kendi düğümünüzü çalıştırabilirsiniz. Belgeler burada (opens in a new tab). Bunu yapmanın bir nedeni, barındırılan hizmet tarafından desteklenmeyen bir ağ kullanmak olabilir. Şu anda desteklenen ağlar burada bulunabilir (opens in a new tab).

Merkeziyetsiz gelecek

GraphQL, yeni gelen olaylar için akışları da destekler. Bunlar şu anda açık beta sürümünde olan Substreams (opens in a new tab) aracılığıyla grafikte desteklenmektedir.

The Graph, 2021 (opens in a new tab) yılında merkeziyetsiz bir dizin oluşturma ağına geçişine başladı. Bu merkeziyetsiz dizin oluşturma ağının mimarisi hakkında daha fazla bilgiyi buradan (opens in a new tab) okuyabilirsiniz.

İki temel unsur şunlardır:

  1. Kullanıcılar sorgular için dizin oluşturuculara ödeme yapar.
  2. Dizin oluşturucular, Graph Jetonlarını (GRT) stake eder.

Sayfanın son güncellenmesi: 26 Şubat 2026

Bu rehber yararlı oldu mu?