Skip to content

Commit

Permalink
Add codec and make chain reader use it. Run the interface tests for b…
Browse files Browse the repository at this point in the history
…oth chain reader and codec.
  • Loading branch information
nolag committed Dec 1, 2023
1 parent dab9762 commit 103a8c5
Show file tree
Hide file tree
Showing 25 changed files with 1,328 additions and 367 deletions.
8 changes: 4 additions & 4 deletions core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,10 @@ require (
github.com/shirou/gopsutil/v3 v3.23.9 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231123003013-379db0b9e4c7 // indirect
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231120072345-ec92d212f606 // indirect
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231126115247-e39408d74e90 // indirect
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231120192657-3769c7fcc8f0 // indirect
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231128165554-4ca240b67cd7 // indirect
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679 // indirect
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231130211003-6d1bb2f0b68a // indirect
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231128204445-3d61b12a0006 // indirect
github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect
github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect
github.com/smartcontractkit/wsrpc v0.7.2 // indirect
Expand Down
16 changes: 8 additions & 8 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1467,14 +1467,14 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumv
github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M=
github.com/smartcontractkit/chainlink-automation v1.0.0-alpha.0.0.20231120164534-d4cab696c459 h1:hJhuShYv9eUQxHJQdOmyEymVmApOrICrQdOY7kKQ5Io=
github.com/smartcontractkit/chainlink-automation v1.0.0-alpha.0.0.20231120164534-d4cab696c459/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231123003013-379db0b9e4c7 h1:QLhOOIeFldRBc+ESAVZfEPrIpfeiduwnmHlvDt1iFmM=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231123003013-379db0b9e4c7/go.mod h1:ipa32mlAXbF3uUQqfVDNxQbBiyu2KKA3AwhK3ewhSSU=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231120072345-ec92d212f606 h1:4RUoJlw/BEonMCkh7AuoTGBWbV+7CY8n0kpesz5tlqA=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231120072345-ec92d212f606/go.mod h1:xwuKim71kn4z+8r1MO6mjlxl3bGlGPpHnqWJW6fQsuU=
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231126115247-e39408d74e90 h1:tcnV7rhW0wC/oukNE/wPRQzC1K86YERbVaWF5KYda7g=
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231126115247-e39408d74e90/go.mod h1:VDRlSwdE7x/7dPIzg4mr+m80dvNm/vmub/7t9T9Mf/k=
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231120192657-3769c7fcc8f0 h1:rWrd5VnZWpFXH/UzcGI0PxhT6u4BAuoZDBr2QBy5YNc=
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231120192657-3769c7fcc8f0/go.mod h1:L2d4754c/HjL8GzzdtzaRsWUxKh1OWMx2Kd6faRf/PA=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231128165554-4ca240b67cd7 h1:Q7Lif7+Hr3A1gsFooCp/StP93qVPuixn32XEllZnSdY=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231128165554-4ca240b67cd7/go.mod h1:ipa32mlAXbF3uUQqfVDNxQbBiyu2KKA3AwhK3ewhSSU=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679 h1:iu1pNbUoSDTrp+7BUtfTygZ2C0f5C2ZOBQhIoJjp+S0=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679/go.mod h1:2Jx7bTEk4ujFQdsZpZq3A0BydvaVPs6mX8clUfxHOEM=
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231130211003-6d1bb2f0b68a h1:JoyTazNcqXvZoMQjNfB0eapnlaoMS6pI0NeRvouRvog=
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231130211003-6d1bb2f0b68a/go.mod h1:rioELYwPY2xBtzPRN/D08Y7iTPbIQEjPknYdJK51CzQ=
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231128204445-3d61b12a0006 h1:1GzOKT53e8N7ZPwsyf1hSbKsynZmXmLOIL3DMvGq9sc=
github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231128204445-3d61b12a0006/go.mod h1:tJLhL7gJ+V3+4N/yLxXIvJ0DAiuyqZCa31+2BSbw7zo=
github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8=
github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs=
github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss=
Expand Down
190 changes: 106 additions & 84 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package evm

import (
"context"
"errors"
"fmt"
"strings"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"

commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm"
evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services"
Expand All @@ -23,19 +27,30 @@ type ChainReaderService interface {
}

type chainReader struct {
lggr logger.Logger
contractID common.Address
lp logpoller.LogPoller
lggr logger.Logger
lp logpoller.LogPoller
codec *evmCodec
client evmclient.Client
}

// NewChainReaderService constructor for ChainReader
func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, contractID common.Address, config types.ChainReaderConfig) (*chainReader, error) {
if err := validateChainReaderConfig(config); err != nil {
return nil, fmt.Errorf("%w err: %w", commontypes.ErrInvalidConfig, err)
// NewChainReaderService is a constructor for ChainReader, returns nil if there is any error
func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, chain evm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) {

parsed := &parsedTypes{
encoderDefs: map[string]*CodecEntry{},
decoderDefs: map[string]*CodecEntry{},
}

if err := addTypes(config.ChainContractReaders, parsed); err != nil {
return nil, err
}

// TODO BCF-2814 implement initialisation of chain reading definitions and pass them into chainReader
return &chainReader{lggr.Named("ChainReader"), contractID, lp}, nil
return &chainReader{
lggr: lggr.Named("ChainReader"),
lp: lp,
codec: codecFromTypes(parsed),
client: chain.Client(),
}, nil
}

func (cr *chainReader) Name() string { return cr.lggr.Name() }
Expand All @@ -45,117 +60,124 @@ func (cr *chainReader) initialize() error {
return nil
}

func (cr *chainReader) CreateType(itemType string, forEncoding bool) (any, error) {
return cr.codec.CreateType(itemType, forEncoding)
}

var _ commontypes.TypeProvider = &chainReader{}

func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
data, err := cr.codec.Encode(ctx, params, method)
if err != nil {
return err
}

address := common.HexToAddress(bc.Address)
callMsg := ethereum.CallMsg{
To: &address,
From: address,
Data: data,
}

output, err := cr.client.CallContract(ctx, callMsg, nil)

if err != nil {
return err
}

return cr.codec.Decode(ctx, output, returnVal, method)
}

func (cr *chainReader) Start(ctx context.Context) error {
if err := cr.initialize(); err != nil {
return fmt.Errorf("Failed to initialize ChainReader: %w", err)
}
return nil
}

func (cr *chainReader) Close() error { return nil }

func (cr *chainReader) Ready() error { return nil }

func (cr *chainReader) HealthReport() map[string]error {
return map[string]error{cr.Name(): nil}
}

func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
return fmt.Errorf("Unimplemented method GetLatestValue called %w", errors.ErrUnsupported)
}

