The Graph: Web3 veri sorgusunu düzeltme
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;23contract Game {4 uint256 totalGamesPlayerWon = 0;5 uint256 totalGamesPlayerLost = 0;6 event BetPlaced(address player, uint256 value, bool hasWon);78 function placeBet() external payable {9 bool hasWon = evaluateBetForPlayer(msg.sender);1011 if (hasWon) {12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');13 require(success, "Transfer failed");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }1819 emit BetPlaced(msg.sender, msg.value, hasWon);20 }21}Tümünü gösterKopyala
Ş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:
totalGamesPlayerWon
al.totalGamesPlayerLost
al.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: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // event fired6})7.on('changed', function(event) {8 // event was removed again9})10.on('error', function(error, receipt) {11 // tx rejected12});Tümünü gösterKopyala
Ş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.
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.
Ş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:
İ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.
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:
- Manifesto (
subgraph.yaml
) - Şema (
schema.graphql
) - 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, 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.12description: Placing Bets on Ethereum3repository: - GitHub link -4schema:5 file: ./schema.graphql6dataSources:7 - kind: ethereum/contract8 name: GameContract9 network: mainnet10 source:11 address: '0x2E6454...cf77eC'12 abi: GameContract13 startBlock: 617524414 mapping:15 kind: ethereum/events16 apiVersion: 0.0.117 language: wasm/assemblyscript18 entities:19 - GameContract20 abis:21 - name: GameContract22 file: ../build/contracts/GameContract.json23 eventHandlers:24 - event: PlacedBet(address,uint256,bool)25 handler: handleNewBet26 file: ./src/mapping.tsTü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}78type Player @entity {9 id: ID!10 totalPlayedCount: Int11 hasWonCount: Int12 hasLostCount: Int13 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"34export function handleNewBet(event: PlacedBet): void {5 let player = Player.load(event.transaction.from.toHex())67 if (player == null) {8 // create if doesn't exist yet9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }1516 let bet = new Bet(17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()18 )19 bet.player = player.id20 bet.playerHasWon = event.params.hasWon21 bet.time = event.block.timestamp22 bet.save()2324 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }3031 // update array like this32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 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})56ReactDOM.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 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }9 }10`1112const { loading, error, data } = useQuery(myGraphQlQuery)1314React.useEffect(() => {15 if (!loading && !error && data) {16 console.log({ data })17 }18}, [loading, error, data])Tümünü göster
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.
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:
- https://thegraph.com/blog/the-graph-network-in-depth-part-1(opens in a new tab)
- https://thegraph.com/blog/the-graph-network-in-depth-part-2(opens in a new tab)
İki kilit noktası şunlardır:
- Kullanıcılar, sorgular için endeksleyicilere ödeme yapacaklar.
- Endeksleyiciler, Graph Token'larını (GRT) stake edecekler.
Son düzenleme: @sumitvekariya(opens in a new tab), 29 Ağustos 2024