Skip to content

Commit

Permalink
add events
Browse files Browse the repository at this point in the history
  • Loading branch information
nolag committed Dec 8, 2023
1 parent e79ff3b commit 2590e1b
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 59 deletions.
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ require (
github.com/shirou/gopsutil/v3 v3.23.10 // 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.20231206153113-f86b0b21496d // indirect
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208012003-888e7dbdb9c6 // indirect
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231128204301-ee4297eff679 // indirect
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 // indirect
github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,8 @@ 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.1 h1:vVjBFq2Zsz21kPy1Pb0wpjF9zrbJX+zjXphDeeR4XZk=
github.com/smartcontractkit/chainlink-automation v1.0.1/go.mod h1:INSchkV3ntyDdlZKGWA030MPDpp6pbeuiRkRKYFCm2k=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231206153113-f86b0b21496d h1:ibnzqA27rmFXJvBKQhx1D/tg7X0Oe9tPsWNhi+GQ3sM=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231206153113-f86b0b21496d/go.mod h1:IdlfCN9rUs8Q/hrOYe8McNBIwEOHEsi0jilb3Cw77xs=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208012003-888e7dbdb9c6 h1:Vqi/po+LG3J47USCh83vvjutn2KX8nr7PsU9q4qHt2o=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20231208012003-888e7dbdb9c6/go.mod h1:IdlfCN9rUs8Q/hrOYe8McNBIwEOHEsi0jilb3Cw77xs=
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-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 h1:xYqRgZO0nMSO8CBCMR0r3WA+LZ4kNL8a6bnbyk/oBtQ=
Expand Down
95 changes: 65 additions & 30 deletions core/services/relay/evm/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"

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

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

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
Expand All @@ -27,44 +29,63 @@ type ChainReaderService interface {
}

type chainReader struct {
lggr logger.Logger
lp logpoller.LogPoller
codec commontypes.RemoteCodec
client evmclient.Client
lggr logger.Logger
lp logpoller.LogPoller
codec commontypes.RemoteCodec
client evmclient.Client
contractID common.Address
events map[string]common.Hash
commonservices.StateMachine
}

// NewChainReaderService is a constructor for ChainReader, returns nil if there is any error
func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, chain legacyevm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) {

func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, contractID common.Address, chain legacyevm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) {
parsed := &parsedTypes{
encoderDefs: map[string]*codecEntry{},
decoderDefs: map[string]*codecEntry{},
}

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

c, err := parsed.toCodec()

return &chainReader{
lggr: lggr.Named("ChainReader"),
lp: lp,
codec: c,
client: chain.Client(),
lggr: lggr.Named("ChainReader"),
lp: lp,
codec: c,
client: chain.Client(),
contractID: contractID,
events: events,
}, err
}

func (cr *chainReader) Name() string { return cr.lggr.Name() }

func (cr *chainReader) initialize() error {
// Initialize chain reader, start cache polling loop, etc.
return nil
}

var _ commontypes.TypeProvider = &chainReader{}

func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
if hash, ok := cr.events[method]; ok {
return cr.getLatestValueFromLogPoller(ctx, bc, method, hash, returnVal)
}
return cr.getLatestValueFromContract(ctx, bc, method, params, returnVal)
}

func (cr *chainReader) getLatestValueFromLogPoller(ctx context.Context, bc commontypes.BoundContract, method string, hash common.Hash, returnVal any) error {
contractAddr := common.HexToAddress(bc.Address)
log, err := cr.lp.LatestLogByEventSigWithConfs(hash, contractAddr, logpoller.Finalized)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return fmt.Errorf("%w: %w", commontypes.ErrNotFound, err)
}
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
return cr.codec.Decode(ctx, log.Data, returnVal, wrapItemType(method, false))
}

