Ana içeriğe geç

The Graph: Web3 veri sorgusunu düzeltme

solidityakıllı kontratlarsorgulamathe graphcreate-eth-appreact
Orta düzey
Markus Waas
soliditydeveloper.com(opens in a new tab)
6 Eylül 2020
7 dakikalık okuma minute read

Bu kez, geçen yıl merkeziyetsiz uygulamalar geliştirmeye yönelik standart yığının asli bir parçası hâline gelen 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 örnekleme amacıyla basit bir örnekle gidelim. 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, "Transfer failed");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Tümünü göster
Kopyala

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

  1. totalGamesPlayerWon al.
  2. totalGamesPlayerLost al.
  3. BetPlaced olaylarına abone ol.

Sağda gösterildiği gibi etkinliği Web3(opens in a new tab)'te dinleyebiliriz ancak bu, birkaç durumu çözmeyi gerektiriyor.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // event fired
6})
7.on('changed', function(event) {
8 // event was removed again
9})
10.on('error', function(error, receipt) {
11 // tx rejected
12});
Tümünü göster
Kopyala

Şimdi bu, basit örneğimiz için hâlâ biraz fazla sofistike. Ama diyelim ki artık sadece mevcut oyuncu için kaybedilen/kazanılan bahis miktarlarını görüntülemek istiyoruz. Şansımız kalmadı, bu değerleri depolayan ve getiren yeni bir sözleşme yapsan iyi olur. Şimdi çok daha karmaşık bir akıllı sözleşme ve merkeziyetsiz uygulama hayal edin, işler hızla karışabilir.

Sorgulamak Öyle Kolay Değil

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

  • Zaten dağıtılmış sözleşmeler için çalışmaz.
  • Bu değerleri saklamak için ekstra gaz maliyetleri.
  • Bir Ethereum düğümünün verilerini almak için başka bir çağrı gerektirir.

Bu, yeterince iyi değil

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

Sizi GraphQL ile tanıştırayım

İlk önce, orijinal 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 yazabileceğinizi hayal edin:

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

(opens in a new tab)

İki görüntü, GraphQL'in özünü hemen hemen yakalar. Sağdaki sorgu ile tam olarak hangi verileri istediğimizi tanımlayabiliriz, böylece orada her şeyi tek bir istekte alırız ve tam olarak ihtiyacımız olandan fazlasını elde ederiz. Bir GraphQL sunucusu, gerekli tüm verilerin alınmasını yönetir, bu nedenle ön uç tüketici tarafının kullanımı inanılmaz derecede kolaydır. Bu, ilgileniyorsanız sunucunun bir sorguyu tam olarak nasıl ele aldığının güzel bir açıklamasıdır(opens in a new tab).

Şimdi bu bilgiyle, nihayet blok zinciri alanına ve The Graph'a geçelim.

The Graph nedir?

Blok zinciri, merkeziyetsiz bir veri tabanıdır ancak normalden farklı olarak bu veri tabanı için bir sorgu dilimiz yoktur. Verileri almak için çözümler, zahmetli veya tamamen imkansızdır. The Graph, blok zinciri verilerini endekslemek ve sorgulamak için merkeziyetsiz bir protokoldür. Tahmin etmişsinizdir, sorgulama dili olarak GraphQL kullanıyor.

The Graph

Bir şeyleri anlamanın en iyi yolu örnekler olduğu için GameContract örneğimiz için The Graph'i kullanalım.

Bir Alt grafik nasıl oluşturulur

Verilerin nasıl endeksleneceğinin tanımına alt grafik denir. Üç bileşen gerektirir:

  1. Manifesto (subgraph.yaml)
  2. Şema (schema.graphql)
  3. Eşleştirme (mapping.ts)

Manifesto (subgraph.yaml)

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

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

Burada birden fazla sözleşme ve işleyici tanımlayabilirsiniz. Tipik bir kurulum, Truffle/Hardhat projesinin içinde kendi deposuna sahip bir alt grafik 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ş örnek bir kurulum için, örnek olarak Aave alt grafik deposuna(opens in a new tab) bakınız.

Ayrıca belgelerin tamamına buradan(opens in a new tab) erişilebilir.

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 izin verecektir. The Graph tarafından desteklenen veri türleri ş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 "1'e çok" ilişkisi tanımladık. "!", değerin boş olamayacağı anlamına gelir. Belgelerin tamamına buradan(opens in a new tab) erişilebilir.

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şleştirme (mapping.ts)

Graph'teki eşleştirme dosyası, gelen olayları varlıklara dönüştüren fonksiyonlarımzı tanımlar. TypeScript'in bir alt kümesi olan AssemblyScript ile yazılmıştır. Bu, eşleştirmenin 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ı id 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. Diziler doğrudan aktarılamaz, ancak burada gösterildiği gibi güncellenmesi gerekir. Bahise başvurmak için id'yi kullanırız. Ve bir varlığı saklamak için sonunda .save() gereklidir.

Belgelerin tamamına buradan erişilebilir: https://thegraph.com/docs/define-a-subgraph#writing-mappings(opens in a new tab). Ayrıca eşleştirme dosyasında kayıt çıktısı da ekleyebilirsiniz, buraya(opens in a new tab) göz atı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 // create if doesn't exist yet
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 // update array like this
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Tümünü göster

Bunu Ön Uçta kullanma

Apollo Boost gibi bir şey kullanarak 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// See all subgraphs: 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ı alacaktır:

  • 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

GraphQL sunucusuna hepsi tek yerde istek.

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

Magic

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

The Graph sunucusu

Graph Arayıcısı: Barındırılan hizmet

En kolay yol, barındırılan hizmeti kullanmaktır. Bir alt grafik dağıtmak için buradaki(opens in a new tab) yönergeleri takip edin. Birçok proje için mevcut alt grafikleri explorer(opens in a new tab)'da bulabilirsiniz.

Graph-Arayıcısı

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

Alternatif olarak kendi düğümünüzü çalıştırabilirsiniz. Dosyalar buradadır(opens in a new tab). Bunu yapmanın bir nedeni, barındırılan hizmet tarafından desteklenmeyen bir ağ kullanmak olabilir. Şu anda Ana Ağ, Kovan, Rinkeby, Ropsten, Goerli, PoA-Core, xDAI ve Sokol desteklenmektedir.

Merkeziyetsiz gelecek

GraphQL, yeni gelen olaylar için akışları da destekler. Bu henüz The Graph tarafından tam olarak desteklenmiyor, ancak yakında yayınlanacak.

Amcal merkeziyetsizleştirme eksik bir özelliktir. Graph, sonunda tamamen merkeziyetsiz bir protokol hâline gelmek için geleceğe yönelik planlara sahiptir. Planı daha ayrıntılı açıklayan iki harika makale:

İki kilit noktası şunlardır:

  1. Kullanıcılar, sorgular için endeksleyicilere ödeme yapacaklar.
  2. Endeksleyiciler, Graph Token'larını (GRT) stake edecekler.

Son düzenleme: @kaanmetu(opens in a new tab), 23 Kasım 2023

Bu rehber yararlı oldu mu?