From f144d932cdecc4a11c86ad4aed3c8b850fe26a07 Mon Sep 17 00:00:00 2001 From: prathamesh0 Date: Wed, 25 May 2022 18:43:53 +0530 Subject: [PATCH] Implement graphql query to get header with transactions --- pkg/eth/cid_retriever.go | 85 ++++++++++++++++++++++- pkg/graphql/graphql.go | 142 ++++++++++++++++++++++++++++++++++++++- pkg/graphql/schema.go | 72 ++++++++++++++------ 3 files changed, 277 insertions(+), 22 deletions(-) diff --git a/pkg/eth/cid_retriever.go b/pkg/eth/cid_retriever.go index bcebcde7..0872d76d 100644 --- a/pkg/eth/cid_retriever.go +++ b/pkg/eth/cid_retriever.go @@ -576,7 +576,7 @@ func (ecr *CIDRetriever) RetrieveBlockByNumber(blockNumber int64) (models.Header // RetrieveHeaderCIDByHash returns the header for the given block hash func (ecr *CIDRetriever) RetrieveHeaderCIDByHash(tx *sqlx.Tx, blockHash common.Hash) (models.HeaderModel, error) { log.Debug("retrieving header cids for block hash ", blockHash.String()) - pgStr := `SELECT block_hash,cid,mh_key FROM eth.header_cids + pgStr := `SELECT block_hash, CAST(block_number as Text), parent_hash, cid, mh_key, timestamp FROM eth.header_cids WHERE block_hash = $1` var headerCID models.HeaderModel return headerCID, tx.Get(&headerCID, pgStr, blockHash.String()) @@ -604,3 +604,86 @@ func (ecr *CIDRetriever) RetrieveReceiptCIDsByTxIDs(tx *sqlx.Tx, txHashes []stri var rctCIDs []models.ReceiptModel return rctCIDs, tx.Select(&rctCIDs, pgStr, pq.Array(txHashes)) } + +func (ecr *CIDRetriever) RetrieveHeaderAndTxCIDsByBlockNumber(blockNumber int64) ([]models.HeaderModel, [][]models.TxModel, error) { + log.Debug("retrieving header cids and tx cids for block number ", blockNumber) + + // Begin new db tx + tx, err := ecr.db.Beginx() + if err != nil { + return nil, nil, err + } + defer func() { + if p := recover(); p != nil { + shared.Rollback(tx) + panic(p) + } else if err != nil { + shared.Rollback(tx) + } else { + err = tx.Commit() + } + }() + + var headerCIDs []models.HeaderModel + headerCIDs, err = ecr.RetrieveHeaderCIDs(tx, blockNumber) + if err != nil { + log.Error("header cid retrieval error") + return nil, nil, err + } + if len(headerCIDs) < 1 { + return nil, nil, fmt.Errorf("header cid retrieval error, no header CIDs found at block %d", blockNumber) + } + + var allTxCIDs [][]models.TxModel + for _, headerCID := range headerCIDs { + var txCIDs []models.TxModel + txCIDs, err = ecr.RetrieveTxCIDsByHeaderID(tx, headerCID.BlockHash) + if err != nil { + log.Error("tx cid retrieval error") + return nil, nil, err + } + allTxCIDs = append(allTxCIDs, txCIDs) + } + + return headerCIDs, allTxCIDs, nil +} + +func (ecr *CIDRetriever) RetrieveHeaderAndTxCIDsByBlockHash(blockHash common.Hash) (models.HeaderModel, []models.TxModel, error) { + log.Debug("retrieving header cid and tx cids for block hash ", blockHash.String()) + + // Begin new db tx + tx, err := ecr.db.Beginx() + if err != nil { + return models.HeaderModel{}, nil, err + } + defer func() { + if p := recover(); p != nil { + shared.Rollback(tx) + panic(p) + } else if err != nil { + shared.Rollback(tx) + } else { + err = tx.Commit() + } + }() + + var headerCID models.HeaderModel + headerCID, err = ecr.RetrieveHeaderCIDByHash(tx, blockHash) + if err != nil { + log.Error("header cid retrieval error") + return models.HeaderModel{}, nil, err + } + if err != nil { + return models.HeaderModel{}, nil, err + } + fmt.Println("RetrieveHeaderAndTxCIDsByBlockHash", headerCID.ParentHash, headerCID.Timestamp) + + var txCIDs []models.TxModel + txCIDs, err = ecr.RetrieveTxCIDsByHeaderID(tx, headerCID.BlockHash) + if err != nil { + log.Error("tx cid retrieval error") + return models.HeaderModel{}, nil, err + } + + return headerCID, txCIDs, nil +} diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 465bf286..588261e6 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -22,6 +22,8 @@ import ( "context" "database/sql" "errors" + "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -32,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/statediff/indexer/models" "github.com/vulcanize/ipld-eth-server/v3/pkg/eth" ) @@ -1017,7 +1020,7 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { return nil, err } - if bytes.Compare(rlpValue, eth.EmptyNodeValue) == 0 { + if bytes.Equal(rlpValue, eth.EmptyNodeValue) { return &StorageResult{value: eth.EmptyNodeValue, cid: cid, ipldBlock: ipldBlock}, nil } @@ -1122,3 +1125,140 @@ func decomposeGQLLogs(logCIDs []eth.LogResult) []logsCID { return logs } + +type EthTransactionCid struct { + cid string + txHash string + index int32 + src string + dst string +} + +func (t EthTransactionCid) Cid(ctx context.Context) string { + return t.cid +} + +func (t EthTransactionCid) TxHash(ctx context.Context) string { + return t.txHash +} + +func (t EthTransactionCid) Index(ctx context.Context) int32 { + return t.index +} + +func (t EthTransactionCid) Src(ctx context.Context) string { + return t.src +} + +func (t EthTransactionCid) Dst(ctx context.Context) string { + return t.dst +} + +type EthTransactionCidsConnection struct { + nodes []*EthTransactionCid +} + +func (transactionCIDResult EthTransactionCidsConnection) Nodes(ctx context.Context) []*EthTransactionCid { + return transactionCIDResult.nodes +} + +type EthHeaderCid struct { + cid string + blockNumber hexutil.Uint64 + blockHash string + parentHash string + timestamp hexutil.Uint64 + transactions []*EthTransactionCid +} + +func (h EthHeaderCid) Cid(ctx context.Context) string { + return h.cid +} + +func (h EthHeaderCid) BlockNumber(ctx context.Context) hexutil.Uint64 { + return h.blockNumber +} + +func (h EthHeaderCid) BlockHash(ctx context.Context) string { + return h.blockHash +} + +func (h EthHeaderCid) ParentHash(ctx context.Context) string { + return h.parentHash +} + +func (h EthHeaderCid) Timestamp(ctx context.Context) hexutil.Uint64 { + return h.timestamp +} + +func (h EthHeaderCid) EthTransactionCidsByHeaderId(ctx context.Context) EthTransactionCidsConnection { + return EthTransactionCidsConnection{nodes: h.transactions} +} + +type EthHeaderCidsConnection struct { + nodes []*EthHeaderCid +} + +func (headerCIDResult EthHeaderCidsConnection) Nodes(ctx context.Context) []*EthHeaderCid { + return headerCIDResult.nodes +} + +type EthHeaderCidCondition struct { + BlockNumber *hexutil.Big + BlockHash *string +} + +func (r *Resolver) AllEthHeaderCids(ctx context.Context, args struct { + Condition *EthHeaderCidCondition +}) (*EthHeaderCidsConnection, error) { + var headerCIDs []models.HeaderModel + var allTxCIDs [][]models.TxModel + var err error + if args.Condition.BlockHash != nil { + headerCID, txCIDs, err := r.backend.Retriever.RetrieveHeaderAndTxCIDsByBlockHash(common.HexToHash(*args.Condition.BlockHash)) + if err != nil { + return nil, err + } + + headerCIDs = append(headerCIDs, headerCID) + allTxCIDs = append(allTxCIDs, txCIDs) + } else if args.Condition.BlockNumber != nil { + headerCIDs, allTxCIDs, err = r.backend.Retriever.RetrieveHeaderAndTxCIDsByBlockNumber(args.Condition.BlockNumber.ToInt().Int64()) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("provide block number or block hash") + } + + var resultNodes []*EthHeaderCid + for idx, headerCID := range headerCIDs { + blockNumber := new(big.Int) + blockNumber.SetString(headerCID.BlockNumber, 10) + + ethHeaderCidNode := EthHeaderCid{ + cid: headerCID.CID, + blockNumber: hexutil.Uint64(blockNumber.Uint64()), + blockHash: headerCID.BlockHash, + parentHash: headerCID.ParentHash, + timestamp: hexutil.Uint64(headerCID.Timestamp), + } + + txCIDs := allTxCIDs[idx] + for _, txCID := range txCIDs { + ethHeaderCidNode.transactions = append(ethHeaderCidNode.transactions, &EthTransactionCid{ + cid: txCID.CID, + txHash: txCID.TxHash, + index: int32(txCID.Index), + src: txCID.Src, + dst: txCID.Dst, + }) + } + + resultNodes = append(resultNodes, ðHeaderCidNode) + } + + return &EthHeaderCidsConnection{ + nodes: resultNodes, + }, nil +} diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index b70765ac..f85d489a 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -138,16 +138,16 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } @@ -258,16 +258,16 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } @@ -282,6 +282,36 @@ const schema string = ` ipldBlock: Bytes! } + input EthHeaderCidCondition { + blockNumber: BigInt + blockHash: String + } + + type EthTransactionCid { + cid: String! + txHash: String! + index: Int! + src: String! + dst: String! + } + + type EthTransactionCidsConnection { + nodes: [EthTransactionCid]! + } + + type EthHeaderCid { + cid: String! + blockNumber: Long! + blockHash: String! + parentHash: String! + timestamp: Long! + ethTransactionCidsByHeaderId: EthTransactionCidsConnection! + } + + type EthHeaderCidsConnection { + nodes: [EthHeaderCid]! + } + type Query { # Block fetches an Ethereum block by number or by hash. If neither is # supplied, the most recent known block is returned. @@ -302,5 +332,7 @@ const schema string = ` # Get contract logs by block hash and contract address. getLogs(blockHash: Bytes32!, contract: Address): [Log!] + + allEthHeaderCids(condition: EthHeaderCidCondition): EthHeaderCidsConnection } `