Skip to content

Commit

Permalink
chainreader get latest value with head data (smartcontractkit#15188)
Browse files Browse the repository at this point in the history
* chainreader get latest value with head data

* revert timeout changes

* Fix GetLatestValueWithHeadData

* common bump

* test/lint fix

* lint

* common bump

* test fix

* test fix

---------

Co-authored-by: ilija <[email protected]>
  • Loading branch information
ettec and ilija42 authored Nov 13, 2024
1 parent cf7db16 commit 973adb5
Show file tree
Hide file tree
Showing 17 changed files with 147 additions and 72 deletions.
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.20241113142256-8a7a997a0371
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.20241113142256-8a7a997a0371 h1:vnNqMaAvheZgR8IDMGw0QIV1Qen3XTh7IChwW40SNfU=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241113142256-8a7a997a0371/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

0 comments on commit 973adb5

Please sign in to comment.