func (cr *chainReader) getLatestValueFromContract(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error {
data, err := cr.codec.Encode(ctx, params, wrapItemType(method, true))
if err != nil {
return err
Expand All @@ -86,13 +107,23 @@ func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundC
return cr.codec.Decode(ctx, output, returnVal, wrapItemType(method, false))
}

func (cr *chainReader) Start(ctx context.Context) error {
if err := cr.initialize(); err != nil {
return fmt.Errorf("Failed to initialize ChainReader: %w", err)
}
func (cr *chainReader) Start(_ context.Context) error {
return cr.StartOnce("ChainReader", func() error {
for name, eventId := range cr.events {
if err := cr.lp.RegisterFilter(logpoller.Filter{
Name: name,
EventSigs: evmtypes.HashArray{eventId},
Addresses: evmtypes.AddressArray{cr.contractID},
}); err != nil {
return fmt.Errorf("%w: %w", commontypes.ErrInternal, err)
}
}
return nil
})
}
func (cr *chainReader) Close() error {
return nil
}
func (cr *chainReader) Close() error { return nil }

func (cr *chainReader) Ready() error { return nil }
func (cr *chainReader) HealthReport() map[string]error {
Expand All @@ -103,13 +134,13 @@ func (cr *chainReader) CreateType(itemType string, forEncoding bool) (any, error
return cr.codec.CreateType(wrapItemType(itemType, forEncoding), forEncoding)
}

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

return addDecoderDef(name, event.Inputs, parsed, chainReaderDefinition)
return event.ID, addDecoderDef(name, event.Inputs, parsed, chainReaderDefinition)
}

func addMethods(name string, abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition, parsed *parsedTypes) error {
Expand Down Expand Up @@ -153,29 +184,33 @@ func addDecoderDef(name string, outputs abi.Arguments, parsed *parsedTypes, def
return output.Init()
}

func addTypes(chainContractReaders map[string]types.ChainContractReader, parsed *parsedTypes) error {
func addTypes(chainContractReaders map[string]types.ChainContractReader, parsed *parsedTypes) (map[string]common.Hash, error) {
events := map[string]common.Hash{}
for contractName, chainContractReader := range chainContractReaders {
contractAbi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI))
if err != nil {
return err
return nil, err
}

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)
var hash common.Hash
hash, err = addEventTypes(chainReadingDefinitionName, contractAbi, chainReaderDefinition, parsed)
events[chainReadingDefinitionName] = hash
default:
return fmt.Errorf("invalid chain reader definition read type: %d", chainReaderDefinition.ReadType)
return nil, 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 nil, errors.Wrap(err, fmt.Sprintf("invalid chain reader config for contract: %q chain reading definition: %q", contractName, chainReadingDefinitionName))
}
}
}

return nil
return events, nil
}

