From fe7329c2844a5a685c6a66bf350e8dca04fdbf4b Mon Sep 17 00:00:00 2001 From: prathamesh0 Date: Thu, 26 May 2022 19:57:23 +0530 Subject: [PATCH] Add custom implementation for graphql scalar BigInt --- pkg/graphql/graphql.go | 22 +++++----- pkg/graphql/schema.go | 8 ++-- pkg/graphql/types.go | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 pkg/graphql/types.go diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 588261e6..44738003 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -23,7 +23,6 @@ import ( "database/sql" "errors" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -1164,10 +1163,10 @@ func (transactionCIDResult EthTransactionCidsConnection) Nodes(ctx context.Conte type EthHeaderCid struct { cid string - blockNumber hexutil.Uint64 + blockNumber BigInt blockHash string parentHash string - timestamp hexutil.Uint64 + timestamp BigInt transactions []*EthTransactionCid } @@ -1175,7 +1174,7 @@ func (h EthHeaderCid) Cid(ctx context.Context) string { return h.cid } -func (h EthHeaderCid) BlockNumber(ctx context.Context) hexutil.Uint64 { +func (h EthHeaderCid) BlockNumber(ctx context.Context) BigInt { return h.blockNumber } @@ -1187,7 +1186,7 @@ func (h EthHeaderCid) ParentHash(ctx context.Context) string { return h.parentHash } -func (h EthHeaderCid) Timestamp(ctx context.Context) hexutil.Uint64 { +func (h EthHeaderCid) Timestamp(ctx context.Context) BigInt { return h.timestamp } @@ -1204,7 +1203,7 @@ func (headerCIDResult EthHeaderCidsConnection) Nodes(ctx context.Context) []*Eth } type EthHeaderCidCondition struct { - BlockNumber *hexutil.Big + BlockNumber *BigInt BlockHash *string } @@ -1233,15 +1232,18 @@ func (r *Resolver) AllEthHeaderCids(ctx context.Context, args struct { var resultNodes []*EthHeaderCid for idx, headerCID := range headerCIDs { - blockNumber := new(big.Int) - blockNumber.SetString(headerCID.BlockNumber, 10) + var blockNumber BigInt + blockNumber.UnmarshalText([]byte(headerCID.BlockNumber)) + + var timestamp BigInt + timestamp.SetUint64(headerCID.Timestamp) ethHeaderCidNode := EthHeaderCid{ cid: headerCID.CID, - blockNumber: hexutil.Uint64(blockNumber.Uint64()), + blockNumber: blockNumber, blockHash: headerCID.BlockHash, parentHash: headerCID.ParentHash, - timestamp: hexutil.Uint64(headerCID.Timestamp), + timestamp: timestamp, } txCIDs := allTxCIDs[idx] diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index f85d489a..b8f5ef6d 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -25,8 +25,7 @@ const schema string = ` # An empty byte string is represented as '0x'. Byte strings must have an even number of hexadecimal nybbles. scalar Bytes # BigInt is a large integer. Input is accepted as either a JSON number or as a string. - # Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all - # 0x-prefixed hexadecimal. + # Input and output strings may be either decimal or 0x-prefixed hexadecimal depending upon the resolver implementation. scalar BigInt # Long is a 64 bit unsigned integer. scalar Long @@ -301,10 +300,10 @@ const schema string = ` type EthHeaderCid { cid: String! - blockNumber: Long! + blockNumber: BigInt! blockHash: String! parentHash: String! - timestamp: Long! + timestamp: BigInt! ethTransactionCidsByHeaderId: EthTransactionCidsConnection! } @@ -333,6 +332,7 @@ const schema string = ` # Get contract logs by block hash and contract address. getLogs(blockHash: Bytes32!, contract: Address): [Log!] + # PostGraphile alternative to get headers with transactions using block number or block hash. allEthHeaderCids(condition: EthHeaderCidCondition): EthHeaderCidsConnection } ` diff --git a/pkg/graphql/types.go b/pkg/graphql/types.go new file mode 100644 index 00000000..2756070d --- /dev/null +++ b/pkg/graphql/types.go @@ -0,0 +1,94 @@ +// VulcanizeDB +// Copyright © 2022 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package graphql + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type BigInt big.Int + +// ToInt converts b to a big.Int. +func (b *BigInt) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns value of b as a decimal string. +func (b *BigInt) String() string { + return b.ToInt().String() +} + +// SetUint64 sets b to x and returns x. +func (b *BigInt) SetUint64(x uint64) *BigInt { + var val big.Int + val.SetUint64(x) + *b = (BigInt)(val) + return b +} + +// MarshalText implements encoding.TextMarshaler +func (b BigInt) MarshalText() ([]byte, error) { + return []byte(b.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *BigInt) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 64 { + return hexutil.ErrBig256Range + } + + var val big.Int + val.SetString(string(input[:]), 10) + *b = (BigInt)(val) + return nil +} + +// ImplementsGraphQLType returns true if BigInt implements the provided GraphQL type. +func (b BigInt) ImplementsGraphQLType(name string) bool { return name == "BigInt" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *BigInt) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + var num big.Int + num.SetInt64(int64(input)) + *b = BigInt(num) + default: + err = fmt.Errorf("unexpected type %T for BigInt", input) + } + return err +} + +func checkNumberText(input []byte) (raw []byte, err error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if len(input) > 1 && input[0] == '0' { + return nil, hexutil.ErrLeadingZero + } + return input, nil +}