Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chainreader get latest value with head data #15188

Merged
merged 9 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions core/chains/evm/types/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"math/big"
"regexp"
"strconv"
"strings"
"sync/atomic"
"time"
Expand All @@ -18,10 +19,12 @@ import (
pkgerrors "github.com/pkg/errors"
"github.com/ugorji/go/codec"

chainagnostictypes "github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/utils/hex"

htrktypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types"
commontypes "github.com/smartcontractkit/chainlink/v2/common/types"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/types/internal/blocks"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
Expand Down Expand Up @@ -198,6 +201,9 @@ func (h *Head) ChainString() string {

// String returns a string representation of this head
func (h *Head) String() string {
if h == nil {
return "<nil>"
}
return fmt.Sprintf("Head{Number: %d, Hash: %s, ParentHash: %s}", h.ToInt(), h.Hash.Hex(), h.ParentHash.Hex())
}

Expand Down Expand Up @@ -325,6 +331,19 @@ func (h *Head) MarshalJSON() ([]byte, error) {
return json.Marshal(jsonHead)
}

func (h *Head) ToChainAgnosticHead() *chainagnostictypes.Head {
if h == nil {
return nil
}

return &chainagnostictypes.Head{
Height: strconv.FormatInt(h.Number, 10),
Hash: h.Hash.Bytes(),
//nolint:gosec // G115
Timestamp: uint64(h.Timestamp.Unix()),
}
}

// Block represents an ethereum block
// This type is only used for the block history estimator, and can be expensive to unmarshal. Don't add unnecessary fields here.
type Block struct {
Expand Down
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/prometheus/client_golang v1.20.5
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chainlink-automation v0.8.1
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241112140826-0e2daed34ef6
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241113121143-ad26f6053786
github.com/smartcontractkit/chainlink/deployment v0.0.0-00010101000000-000000000000
github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241106193309-5560cd76211a
github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1094,8 +1094,8 @@ github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgB
github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20241112095015-3e85d9f1898b h1:4kmZtaQ4fXwduHnw9xk5VmiIOW4nHg/Mx6iidlZJt5o=
github.com/smartcontractkit/chainlink-ccip v0.0.0-20241112095015-3e85d9f1898b/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241112140826-0e2daed34ef6 h1:yJNBWCdNL/X8+wEs3TGTBe9gssMmw5FTFxxrlo+0mVo=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241112140826-0e2daed34ef6/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241113121143-ad26f6053786 h1:IHUy8ErJOoq7nL4A5h7TEMHaxlh7HcM9NDwc9v3h07s=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241113121143-ad26f6053786/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco=
github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw=
github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f/go.mod h1:wHtwSR3F1CQSJJZDQKuqaqFYnvkT+kMyget7dl8Clvo=
github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e h1:JiETqdNM0bktAUGMc62COwXIaw3rR3M77Me6bBLG0Fg=
Expand Down
34 changes: 33 additions & 1 deletion core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ func (cr *chainReader) GetLatestValue(ctx context.Context, readName string, conf

ptrToValue, isValue := returnVal.(*values.Value)
if !isValue {
return binding.GetLatestValue(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal)
_, err = binding.GetLatestValueWithHeadData(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal)
return err
}

contractType, err := cr.CreateContractType(readName, false)
Expand All @@ -219,6 +220,37 @@ func (cr *chainReader) GetLatestValue(ctx context.Context, readName string, conf
return nil
}

func (cr *chainReader) GetLatestValueWithHeadData(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any) (head *commontypes.Head, err error) {
binding, address, err := cr.bindings.GetReader(readName)
if err != nil {
return nil, err
}

ptrToValue, isValue := returnVal.(*values.Value)
if !isValue {
return binding.GetLatestValueWithHeadData(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal)
}

contractType, err := cr.CreateContractType(readName, false)
if err != nil {
return nil, err
}

head, err = cr.GetLatestValueWithHeadData(ctx, readName, confidenceLevel, params, contractType)
if err != nil {
return nil, err
}

value, err := values.Wrap(contractType)
if err != nil {
return nil, err
}

*ptrToValue = value

return head, nil
}

func (cr *chainReader) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) {
return cr.bindings.BatchGetLatestValues(ctx, request)
}
Expand Down
2 changes: 1 addition & 1 deletion core/services/relay/evm/read/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

type Reader interface {
BatchCall(address common.Address, params, retVal any) (Call, error)
GetLatestValue(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params, returnVal any) error
GetLatestValueWithHeadData(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params, returnVal any) (*commontypes.Head, error)
QueryKey(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, any) ([]commontypes.Sequence, error)

Bind(context.Context, ...common.Address) error
Expand Down
15 changes: 9 additions & 6 deletions core/services/relay/evm/read/bindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func TestBindingsRegistry(t *testing.T) {

mReg.EXPECT().HasFilter(mock.Anything).Return(false)
mReg.EXPECT().RegisterFilter(mock.Anything, mock.Anything).Return(nil)
mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x25"), mock.Anything, mock.Anything, mock.Anything).Return(nil)
mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x24"), mock.Anything, mock.Anything, mock.Anything).Return(nil)
mRdr1.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x26"), mock.Anything, mock.Anything, mock.Anything).Return(nil)
mRdr0.EXPECT().GetLatestValueWithHeadData(mock.Anything, common.HexToAddress("0x25"), mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
mRdr0.EXPECT().GetLatestValueWithHeadData(mock.Anything, common.HexToAddress("0x24"), mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
mRdr1.EXPECT().GetLatestValueWithHeadData(mock.Anything, common.HexToAddress("0x26"), mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)

// part of the init phase of chain reader
require.NoError(t, named.AddReader(contractName1, methodName1, mRdr0))
Expand All @@ -100,9 +100,12 @@ func TestBindingsRegistry(t *testing.T) {
rdr2, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName2))
require.NoError(t, err)

require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x25"), primitives.Finalized, nil, nil))
require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x24"), primitives.Finalized, nil, nil))
require.NoError(t, rdr2.GetLatestValue(context.Background(), common.HexToAddress("0x26"), primitives.Finalized, nil, nil))
_, err = rdr1.GetLatestValueWithHeadData(context.Background(), common.HexToAddress("0x25"), primitives.Finalized, nil, nil)
require.NoError(t, err)
_, err = rdr1.GetLatestValueWithHeadData(context.Background(), common.HexToAddress("0x24"), primitives.Finalized, nil, nil)
require.NoError(t, err)
_, err = rdr2.GetLatestValueWithHeadData(context.Background(), common.HexToAddress("0x26"), primitives.Finalized, nil, nil)
require.NoError(t, err)

mBatch.AssertExpectations(t)
mRdr0.AssertExpectations(t)
Expand Down
28 changes: 16 additions & 12 deletions core/services/relay/evm/read/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (b *EventBinding) BatchCall(_ common.Address, _, _ any) (Call, error) {
return Call{}, fmt.Errorf("%w: events are not yet supported in batch get latest values", commontypes.ErrInvalidType)
}

func (b *EventBinding) GetLatestValue(ctx context.Context, address common.Address, confidenceLevel primitives.ConfidenceLevel, params, into any) (err error) {
func (b *EventBinding) GetLatestValueWithHeadData(ctx context.Context, address common.Address, confidenceLevel primitives.ConfidenceLevel, params, into any) (head *commontypes.Head, err error) {
var (
confs evmtypes.Confirmations
result *string
Expand All @@ -256,51 +256,55 @@ func (b *EventBinding) GetLatestValue(ctx context.Context, address common.Addres
}()

if err = b.validateBound(address); err != nil {
return err
return nil, err
}

confs, err = confidenceToConfirmations(b.confirmationsMapping, confidenceLevel)
if err != nil {
return err
return nil, err
}

topicTypeID := codec.WrapItemType(b.contractName, b.eventName, true)

onChainTypedVal, err := b.toNativeOnChainType(topicTypeID, params)
if err != nil {
return err
return nil, err
}

filterTopics, err := b.extractFilterTopics(topicTypeID, onChainTypedVal)
if err != nil {
return err
return nil, err
}

var log *logpoller.Log
if len(filterTopics) != 0 {
var hashedTopics []common.Hash
hashedTopics, err = b.hashTopics(topicTypeID, filterTopics)
if err != nil {
return err
return nil, err
}

if log, err = b.getLatestLog(ctx, address, confs, hashedTopics); err != nil {
return err
return nil, err
}
} else {
if log, err = b.lp.LatestLogByEventSigWithConfs(ctx, b.hash, address, confs); err != nil {
return wrapInternalErr(err)
return nil, wrapInternalErr(err)
}
}

if err := b.decodeLog(ctx, log, into); err != nil {
if err = b.decodeLog(ctx, log, into); err != nil {
encoded := hex.EncodeToString(log.Data)
result = &encoded

return err
return nil, err
}

return nil
return &commontypes.Head{
Height: strconv.FormatInt(log.BlockNumber, 10),
Hash: log.BlockHash.Bytes(),
//nolint:gosec // G115
Timestamp: uint64(log.BlockTimestamp.Unix()),
}, nil
}

func (b *EventBinding) QueryKey(ctx context.Context, address common.Address, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) (sequences []commontypes.Sequence, err error) {
Expand Down
47 changes: 26 additions & 21 deletions core/services/relay/evm/read/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,19 @@ func (b *MethodBinding) BatchCall(address common.Address, params, retVal any) (C
}, nil
}

func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error {
func (b *MethodBinding) GetLatestValueWithHeadData(ctx context.Context, addr common.Address, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) (*commontypes.Head, error) {
if !b.isBound(addr) {
return fmt.Errorf("%w: %w", commontypes.ErrInvalidConfig, newUnboundAddressErr(addr.Hex(), b.contractName, b.method))
return nil, fmt.Errorf("%w: %w", commontypes.ErrInvalidConfig, newUnboundAddressErr(addr.Hex(), b.contractName, b.method))
}

block, err := b.blockNumberFromConfidence(ctx, confidenceLevel)
block, confirmations, err := b.blockAndConfirmationsFromConfidence(ctx, confidenceLevel)
if err != nil {
return err
return nil, err
}

var blockNum *big.Int
if block != nil && confirmations != evmtypes.Unconfirmed {
blockNum = big.NewInt(block.Number)
}

data, err := b.codec.Encode(ctx, params, codec.WrapItemType(b.contractName, b.method, true))
Expand All @@ -141,9 +146,9 @@ func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address,
ReadName: b.method,
Params: params,
ReturnVal: returnVal,
}, block.String(), false)
}, blockNum.String(), false)

return callErr
return nil, callErr
}

callMsg := ethereum.CallMsg{
Expand All @@ -152,7 +157,7 @@ func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address,
Data: data,
}

bytes, err := b.client.CallContract(ctx, callMsg, block)
bytes, err := b.client.CallContract(ctx, callMsg, blockNum)
if err != nil {
callErr := newErrorFromCall(
fmt.Errorf("%w: contract call: %s", commontypes.ErrInvalidType, err.Error()),
Expand All @@ -162,9 +167,9 @@ func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address,
ReadName: b.method,
Params: params,
ReturnVal: returnVal,
}, block.String(), false)
}, blockNum.String(), false)

return callErr
return nil, callErr
}

if err = b.codec.Decode(ctx, bytes, returnVal, codec.WrapItemType(b.contractName, b.method, false)); err != nil {
Expand All @@ -176,15 +181,15 @@ func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address,
ReadName: b.method,
Params: params,
ReturnVal: returnVal,
}, block.String(), false)
}, blockNum.String(), false)