func validateChainReaderConfig(cfg types.ChainReaderConfig) error {
if len(cfg.ChainContractReaders) == 0 {
return fmt.Errorf("config is empty")
func addEventTypes(name string, contractABI abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
event, methodExists := contractABI.Events[chainReaderDefinition.ChainSpecificName]
if !methodExists {
return fmt.Errorf("method: %s doesn't exist", chainReaderDefinition.ChainSpecificName)
}

for contractName, chainContractReader := range cfg.ChainContractReaders {
abi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI))
if err != nil {
return fmt.Errorf("invalid abi: %w", err)
}

for chainReadingDefinitionName, chainReaderDefinition := range chainContractReader.ChainReaderDefinitions {
switch chainReaderDefinition.ReadType {
case types.Method:
err = validateMethods(abi, chainReaderDefinition)
case types.Event:
err = validateEvents(abi, chainReaderDefinition)
default:
return fmt.Errorf("invalid chain reading definition read type: %d for contract: %q", chainReaderDefinition.ReadType, contractName)
}
if err != nil {
return fmt.Errorf("invalid chain reading definition: %q for contract: %q, err: %w", chainReadingDefinitionName, contractName, err)
}
}
if err := addOverrides(chainReaderDefinition, event.Inputs); err != nil {
return err
}

return nil
return addDecoderDef(name, event.Inputs, parsed)
}

func validateEvents(contractABI abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error {
event, methodExists := contractABI.Events[chainReaderDefinition.ChainSpecificName]
func addMethods(name string, abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
method, methodExists := abi.Methods[chainReaderDefinition.ChainSpecificName]
if !methodExists {
return fmt.Errorf("event: %s doesn't exist", chainReaderDefinition.ChainSpecificName)
return fmt.Errorf("method: %q doesn't exist", chainReaderDefinition.ChainSpecificName)
}

var abiEventIndexedInputs []abi.Argument
for _, eventInput := range event.Inputs {
if eventInput.Indexed {
abiEventIndexedInputs = append(abiEventIndexedInputs, eventInput)
}
if err := addOverrides(chainReaderDefinition, method.Inputs); err != nil {
return err
}

var chainReaderEventParams []string
for chainReaderEventParam := range chainReaderDefinition.Params {
chainReaderEventParams = append(chainReaderEventParams, chainReaderEventParam)
// ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same.
input := &CodecEntry{Args: method.Inputs, encodingPrefix: method.ID}
if err := input.Init(); err != nil {
return err
}

if !areChainReaderArgumentsValid(abiEventIndexedInputs, chainReaderEventParams) {
var abiEventIndexedInputsNames []string
for _, abiEventIndexedInput := range abiEventIndexedInputs {
abiEventIndexedInputsNames = append(abiEventIndexedInputsNames, abiEventIndexedInput.Name)
}
return fmt.Errorf("params: [%s] don't match abi event indexed inputs: [%s]", strings.Join(chainReaderEventParams, ","), strings.Join(abiEventIndexedInputsNames, ","))
}
return nil
parsed.encoderDefs[name] = input
return addDecoderDef(name, method.Outputs, parsed)
}

func validateMethods(abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error {
method, methodExists := abi.Methods[chainReaderDefinition.ChainSpecificName]
if !methodExists {
return fmt.Errorf("method: %q doesn't exist", chainReaderDefinition.ChainSpecificName)
}

var methodNames []string
for methodName := range chainReaderDefinition.Params {
methodNames = append(methodNames, methodName)
}
func addDecoderDef(name string, outputs abi.Arguments, parsed *parsedTypes) error {
output := &CodecEntry{Args: outputs}
parsed.decoderDefs[name] = output
return output.Init()
}

if !areChainReaderArgumentsValid(method.Inputs, methodNames) {
var abiMethodInputs []string
for _, input := range method.Inputs {
abiMethodInputs = append(abiMethodInputs, input.Name)
func addOverrides(chainReaderDefinition types.ChainReaderDefinition, inputs abi.Arguments) error {
// TODO add transforms to add params artificially
paramsLoop:
for argName, param := range chainReaderDefinition.Params {
// TODO add type check too
_ = param
for _, input := range inputs {
if argName == input.Name {
continue paramsLoop
}
}
return fmt.Errorf("params: [%s] don't match abi method inputs: [%s]", strings.Join(methodNames, ","), strings.Join(abiMethodInputs, ","))
return fmt.Errorf("cannot find parameter %v in %v", argName, chainReaderDefinition.ChainSpecificName)
}

return nil
}

func areChainReaderArgumentsValid(contractArgs []abi.Argument, chainReaderArgs []string) bool {
for _, contractArg := range contractArgs {
found := false
for _, chArgName := range chainReaderArgs {
if chArgName == contractArg.Name {
found = true
break
}
func addTypes(chainContractReaders map[string]types.ChainContractReader, parsed *parsedTypes) error {
for contractName, chainContractReader := range chainContractReaders {
contractAbi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI))
if err != nil {
return err
}
if !found {
return false

for chainReadingDefinitionName, chainReaderDefinition := range chainContractReader.ChainReaderDefinitions {
switch chainReaderDefinition.ReadType {
case types.Method:
err = addMethods(chainReadingDefinitionName, contractAbi, chainReaderDefinition, parsed)
case types.Event:
err = addEventTypes(chainReadingDefinitionName, contractAbi, chainReaderDefinition, parsed)
default:
return fmt.Errorf("invalid chain reader definition read type: %d", chainReaderDefinition.ReadType)
}
if err != nil {
return errors.Wrap(err, fmt.Sprintf("invalid chain reader config for contract: %q chain reading definition: %q", contractName, chainReadingDefinitionName))
}
}
}

return true
return nil
}
Loading

0 comments on commit 103a8c5

Please sign in to comment.