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:
parent
fb8a3aaf1e
commit
a236e03d00
@ -24,6 +24,7 @@ import (
|
||||
"math/big"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -80,12 +81,26 @@ type Account struct {
|
||||
r *Resolver
|
||||
address common.Address
|
||||
blockNrOrHash rpc.BlockNumberOrHash
|
||||
state *state.StateDB
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// getState fetches the StateDB object for an account.
|
||||
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)
|
||||
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) {
|
||||
@ -185,15 +200,22 @@ func (at *AccessTuple) StorageKeys(ctx context.Context) []common.Hash {
|
||||
// backend and hash are mandatory; all others will be fetched when required.
|
||||
type Transaction struct {
|
||||
r *Resolver
|
||||
hash common.Hash
|
||||
hash common.Hash // Must be present after initialization
|
||||
mu sync.Mutex
|
||||
// mu protects following resources
|
||||
tx *types.Transaction
|
||||
block *Block
|
||||
index uint64
|
||||
}
|
||||
|
||||
// resolve returns the internal transaction object, fetching it if needed.
|
||||
func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) {
|
||||
if t.tx == nil {
|
||||
// It also returns the block the tx blongs to, unless it is a pending tx.
|
||||
func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.tx != nil {
|
||||
return t.tx, t.block, nil
|
||||
}
|
||||
// Try to return an already finalized transaction
|
||||
tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash)
|
||||
if err == nil && tx != nil {
|
||||
@ -202,14 +224,14 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) {
|
||||
t.block = &Block{
|
||||
r: t.r,
|
||||
numberOrHash: &blockNrOrHash,
|
||||
hash: blockHash,
|
||||
}
|
||||
t.index = index
|
||||
return t.tx, nil
|
||||
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
|
||||
return t.tx, nil, nil
|
||||
}
|
||||
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, block, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return hexutil.Big{}, err
|
||||
}
|
||||
@ -241,8 +263,8 @@ func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
||||
case types.AccessListTxType:
|
||||
return hexutil.Big(*tx.GasPrice()), nil
|
||||
case types.DynamicFeeTxType:
|
||||
if t.block != nil {
|
||||
if baseFee, _ := t.block.BaseFeePerGas(ctx); baseFee != nil {
|
||||
if block != nil {
|
||||
if baseFee, _ := block.BaseFeePerGas(ctx); baseFee != nil {
|
||||
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, block, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pending tx
|
||||
if t.block == nil {
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
header, err := t.block.resolveHeader(ctx)
|
||||
header, err := block.resolveHeader(ctx)
|
||||
if err != nil || header == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, block, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pending tx
|
||||
if t.block == nil {
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
header, err := t.block.resolveHeader(ctx)
|
||||
header, err := block.resolveHeader(ctx)
|
||||
if err != nil || header == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
if _, err := t.resolve(ctx); err != nil {
|
||||
_, block, err := t.resolve(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.block, nil
|
||||
return block, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if t.block == nil {
|
||||
// Pending tx
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
if t.block == nil {
|
||||
// Pending tx
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
receipts, err := t.block.resolveReceipts(ctx)
|
||||
receipts, err := block.resolveReceipts(ctx)
|
||||
if err != nil {
|
||||
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) {
|
||||
if _, err := t.resolve(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if _, ok := t.block.numberOrHash.Hash(); !ok {
|
||||
header, err := t.r.backend.HeaderByNumberOrHash(ctx, *t.block.numberOrHash)
|
||||
_, block, err := t.resolve(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash := header.Hash()
|
||||
t.block.numberOrHash.BlockHash = &hash
|
||||
// Pending tx
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return t.getLogs(ctx)
|
||||
h, err := block.Hash(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.getLogs(ctx, h)
|
||||
}
|
||||
|
||||
// getLogs returns log objects for the given tx.
|
||||
// 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 (
|
||||
hash, _ = t.block.numberOrHash.Hash()
|
||||
filter = t.r.filterSystem.NewBlockFilter(hash, nil, nil)
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
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) {
|
||||
tx, err := t.resolve(ctx)
|
||||
tx, _, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return hexutil.Bytes{}, err
|
||||
}
|
||||
@ -568,8 +592,10 @@ type BlockType int
|
||||
// when required.
|
||||
type Block struct {
|
||||
r *Resolver
|
||||
numberOrHash *rpc.BlockNumberOrHash
|
||||
hash common.Hash
|
||||
numberOrHash *rpc.BlockNumberOrHash // Field resolvers assume numberOrHash is always present
|
||||
mu sync.Mutex
|
||||
// mu protects following resources
|
||||
hash common.Hash // Must be resolved during initialization
|
||||
header *types.Header
|
||||
block *types.Block
|
||||
receipts []*types.Receipt
|
||||
@ -578,6 +604,8 @@ type Block struct {
|
||||
// resolve returns the internal Block object representing this block, fetching
|
||||
// it if necessary.
|
||||
func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.block != nil {
|
||||
return b.block, nil
|
||||
}
|
||||
@ -587,10 +615,10 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
|
||||
}
|
||||
var err error
|
||||
b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash)
|
||||
if b.block != nil && b.header == nil {
|
||||
if b.block != nil {
|
||||
b.hash = b.block.Hash()
|
||||
if b.header == nil {
|
||||
b.header = b.block.Header()
|
||||
if hash, ok := b.numberOrHash.Hash(); ok {
|
||||
b.hash = hash
|
||||
}
|
||||
}
|
||||
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
|
||||
// additional data (transactions and uncles).
|
||||
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{}) {
|
||||
return nil, errBlockInvariant
|
||||
}
|
||||
var err error
|
||||
if b.header == nil {
|
||||
if b.hash != (common.Hash{}) {
|
||||
b.header, err = b.r.backend.HeaderByHash(ctx, b.hash)
|
||||
} else {
|
||||
b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.hash == (common.Hash{}) {
|
||||
b.hash = b.header.Hash()
|
||||
}
|
||||
return b.header, err
|
||||
return b.header, nil
|
||||
}
|
||||
|
||||
// resolveReceipts returns the list of receipts for this block, fetching them
|
||||
// if necessary.
|
||||
func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
|
||||
if b.receipts == nil {
|
||||
hash := b.hash
|
||||
if hash == (common.Hash{}) {
|
||||
header, err := b.resolveHeader(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.receipts != nil {
|
||||
return b.receipts, nil
|
||||
}
|
||||
hash = header.Hash()
|
||||
}
|
||||
receipts, err := b.r.backend.GetReceipts(ctx, hash)
|
||||
receipts, err := b.r.backend.GetReceipts(ctx, b.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.receipts = receipts
|
||||
}
|
||||
return b.receipts, nil
|
||||
return receipts, nil
|
||||
}
|
||||
|
||||
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) {
|
||||
if b.hash == (common.Hash{}) {
|
||||
header, err := b.resolveHeader(ctx)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
b.hash = header.Hash()
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
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 {
|
||||
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{
|
||||
r: b.r,
|
||||
numberOrHash: &num,
|
||||
hash: b.header.ParentHash,
|
||||
numberOrHash: &numOrHash,
|
||||
hash: hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -798,6 +828,7 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) {
|
||||
r: b.r,
|
||||
numberOrHash: &blockNumberOrHash,
|
||||
header: uncle,
|
||||
hash: uncle.Hash(),
|
||||
})
|
||||
}
|
||||
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) {
|
||||
h := b.hash
|
||||
if h == (common.Hash{}) {
|
||||
header, err := b.resolveHeader(ctx)
|
||||
hash, err := b.Hash(ctx)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@ -948,6 +975,7 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block
|
||||
r: b.r,
|
||||
numberOrHash: &blockNumberOrHash,
|
||||
header: uncle,
|
||||
hash: uncle.Hash(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -997,15 +1025,11 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri
|
||||
if args.Filter.Topics != nil {
|
||||
topics = *args.Filter.Topics
|
||||
}
|
||||
hash := b.hash
|
||||
if hash == (common.Hash{}) {
|
||||
header, err := b.resolveHeader(ctx)
|
||||
// Construct the range filter
|
||||
hash, err := b.Hash(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash = header.Hash()
|
||||
}
|
||||
// Construct the range filter
|
||||
filter := b.r.filterSystem.NewBlockFilter(hash, addresses, topics)
|
||||
|
||||
// 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 {
|
||||
Address common.Address
|
||||
}) (*Account, error) {
|
||||
if b.numberOrHash == nil {
|
||||
_, err := b.resolveHeader(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Account{
|
||||
r: b.r,
|
||||
address: args.Address,
|
||||
@ -1063,12 +1081,6 @@ func (c *CallResult) Status() Long {
|
||||
func (b *Block) Call(ctx context.Context, args struct {
|
||||
Data ethapi.TransactionArgs
|
||||
}) (*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())
|
||||
if err != nil {
|
||||
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 {
|
||||
Data ethapi.TransactionArgs
|
||||
}) (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())
|
||||
return Long(gas), err
|
||||
}
|
||||
@ -1173,30 +1179,22 @@ func (r *Resolver) Block(ctx context.Context, args struct {
|
||||
Number *Long
|
||||
Hash *common.Hash
|
||||
}) (*Block, error) {
|
||||
var block *Block
|
||||
var numberOrHash rpc.BlockNumberOrHash
|
||||
if args.Number != nil {
|
||||
if *args.Number < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
number := rpc.BlockNumber(*args.Number)
|
||||
numberOrHash := rpc.BlockNumberOrHashWithNumber(number)
|
||||
block = &Block{
|
||||
r: r,
|
||||
numberOrHash: &numberOrHash,
|
||||
}
|
||||
numberOrHash = rpc.BlockNumberOrHashWithNumber(number)
|
||||
} else if args.Hash != nil {
|
||||
numberOrHash := rpc.BlockNumberOrHashWithHash(*args.Hash, false)
|
||||
block = &Block{
|
||||
r: r,
|
||||
numberOrHash: &numberOrHash,
|
||||
}
|
||||
numberOrHash = rpc.BlockNumberOrHashWithHash(*args.Hash, false)
|
||||
} else {
|
||||
numberOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||
block = &Block{
|
||||
numberOrHash = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||
}
|
||||
block := &Block{
|
||||
r: r,
|
||||
numberOrHash: &numberOrHash,
|
||||
}
|
||||
}
|
||||
// Resolve the header, return nil if it doesn't exist.
|
||||
// Note we don't resolve block directly here since it will require an
|
||||
// additional network request for light client.
|
||||
@ -1256,7 +1254,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has
|
||||
hash: args.Hash,
|
||||
}
|
||||
// Resolve the transaction; if it doesn't exist, return nil.
|
||||
t, err := tx.resolve(ctx)
|
||||
t, _, err := tx.resolve(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if t == nil {
|
||||
|
@ -270,7 +270,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestGraphQLTransactionLogs(t *testing.T) {
|
||||
func TestGraphQLConcurrentResolvers(t *testing.T) {
|
||||
var (
|
||||
key, _ = crypto.GenerateKey()
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
@ -295,8 +295,9 @@ func TestGraphQLTransactionLogs(t *testing.T) {
|
||||
)
|
||||
defer stack.Close()
|
||||
|
||||
handler := 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)})
|
||||
var tx *types.Transaction
|
||||
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)
|
||||
tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)})
|
||||
gen.AddTx(tx)
|
||||
@ -307,18 +308,59 @@ func TestGraphQLTransactionLogs(t *testing.T) {
|
||||
if err := stack.Start(); err != nil {
|
||||
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 {
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
// Multiple txes race to get/set the block hash.
|
||||
{
|
||||
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),
|
||||
},
|
||||
// Multiple fields of a tx race to resolve it. Happens in this case
|
||||
// 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("graphql query failed: %v", res.Errors)
|
||||
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: %s", err)
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
t.Errorf("response unmatch. expected %s, got %s", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,7 +378,7 @@ func createNode(t *testing.T) *node.Node {
|
||||
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{
|
||||
Genesis: gspec,
|
||||
Ethash: ethash.Config{
|
||||
@ -367,5 +409,5 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock
|
||||
if err != nil {
|
||||
t.Fatalf("could not create graphql service: %v", err)
|
||||
}
|
||||
return handler
|
||||
return handler, chain
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user