strResult := hexutil.Encode(bytes)
callErr.Result = &strResult

return callErr
return nil, callErr
}

return nil
return block.ToChainAgnosticHead(), nil
}

func (b *MethodBinding) QueryKey(
Expand All @@ -200,31 +205,31 @@ func (b *MethodBinding) QueryKey(
func (b *MethodBinding) Register(_ context.Context) error { return nil }
func (b *MethodBinding) Unregister(_ context.Context) error { return nil }

func (b *MethodBinding) blockNumberFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*big.Int, error) {
func (b *MethodBinding) blockAndConfirmationsFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*evmtypes.Head, evmtypes.Confirmations, error) {
confirmations, err := confidenceToConfirmations(b.confirmationsMapping, confidenceLevel)
if err != nil {
err = fmt.Errorf("%w: contract: %s; method: %s;", err, b.contractName, b.method)
err = fmt.Errorf("%w: contract: %s; method: %s", err, b.contractName, b.method)
if confidenceLevel == primitives.Unconfirmed {
b.lggr.Debugw("Falling back to default contract call behaviour that calls latest state", "contract", b.contractName, "method", b.method, "err", err)

return nil, nil
return nil, 0, err
}

return nil, err
return nil, 0, err
}

_, finalized, err := b.ht.LatestAndFinalizedBlock(ctx)
latest, finalized, err := b.ht.LatestAndFinalizedBlock(ctx)
if err != nil {
return nil, fmt.Errorf("%w: head tracker: %w", commontypes.ErrInternal, err)
return nil, 0, fmt.Errorf("%w: head tracker: %w", commontypes.ErrInternal, err)
}

if confirmations == evmtypes.Finalized {
return big.NewInt(finalized.Number), nil
return finalized, confirmations, nil
} else if confirmations == evmtypes.Unconfirmed {
return nil, nil
return latest, confirmations, nil
}

return nil, fmt.Errorf("%w: [unknown evm confirmations]: %v; contract: %s; method: %s;", commontypes.ErrInvalidConfig, confirmations, b.contractName, b.method)
return nil, 0, fmt.Errorf("%w: [unknown evm confirmations]: %v; contract: %s; method: %s", commontypes.ErrInvalidConfig, confirmations, b.contractName, b.method)
}

func (b *MethodBinding) isBound(binding common.Address) bool {
Expand Down
Loading
Loading