Patch for concurrent iterator & others (onto v1.11.6) #386
@ -24,6 +24,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -80,12 +81,26 @@ type Account struct {
|
|||||||
r *Resolver
|
r *Resolver
|
||||||
address common.Address
|
address common.Address
|
||||||
blockNrOrHash rpc.BlockNumberOrHash
|
blockNrOrHash rpc.BlockNumberOrHash
|
||||||
|
state *state.StateDB
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// getState fetches the StateDB object for an account.
|
// getState fetches the StateDB object for an account.
|
||||||
func (a *Account) getState(ctx context.Context) (*state.StateDB, error) {
|
func (a *Account) getState(ctx context.Context) (*state.StateDB, error) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.state != nil {
|
||||||
|
return a.state, nil
|
||||||
|
}
|
||||||
state, _, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
|
state, _, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
|
||||||
return state, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.state = state
|
||||||
|
// Cache the state object. This is done so that concurrent resolvers
|
||||||
|
// don't have to fetch the object from DB individually.
|
||||||
|
a.state.GetOrNewStateObject(a.address)
|
||||||
|
return a.state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) Address(ctx context.Context) (common.Address, error) {
|
func (a *Account) Address(ctx context.Context) (common.Address, error) {
|
||||||
@ -184,32 +199,39 @@ func (at *AccessTuple) StorageKeys(ctx context.Context) []common.Hash {
|
|||||||
// Transaction represents an Ethereum transaction.
|
// Transaction represents an Ethereum transaction.
|
||||||
// backend and hash are mandatory; all others will be fetched when required.
|
// backend and hash are mandatory; all others will be fetched when required.
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
r *Resolver
|
r *Resolver
|
||||||
hash common.Hash
|
hash common.Hash // Must be present after initialization
|
||||||
|
mu sync.Mutex
|
||||||
|
// mu protects following resources
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
block *Block
|
block *Block
|
||||||
index uint64
|
index uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve returns the internal transaction object, fetching it if needed.
|
// resolve returns the internal transaction object, fetching it if needed.
|
||||||
func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) {
|
// It also returns the block the tx blongs to, unless it is a pending tx.
|
||||||
if t.tx == nil {
|
func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block, error) {
|
||||||
// Try to return an already finalized transaction
|
t.mu.Lock()
|
||||||
tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash)
|
defer t.mu.Unlock()
|
||||||
if err == nil && tx != nil {
|
if t.tx != nil {
|
||||||
t.tx = tx
|
return t.tx, t.block, nil
|
||||||
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
|
|
||||||
t.block = &Block{
|
|
||||||
r: t.r,
|
|
||||||
numberOrHash: &blockNrOrHash,
|
|
||||||
}
|
|
||||||
t.index = index
|
|
||||||
return t.tx, nil
|
|
||||||
}
|
|
||||||
// No finalized transaction, try to retrieve it from the pool
|
|
||||||
t.tx = t.r.backend.GetPoolTransaction(t.hash)
|
|
||||||
}
|
}
|
||||||
return t.tx, nil
|
// Try to return an already finalized transaction
|
||||||
|
tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash)
|
||||||
|
if err == nil && tx != nil {
|
||||||
|
t.tx = tx
|
||||||
|
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
|
||||||
|
t.block = &Block{
|
||||||
|
r: t.r,
|
||||||
|
numberOrHash: &blockNrOrHash,
|
||||||
|
hash: blockHash,
|
||||||
|
}
|
||||||
|
t.index = index
|
||||||
|
return t.tx, t.block, nil
|
||||||
|
}
|
||||||
|
// No finalized transaction, try to retrieve it from the pool
|
||||||
|
t.tx = t.r.backend.GetPoolTransaction(t.hash)
|
||||||
|
return t.tx, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Hash(ctx context.Context) common.Hash {
|
func (t *Transaction) Hash(ctx context.Context) common.Hash {
|
||||||
@ -217,7 +239,7 @@ func (t *Transaction) Hash(ctx context.Context) common.Hash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) {
|
func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Bytes{}, err
|
return hexutil.Bytes{}, err
|
||||||
}
|
}
|
||||||
@ -225,7 +247,7 @@ func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) {
|
func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -233,7 +255,7 @@ func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, block, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Big{}, err
|
return hexutil.Big{}, err
|
||||||
}
|
}
|
||||||
@ -241,8 +263,8 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
|||||||
case types.AccessListTxType:
|
case types.AccessListTxType:
|
||||||
return hexutil.Big(*tx.GasPrice()), nil
|
return hexutil.Big(*tx.GasPrice()), nil
|
||||||
case types.DynamicFeeTxType:
|
case types.DynamicFeeTxType:
|
||||||
if t.block != nil {
|
if block != nil {
|
||||||
if baseFee, _ := t.block.BaseFeePerGas(ctx); baseFee != nil {
|
if baseFee, _ := block.BaseFeePerGas(ctx); baseFee != nil {
|
||||||
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
||||||
return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())), nil
|
return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())), nil
|
||||||
}
|
}
|
||||||
@ -254,15 +276,15 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, error) {
|
func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, block, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Pending tx
|
// Pending tx
|
||||||
if t.block == nil {
|
if block == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
header, err := t.block.resolveHeader(ctx)
|
header, err := block.resolveHeader(ctx)
|
||||||
if err != nil || header == nil {
|
if err != nil || header == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -273,7 +295,7 @@ func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -288,7 +310,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -303,15 +325,15 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) {
|
func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, block, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Pending tx
|
// Pending tx
|
||||||
if t.block == nil {
|
if block == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
header, err := t.block.resolveHeader(ctx)
|
header, err := block.resolveHeader(ctx)
|
||||||
if err != nil || header == nil {
|
if err != nil || header == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -327,7 +349,7 @@ func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) {
|
func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Big{}, err
|
return hexutil.Big{}, err
|
||||||
}
|
}
|
||||||
@ -338,7 +360,7 @@ func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) {
|
func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -346,7 +368,7 @@ func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, error) {
|
func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -362,7 +384,7 @@ func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, error) {
|
func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -376,17 +398,20 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Block(ctx context.Context) (*Block, error) {
|
func (t *Transaction) Block(ctx context.Context) (*Block, error) {
|
||||||
if _, err := t.resolve(ctx); err != nil {
|
_, block, err := t.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return t.block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Index(ctx context.Context) (*int32, error) {
|
func (t *Transaction) Index(ctx context.Context) (*int32, error) {
|
||||||
if _, err := t.resolve(ctx); err != nil {
|
_, block, err := t.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t.block == nil {
|
// Pending tx
|
||||||
|
if block == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
index := int32(t.index)
|
index := int32(t.index)
|
||||||
@ -395,13 +420,15 @@ func (t *Transaction) Index(ctx context.Context) (*int32, error) {
|
|||||||
|
|
||||||
// getReceipt returns the receipt associated with this transaction, if any.
|
// getReceipt returns the receipt associated with this transaction, if any.
|
||||||
func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) {
|
func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) {
|
||||||
if _, err := t.resolve(ctx); err != nil {
|
_, block, err := t.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t.block == nil {
|
// Pending tx
|
||||||
|
if block == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
receipts, err := t.block.resolveReceipts(ctx)
|
receipts, err := block.resolveReceipts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -451,28 +478,25 @@ func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
|
func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
|
||||||
if _, err := t.resolve(ctx); err != nil {
|
_, block, err := t.resolve(ctx)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t.block == nil {
|
// Pending tx
|
||||||
|
if block == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if _, ok := t.block.numberOrHash.Hash(); !ok {
|
h, err := block.Hash(ctx)
|
||||||
header, err := t.r.backend.HeaderByNumberOrHash(ctx, *t.block.numberOrHash)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hash := header.Hash()
|
|
||||||
t.block.numberOrHash.BlockHash = &hash
|
|
||||||
}
|
}
|
||||||
return t.getLogs(ctx)
|
return t.getLogs(ctx, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLogs returns log objects for the given tx.
|
// getLogs returns log objects for the given tx.
|
||||||
// Assumes block hash is resolved.
|
// Assumes block hash is resolved.
|
||||||
func (t *Transaction) getLogs(ctx context.Context) (*[]*Log, error) {
|
func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, error) {
|
||||||
var (
|
var (
|
||||||
hash, _ = t.block.numberOrHash.Hash()
|
|
||||||
filter = t.r.filterSystem.NewBlockFilter(hash, nil, nil)
|
filter = t.r.filterSystem.NewBlockFilter(hash, nil, nil)
|
||||||
logs, err = filter.Logs(ctx)
|
logs, err = filter.Logs(ctx)
|
||||||
)
|
)
|
||||||
@ -494,7 +518,7 @@ func (t *Transaction) getLogs(ctx context.Context) (*[]*Log, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Type(ctx context.Context) (*int32, error) {
|
func (t *Transaction) Type(ctx context.Context) (*int32, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -503,7 +527,7 @@ func (t *Transaction) Type(ctx context.Context) (*int32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
|
func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -519,7 +543,7 @@ func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
|
func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Big{}, err
|
return hexutil.Big{}, err
|
||||||
}
|
}
|
||||||
@ -528,7 +552,7 @@ func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) {
|
func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Big{}, err
|
return hexutil.Big{}, err
|
||||||
}
|
}
|
||||||
@ -537,7 +561,7 @@ func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) {
|
func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Big{}, err
|
return hexutil.Big{}, err
|
||||||
}
|
}
|
||||||
@ -546,7 +570,7 @@ func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transaction) Raw(ctx context.Context) (hexutil.Bytes, error) {
|
func (t *Transaction) Raw(ctx context.Context) (hexutil.Bytes, error) {
|
||||||
tx, err := t.resolve(ctx)
|
tx, _, err := t.resolve(ctx)
|
||||||
if err != nil || tx == nil {
|
if err != nil || tx == nil {
|
||||||
return hexutil.Bytes{}, err
|
return hexutil.Bytes{}, err
|
||||||
}
|
}
|
||||||
@ -568,16 +592,20 @@ type BlockType int
|
|||||||
// when required.
|
// when required.
|
||||||
type Block struct {
|
type Block struct {
|
||||||
r *Resolver
|
r *Resolver
|
||||||
numberOrHash *rpc.BlockNumberOrHash
|
numberOrHash *rpc.BlockNumberOrHash // Field resolvers assume numberOrHash is always present
|
||||||
hash common.Hash
|
mu sync.Mutex
|
||||||
header *types.Header
|
// mu protects following resources
|
||||||
block *types.Block
|
hash common.Hash // Must be resolved during initialization
|
||||||
receipts []*types.Receipt
|
header *types.Header
|
||||||
|
block *types.Block
|
||||||
|
receipts []*types.Receipt
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve returns the internal Block object representing this block, fetching
|
// resolve returns the internal Block object representing this block, fetching
|
||||||
// it if necessary.
|
// it if necessary.
|
||||||
func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
if b.block != nil {
|
if b.block != nil {
|
||||||
return b.block, nil
|
return b.block, nil
|
||||||
}
|
}
|
||||||
@ -587,10 +615,10 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash)
|
b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash)
|
||||||
if b.block != nil && b.header == nil {
|
if b.block != nil {
|
||||||
b.header = b.block.Header()
|
b.hash = b.block.Hash()
|
||||||
if hash, ok := b.numberOrHash.Hash(); ok {
|
if b.header == nil {
|
||||||
b.hash = hash
|
b.header = b.block.Header()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.block, err
|
return b.block, err
|
||||||
@ -600,39 +628,39 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
|||||||
// if necessary. Call this function instead of `resolve` unless you need the
|
// if necessary. Call this function instead of `resolve` unless you need the
|
||||||
// additional data (transactions and uncles).
|
// additional data (transactions and uncles).
|
||||||
func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
|
func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.header != nil {
|
||||||
|
return b.header, nil
|
||||||
|
}
|
||||||
if b.numberOrHash == nil && b.hash == (common.Hash{}) {
|
if b.numberOrHash == nil && b.hash == (common.Hash{}) {
|
||||||
return nil, errBlockInvariant
|
return nil, errBlockInvariant
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if b.header == nil {
|
b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash)
|
||||||
if b.hash != (common.Hash{}) {
|
if err != nil {
|
||||||
b.header, err = b.r.backend.HeaderByHash(ctx, b.hash)
|
return nil, err
|
||||||
} else {
|
|
||||||
b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return b.header, err
|
if b.hash == (common.Hash{}) {
|
||||||
|
b.hash = b.header.Hash()
|
||||||
|
}
|
||||||
|
return b.header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveReceipts returns the list of receipts for this block, fetching them
|
// resolveReceipts returns the list of receipts for this block, fetching them
|
||||||
// if necessary.
|
// if necessary.
|
||||||
func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
|
func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
|
||||||
if b.receipts == nil {
|
b.mu.Lock()
|
||||||
hash := b.hash
|
defer b.mu.Unlock()
|
||||||
if hash == (common.Hash{}) {
|
if b.receipts != nil {
|
||||||
header, err := b.resolveHeader(ctx)
|
return b.receipts, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hash = header.Hash()
|
|
||||||
}
|
|
||||||
receipts, err := b.r.backend.GetReceipts(ctx, hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.receipts = receipts
|
|
||||||
}
|
}
|
||||||
return b.receipts, nil
|
receipts, err := b.r.backend.GetReceipts(ctx, b.hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.receipts = receipts
|
||||||
|
return receipts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) Number(ctx context.Context) (Long, error) {
|
func (b *Block) Number(ctx context.Context) (Long, error) {
|
||||||
@ -645,13 +673,8 @@ func (b *Block) Number(ctx context.Context) (Long, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
|
func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
|
||||||
if b.hash == (common.Hash{}) {
|
b.mu.Lock()
|
||||||
header, err := b.resolveHeader(ctx)
|
defer b.mu.Unlock()
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
b.hash = header.Hash()
|
|
||||||
}
|
|
||||||
return b.hash, nil
|
return b.hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,11 +728,18 @@ func (b *Block) Parent(ctx context.Context) (*Block, error) {
|
|||||||
if b.header == nil || b.header.Number.Uint64() < 1 {
|
if b.header == nil || b.header.Number.Uint64() < 1 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
num := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.header.Number.Uint64() - 1))
|
var (
|
||||||
|
num = rpc.BlockNumber(b.header.Number.Uint64() - 1)
|
||||||
|
hash = b.header.ParentHash
|
||||||
|
numOrHash = rpc.BlockNumberOrHash{
|
||||||
|
BlockNumber: &num,
|
||||||
|
BlockHash: &hash,
|
||||||
|
}
|
||||||
|
)
|
||||||
return &Block{
|
return &Block{
|
||||||
r: b.r,
|
r: b.r,
|
||||||
numberOrHash: &num,
|
numberOrHash: &numOrHash,
|
||||||
hash: b.header.ParentHash,
|
hash: hash,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -798,6 +828,7 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) {
|
|||||||
r: b.r,
|
r: b.r,
|
||||||
numberOrHash: &blockNumberOrHash,
|
numberOrHash: &blockNumberOrHash,
|
||||||
header: uncle,
|
header: uncle,
|
||||||
|
hash: uncle.Hash(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return &ret, nil
|
return &ret, nil
|
||||||
@ -820,17 +851,13 @@ func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
|
func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
|
||||||
h := b.hash
|
hash, err := b.Hash(ctx)
|
||||||
if h == (common.Hash{}) {
|
if err != nil {
|
||||||
header, err := b.resolveHeader(ctx)
|
return hexutil.Big{}, err
|
||||||
if err != nil {
|
|
||||||
return hexutil.Big{}, err
|
|
||||||
}
|
|
||||||
h = header.Hash()
|
|
||||||
}
|
}
|
||||||
td := b.r.backend.GetTd(ctx, h)
|
td := b.r.backend.GetTd(ctx, hash)
|
||||||
if td == nil {
|
if td == nil {
|
||||||
return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", b.hash)
|
return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", hash)
|
||||||
}
|
}
|
||||||
return hexutil.Big(*td), nil
|
return hexutil.Big(*td), nil
|
||||||
}
|
}
|
||||||
@ -948,6 +975,7 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block
|
|||||||
r: b.r,
|
r: b.r,
|
||||||
numberOrHash: &blockNumberOrHash,
|
numberOrHash: &blockNumberOrHash,
|
||||||
header: uncle,
|
header: uncle,
|
||||||
|
hash: uncle.Hash(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -997,15 +1025,11 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri
|
|||||||
if args.Filter.Topics != nil {
|
if args.Filter.Topics != nil {
|
||||||
topics = *args.Filter.Topics
|
topics = *args.Filter.Topics
|
||||||
}
|
}
|
||||||
hash := b.hash
|
|
||||||
if hash == (common.Hash{}) {
|
|
||||||
header, err := b.resolveHeader(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hash = header.Hash()
|
|
||||||
}
|
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
|
hash, err := b.Hash(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
filter := b.r.filterSystem.NewBlockFilter(hash, addresses, topics)
|
filter := b.r.filterSystem.NewBlockFilter(hash, addresses, topics)
|
||||||
|
|
||||||
// Run the filter and return all the logs
|
// Run the filter and return all the logs
|
||||||
@ -1015,12 +1039,6 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri
|
|||||||
func (b *Block) Account(ctx context.Context, args struct {
|
func (b *Block) Account(ctx context.Context, args struct {
|
||||||
Address common.Address
|
Address common.Address
|
||||||
}) (*Account, error) {
|
}) (*Account, error) {
|
||||||
if b.numberOrHash == nil {
|
|
||||||
_, err := b.resolveHeader(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Account{
|
return &Account{
|
||||||
r: b.r,
|
r: b.r,
|
||||||
address: args.Address,
|
address: args.Address,
|
||||||
@ -1063,12 +1081,6 @@ func (c *CallResult) Status() Long {
|
|||||||
func (b *Block) Call(ctx context.Context, args struct {
|
func (b *Block) Call(ctx context.Context, args struct {
|
||||||
Data ethapi.TransactionArgs
|
Data ethapi.TransactionArgs
|
||||||
}) (*CallResult, error) {
|
}) (*CallResult, error) {
|
||||||
if b.numberOrHash == nil {
|
|
||||||
_, err := b.resolve(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
|
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1088,12 +1100,6 @@ func (b *Block) Call(ctx context.Context, args struct {
|
|||||||
func (b *Block) EstimateGas(ctx context.Context, args struct {
|
func (b *Block) EstimateGas(ctx context.Context, args struct {
|
||||||
Data ethapi.TransactionArgs
|
Data ethapi.TransactionArgs
|
||||||
}) (Long, error) {
|
}) (Long, error) {
|
||||||
if b.numberOrHash == nil {
|
|
||||||
_, err := b.resolveHeader(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gas, err := ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, b.r.backend.RPCGasCap())
|
gas, err := ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, b.r.backend.RPCGasCap())
|
||||||
return Long(gas), err
|
return Long(gas), err
|
||||||
}
|
}
|
||||||
@ -1173,29 +1179,21 @@ func (r *Resolver) Block(ctx context.Context, args struct {
|
|||||||
Number *Long
|
Number *Long
|
||||||
Hash *common.Hash
|
Hash *common.Hash
|
||||||
}) (*Block, error) {
|
}) (*Block, error) {
|
||||||
var block *Block
|
var numberOrHash rpc.BlockNumberOrHash
|
||||||
if args.Number != nil {
|
if args.Number != nil {
|
||||||
if *args.Number < 0 {
|
if *args.Number < 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
number := rpc.BlockNumber(*args.Number)
|
number := rpc.BlockNumber(*args.Number)
|
||||||
numberOrHash := rpc.BlockNumberOrHashWithNumber(number)
|
numberOrHash = rpc.BlockNumberOrHashWithNumber(number)
|
||||||
block = &Block{
|
|
||||||
r: r,
|
|
||||||
numberOrHash: &numberOrHash,
|
|
||||||
}
|
|
||||||
} else if args.Hash != nil {
|
} else if args.Hash != nil {
|
||||||
numberOrHash := rpc.BlockNumberOrHashWithHash(*args.Hash, false)
|
numberOrHash = rpc.BlockNumberOrHashWithHash(*args.Hash, false)
|
||||||
block = &Block{
|
|
||||||
r: r,
|
|
||||||
numberOrHash: &numberOrHash,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
numberOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
numberOrHash = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
block = &Block{
|
}
|
||||||
r: r,
|
block := &Block{
|
||||||
numberOrHash: &numberOrHash,
|
r: r,
|
||||||
}
|
numberOrHash: &numberOrHash,
|
||||||
}
|
}
|
||||||
// Resolve the header, return nil if it doesn't exist.
|
// Resolve the header, return nil if it doesn't exist.
|
||||||
// Note we don't resolve block directly here since it will require an
|
// Note we don't resolve block directly here since it will require an
|
||||||
@ -1256,7 +1254,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has
|
|||||||
hash: args.Hash,
|
hash: args.Hash,
|
||||||
}
|
}
|
||||||
// Resolve the transaction; if it doesn't exist, return nil.
|
// Resolve the transaction; if it doesn't exist, return nil.
|
||||||
t, err := tx.resolve(ctx)
|
t, _, err := tx.resolve(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if t == nil {
|
} else if t == nil {
|
||||||
|
@ -270,7 +270,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGraphQLTransactionLogs(t *testing.T) {
|
func TestGraphQLConcurrentResolvers(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
@ -295,8 +295,9 @@ func TestGraphQLTransactionLogs(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer stack.Close()
|
defer stack.Close()
|
||||||
|
|
||||||
handler := newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) {
|
var tx *types.Transaction
|
||||||
tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)})
|
handler, chain := newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) {
|
||||||
|
tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)})
|
||||||
gen.AddTx(tx)
|
gen.AddTx(tx)
|
||||||
tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)})
|
tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)})
|
||||||
gen.AddTx(tx)
|
gen.AddTx(tx)
|
||||||
@ -307,18 +308,59 @@ func TestGraphQLTransactionLogs(t *testing.T) {
|
|||||||
if err := stack.Start(); err != nil {
|
if err := stack.Start(); err != nil {
|
||||||
t.Fatalf("could not start node: %v", err)
|
t.Fatalf("could not start node: %v", err)
|
||||||
}
|
}
|
||||||
query := `{block { transactions { logs { account { address } } } } }`
|
|
||||||
res := handler.Schema.Exec(context.Background(), query, "", map[string]interface{}{})
|
for i, tt := range []struct {
|
||||||
if res.Errors != nil {
|
body string
|
||||||
t.Fatalf("graphql query failed: %v", res.Errors)
|
want string
|
||||||
}
|
}{
|
||||||
have, err := json.Marshal(res.Data)
|
// Multiple txes race to get/set the block hash.
|
||||||
if err != nil {
|
{
|
||||||
t.Fatalf("failed to encode graphql response: %s", err)
|
body: "{block { transactions { logs { account { address } } } } }",
|
||||||
}
|
want: fmt.Sprintf(`{"block":{"transactions":[{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]}]}}`, dadStr, dadStr, dadStr, dadStr, dadStr, dadStr),
|
||||||
want := fmt.Sprintf(`{"block":{"transactions":[{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]}]}}`, dadStr, dadStr, dadStr, dadStr, dadStr, dadStr)
|
},
|
||||||
if string(have) != want {
|
// Multiple fields of a tx race to resolve it. Happens in this case
|
||||||
t.Errorf("response unmatch. expected %s, got %s", want, have)
|
// because resolving the tx body belonging to a log is delayed.
|
||||||
|
{
|
||||||
|
body: `{block { logs(filter: {}) { transaction { nonce value gasPrice }}}}`,
|
||||||
|
want: `{"block":{"logs":[{"transaction":{"nonce":"0x0","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x0","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x1","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x1","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x2","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x2","value":"0x0","gasPrice":"0x3b9aca00"}}]}}`,
|
||||||
|
},
|
||||||
|
// Multiple txes of a block race to set/retrieve receipts of a block.
|
||||||
|
{
|
||||||
|
body: "{block { transactions { status gasUsed } } }",
|
||||||
|
want: `{"block":{"transactions":[{"status":1,"gasUsed":21768},{"status":1,"gasUsed":21768},{"status":1,"gasUsed":21768}]}}`,
|
||||||
|
},
|
||||||
|
// Multiple fields of block race to resolve header and body.
|
||||||
|
{
|
||||||
|
body: "{ block { number hash gasLimit ommerCount transactionCount totalDifficulty } }",
|
||||||
|
want: fmt.Sprintf(`{"block":{"number":1,"hash":"%s","gasLimit":11500000,"ommerCount":0,"transactionCount":3,"totalDifficulty":"0x200000"}}`, chain[len(chain)-1].Hash()),
|
||||||
|
},
|
||||||
|
// Multiple fields of a block race to resolve the header and body.
|
||||||
|
{
|
||||||
|
body: fmt.Sprintf(`{ transaction(hash: "%s") { block { number hash gasLimit ommerCount transactionCount } } }`, tx.Hash()),
|
||||||
|
want: fmt.Sprintf(`{"transaction":{"block":{"number":1,"hash":"%s","gasLimit":11500000,"ommerCount":0,"transactionCount":3}}}`, chain[len(chain)-1].Hash()),
|
||||||
|
},
|
||||||
|
// Account fields race the resolve the state object.
|
||||||
|
{
|
||||||
|
body: fmt.Sprintf(`{ block { account(address: "%s") { balance transactionCount code } } }`, dadStr),
|
||||||
|
want: `{"block":{"account":{"balance":"0x0","transactionCount":"0x0","code":"0x60006000a060006000a060006000f3"}}}`,
|
||||||
|
},
|
||||||
|
// Test values for a non-existent account.
|
||||||
|
{
|
||||||
|
body: fmt.Sprintf(`{ block { account(address: "%s") { balance transactionCount code } } }`, "0x1111111111111111111111111111111111111111"),
|
||||||
|
want: `{"block":{"account":{"balance":"0x0","transactionCount":"0x0","code":"0x"}}}`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
res := handler.Schema.Exec(context.Background(), tt.body, "", map[string]interface{}{})
|
||||||
|
if res.Errors != nil {
|
||||||
|
t.Fatalf("failed to execute query for testcase #%d: %v", i, res.Errors)
|
||||||
|
}
|
||||||
|
have, err := json.Marshal(res.Data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to encode graphql response for testcase #%d: %s", i, err)
|
||||||
|
}
|
||||||
|
if string(have) != tt.want {
|
||||||
|
t.Errorf("response unmatch for testcase #%d.\nExpected:\n%s\nGot:\n%s\n", i, tt.want, have)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +378,7 @@ func createNode(t *testing.T) *node.Node {
|
|||||||
return stack
|
return stack
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) *handler {
|
func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) (*handler, []*types.Block) {
|
||||||
ethConf := ðconfig.Config{
|
ethConf := ðconfig.Config{
|
||||||
Genesis: gspec,
|
Genesis: gspec,
|
||||||
Ethash: ethash.Config{
|
Ethash: ethash.Config{
|
||||||
@ -367,5 +409,5 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create graphql service: %v", err)
|
t.Fatalf("could not create graphql service: %v", err)
|
||||||
}
|
}
|
||||||
return handler
|
return handler, chain
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user