Skip to content

Commit

Permalink
Merge pull request #11303 from smartcontractkit/rtinianov_encode_decode
Browse files Browse the repository at this point in the history
Adds a codec and makes chain reader use it.
  • Loading branch information
nolag authored Dec 1, 2023
2 parents dab9762 + 103a8c5 commit 3773479
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 3773479

Please sign in to comment.