func wrapItemType(itemType string, isParams bool) string {
Expand Down
68 changes: 54 additions & 14 deletions core/services/relay/evm/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"math"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
evmtypes "github.com/ethereum/go-ethereum/core/types"
Expand All @@ -24,8 +26,10 @@ import (
. "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with .

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
mocklogpoller "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller"
"github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm"
"github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/testfiles"
Expand All @@ -48,18 +52,24 @@ type chainReaderInterfaceTester struct {
sim *backends.SimulatedBackend
pk *ecdsa.PrivateKey
evmTest *testfiles.Testfiles
cr evm.ChainReaderService
}

func (it *chainReaderInterfaceTester) Setup(t *testing.T) {
func (it *chainReaderInterfaceTester) Setup(ctx context.Context, t *testing.T) {
t.Cleanup(func() {
it.address = ""
require.NoError(t, it.cr.Close())
it.cr = nil
})

// can re-use the same chain for tests, just make new contract for each test
if it.chain != nil {
it.deployNewContract(ctx, t)
return
}

t.Cleanup(func() { it.address = "" })
it.chain = &mocks.Chain{}
it.setupChainNoClient(t)
it.chain.On("LogPoller").Return(logger.NullLogger)

testStruct := CreateTestStruct(0, it)

Expand All @@ -77,6 +87,10 @@ func (it *chainReaderInterfaceTester) Setup(t *testing.T) {
MethodReturningUint64Slice: {
ChainSpecificName: "GetSliceValue",
},
EventName: {
ChainSpecificName: "Triggered",
ReadType: types.Event,
},
MethodReturningSeenStruct: {
ChainSpecificName: returnSeenName,
InputModifications: codec.ModifiersConfig{
Expand All @@ -97,6 +111,7 @@ func (it *chainReaderInterfaceTester) Setup(t *testing.T) {
},
}
it.chain.On("Client").Return(client.NewSimulatedBackendClient(t, it.sim, big.NewInt(1337)))
it.deployNewContract(ctx, t)
}

func (it *chainReaderInterfaceTester) Name() string {
Expand All @@ -110,15 +125,25 @@ func (it *chainReaderInterfaceTester) GetAccountBytes(i int) []byte {
return account
}

func (it *chainReaderInterfaceTester) GetChainReader(t *testing.T) clcommontypes.ChainReader {
cr, err := evm.NewChainReaderService(logger.NullLogger, mocklogpoller.NewLogPoller(t), it.chain, it.chainConfig)
func (it *chainReaderInterfaceTester) GetChainReader(ctx context.Context, t *testing.T) clcommontypes.ChainReader {
if it.cr != nil {
return it.cr
}

addr := common.HexToAddress(it.address)
lggr := logger.NullLogger
db := pgtest.NewSqlxDB(t)
lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.SimulatedChainID, db, lggr, pgtest.NewQConfig(true)), it.chain.Client(), lggr, time.Millisecond, false, 0, 1, 1, 10000)
require.NoError(t, lp.Start(ctx))
it.chain.On("LogPoller").Return(lp)
cr, err := evm.NewChainReaderService(lggr, lp, addr, it.chain, it.chainConfig)
require.NoError(t, err)
require.NoError(t, cr.Start(ctx))
it.cr = cr
return cr
}

func (it *chainReaderInterfaceTester) GetPrimitiveContract(ctx context.Context, t *testing.T) clcommontypes.BoundContract {
// Since most tests don't use the contract, it's set up lazily to save time
it.deployNewContract(ctx, t)
func (it *chainReaderInterfaceTester) GetPrimitiveContract(_ context.Context, t *testing.T) clcommontypes.BoundContract {
return clcommontypes.BoundContract{
Address: it.address,
Name: MethodReturningUint64,
Expand All @@ -142,10 +167,29 @@ func (it *chainReaderInterfaceTester) GetSliceContract(ctx context.Context, t *t
}

func (it *chainReaderInterfaceTester) SetLatestValue(ctx context.Context, t *testing.T, testStruct *TestStruct) clcommontypes.BoundContract {
it.sendTxWithTestStruct(ctx, t, testStruct, (*testfiles.TestfilesTransactor).AddTestStruct)
return clcommontypes.BoundContract{
Address: it.address,
Name: MethodTakingLatestParamsReturningTestStruct,
}
}

func (it *chainReaderInterfaceTester) TriggerEvent(ctx context.Context, t *testing.T, testStruct *TestStruct) clcommontypes.BoundContract {
it.sendTxWithTestStruct(ctx, t, testStruct, (*testfiles.TestfilesTransactor).TriggerEvent)
return clcommontypes.BoundContract{
Address: it.address,
Name: EventName,
}
}

type testStructFn = func(*testfiles.TestfilesTransactor, *bind.TransactOpts, int32, string, uint8, [32]uint8, [32]byte, [][32]byte, *big.Int, testfiles.MidLevelTestStruct) (*evmtypes.Transaction, error)

func (it *chainReaderInterfaceTester) sendTxWithTestStruct(ctx context.Context, t *testing.T, testStruct *TestStruct, fn testStructFn) {
// Since most tests don't use the contract, it's set up lazily to save time
it.deployNewContract(ctx, t)

tx, err := it.evmTest.AddTestStruct(
tx, err := fn(
&it.evmTest.TestfilesTransactor,
it.auth,
testStruct.Field,
testStruct.DifferentField,
Expand All @@ -160,10 +204,6 @@ func (it *chainReaderInterfaceTester) SetLatestValue(ctx context.Context, t *tes
it.sim.Commit()
it.incNonce()
it.awaitTx(ctx, t, tx)
return clcommontypes.BoundContract{
Address: it.address,
Name: MethodTakingLatestParamsReturningTestStruct,
}
}

func convertOracleIDs(oracleIDs [32]commontypes.OracleID) [32]byte {
Expand Down
3 changes: 2 additions & 1 deletion core/services/relay/evm/codec_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evm_test

import (
"context"
"encoding/json"
"testing"

Expand Down Expand Up @@ -49,7 +50,7 @@ func TestCodec(t *testing.T) {

type codecInterfaceTester struct{}

func (it *codecInterfaceTester) Setup(_ *testing.T) {}
func (it *codecInterfaceTester) Setup(_ context.Context, _ *testing.T) {}

func (it *codecInterfaceTester) GetAccountBytes(i int) []byte {
account := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2}
Expand Down
3 changes: 2 additions & 1 deletion core/services/relay/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp
if !common.IsHexAddress(relayOpts.ContractID) {
return nil, fmt.Errorf("invalid contractID %s, expected hex address", relayOpts.ContractID)
}
contractID := common.HexToAddress(relayOpts.ContractID)

configWatcher, err := newConfigProvider(lggr, r.chain, relayOpts, r.eventBroadcaster)
if err != nil {
Expand Down Expand Up @@ -533,7 +534,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp
// allow fallback until chain reader is default and median contract is removed, but still log just in case
var chainReaderService commontypes.ChainReader
if relayConfig.ChainReader != nil {
if chainReaderService, err = NewChainReaderService(lggr, r.chain.LogPoller(), r.chain, *relayConfig.ChainReader); err != nil {
if chainReaderService, err = NewChainReaderService(lggr, r.chain.LogPoller(), contractID, r.chain, *relayConfig.ChainReader); err != nil {
return nil, err
}
} else {
Expand Down
Loading

0 comments on commit 2590e1b

Please sign in to comment.