graphql: fix data races (#26965)

Fixes multiple data races caused by the fact that resolving fields are done concurrently by the graphql library. It also enforces caching at the stateobject level for account fields.
This commit is contained in:
Sina Mahmoodi 2023-03-28 09:08:10 +02:00 committed by GitHub
parent fb8a3aaf1e
commit a236e03d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 212 additions and 172 deletions

@ -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 := &ethconfig.Config{ ethConf := &ethconfig.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
} }