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

[CAPPL-19] Gateway Connector service wrapper #14356

Merged
merged 29 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e951bdb
service wrapper
DavidOrchard Sep 5, 2024
d4ce0db
PR updates: simplify connector into signer and consolidate files
DavidOrchard Sep 6, 2024
7784141
keep signer as handler with empty methods for now
DavidOrchard Sep 6, 2024
ac908dd
lint
DavidOrchard Sep 6, 2024
cb3088d
PR comments except for unit test
DavidOrchard Sep 6, 2024
b5bafec
package lint
DavidOrchard Sep 6, 2024
79d6e4c
unit test v1
DavidOrchard Sep 9, 2024
a2d5ab6
fix unit tests
DavidOrchard Sep 9, 2024
be130db
Merge branch 'develop' into feature/CAPPL-19-connector-service-3
DavidOrchard Sep 9, 2024
ef63be6
PR Review: fix config, separate tests
DavidOrchard Sep 9, 2024
2b66be0
PR review: clean up pointers, service wrapper generation.
DavidOrchard Sep 10, 2024
6519107
fix mocks return args issue
DavidOrchard Sep 10, 2024
493f94c
add logs to figure out Sign issue
DavidOrchard Sep 10, 2024
cfaf0ca
fix keys issues
DavidOrchard Sep 10, 2024
3b1e3ee
typos
DavidOrchard Sep 10, 2024
6b68bff
Merge branch 'develop' into feature/CAPPL-19-connector-service-3
bolekk Sep 10, 2024
f945bd2
Merge branch 'develop' into feature/CAPPL-19-connector-service-3
bolekk Sep 10, 2024
3971608
remove redundant named import
DavidOrchard Sep 10, 2024
1cfa38f
update order of deps to try to fix strange lint error
DavidOrchard Sep 10, 2024
5ac44fa
remove handlegatewaymessage
DavidOrchard Sep 10, 2024
8f26d15
change name to gwcommon
DavidOrchard Sep 10, 2024
c3f5616
remove unused funcs and clean up var reuse
DavidOrchard Sep 10, 2024
558f577
add clock to servicewrapper
DavidOrchard Sep 10, 2024
f2f8d9c
fix bug in configs
DavidOrchard Sep 10, 2024
e119169
fix Ready and healthreport
DavidOrchard Sep 10, 2024
d572c97
remove connectorSigner struct
DavidOrchard Sep 11, 2024
5e0ae26
Merge branch 'develop' into feature/CAPPL-19-connector-service-3
DavidOrchard Sep 11, 2024
d4e0068
remove privateKey from constructor, keep initialization in Start
DavidOrchard Sep 11, 2024
e256b2f
lint
DavidOrchard Sep 11, 2024
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
114 changes: 114 additions & 0 deletions core/capabilities/gateway_connector/service_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package gatewayconnector

import (
"context"
"crypto/ecdsa"
"errors"
"math/big"
"slices"

"github.com/ethereum/go-ethereum/common"
"github.com/jonboulle/clockwork"

"github.com/smartcontractkit/chainlink-common/pkg/services"
"github.com/smartcontractkit/chainlink/v2/core/config"
"github.com/smartcontractkit/chainlink/v2/core/logger"
gwcommon "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common"
"github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector"
"github.com/smartcontractkit/chainlink/v2/core/services/gateway/network"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
)

type ServiceWrapper struct {
services.StateMachine

config config.GatewayConnector
keystore keystore.Eth
connector connector.GatewayConnector
signerKey *ecdsa.PrivateKey
lggr logger.Logger
clock clockwork.Clock
}

func translateConfigs(f config.GatewayConnector) connector.ConnectorConfig {
r := connector.ConnectorConfig{}
r.NodeAddress = f.NodeAddress()
r.DonId = f.DonID()

if len(f.Gateways()) != 0 {
r.Gateways = make([]connector.ConnectorGatewayConfig, len(f.Gateways()))
for index, element := range f.Gateways() {
r.Gateways[index] = connector.ConnectorGatewayConfig{Id: element.ID(), URL: element.URL()}
}
}

r.WsClientConfig = network.WebSocketClientConfig{HandshakeTimeoutMillis: f.WSHandshakeTimeoutMillis()}
r.AuthMinChallengeLen = f.AuthMinChallengeLen()
r.AuthTimestampToleranceSec = f.AuthTimestampToleranceSec()
return r
}

// NOTE: this wrapper is needed to make sure that our services are started after Keystore.
func NewGatewayConnectorServiceWrapper(config config.GatewayConnector, keystore keystore.Eth, clock clockwork.Clock, lggr logger.Logger) *ServiceWrapper {
return &ServiceWrapper{
config: config,
keystore: keystore,
clock: clock,
lggr: lggr,
}
}

func (e *ServiceWrapper) Start(ctx context.Context) error {
return e.StartOnce("GatewayConnectorServiceWrapper", func() error {
conf := e.config
nodeAddress := conf.NodeAddress()
chainID, _ := new(big.Int).SetString(conf.ChainIDForNodeKey(), 0)
enabledKeys, err := e.keystore.EnabledKeysForChain(ctx, chainID)
if err != nil {
return err
}
if len(enabledKeys) == 0 {
return errors.New("no available keys found")
}
configuredNodeAddress := common.HexToAddress(nodeAddress)
idx := slices.IndexFunc(enabledKeys, func(key ethkey.KeyV2) bool { return key.Address == configuredNodeAddress })

if idx == -1 {
return errors.New("key for configured node address not found")
}
e.signerKey = enabledKeys[idx].ToEcdsaPrivKey()
if enabledKeys[idx].ID() != nodeAddress {
return errors.New("node address mismatch")
}

translated := translateConfigs(conf)
e.connector, err = connector.NewGatewayConnector(&translated, e, e.clock, e.lggr)
if err != nil {
return err
}
return e.connector.Start(ctx)
})
}

func (e *ServiceWrapper) Sign(data ...[]byte) ([]byte, error) {
return gwcommon.SignData(e.signerKey, data...)
}

func (e *ServiceWrapper) Close() error {
return e.StopOnce("GatewayConnectorServiceWrapper", func() (err error) {
return e.connector.Close()
})
}

func (e *ServiceWrapper) HealthReport() map[string]error {
return map[string]error{e.Name(): e.Healthy()}
}

func (e *ServiceWrapper) Name() string {
return "GatewayConnectorServiceWrapper"
}

func (e *ServiceWrapper) GetGatewayConnector() connector.GatewayConnector {
return e.connector
}
80 changes: 80 additions & 0 deletions core/capabilities/gateway_connector/service_wrapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package gatewayconnector

import (
"crypto/ecdsa"
"testing"

"github.com/jonboulle/clockwork"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink/v2/core/config/toml"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks"
)

func generateWrapper(t *testing.T, privateKey *ecdsa.PrivateKey, keystoreKey *ecdsa.PrivateKey) (*ServiceWrapper, error) {
logger := logger.TestLogger(t)
privateKeyV2 := ethkey.FromPrivateKey(privateKey)
addr := privateKeyV2.Address
keystoreKeyV2 := ethkey.FromPrivateKey(keystoreKey)

config, err := chainlink.GeneralConfigOpts{
Config: chainlink.Config{
Core: toml.Core{
Capabilities: toml.Capabilities{
GatewayConnector: toml.GatewayConnector{
ChainIDForNodeKey: ptr("1"),
NodeAddress: ptr(addr.Hex()),
DonID: ptr("5"),
WSHandshakeTimeoutMillis: ptr[uint32](100),
AuthMinChallengeLen: ptr[int](0),
AuthTimestampToleranceSec: ptr[uint32](10),
Gateways: []toml.ConnectorGateway{{ID: ptr("example_gateway"), URL: ptr("wss://localhost:8081/node")}},
},
},
},
},
}.New()
ethKeystore := ksmocks.NewEth(t)
ethKeystore.On("EnabledKeysForChain", mock.Anything, mock.Anything).Return([]ethkey.KeyV2{keystoreKeyV2}, nil)
gc := config.Capabilities().GatewayConnector()
wrapper := NewGatewayConnectorServiceWrapper(gc, ethKeystore, clockwork.NewFakeClock(), logger)
require.NoError(t, err)
return wrapper, err
}

func TestGatewayConnectorServiceWrapper_CleanStartClose(t *testing.T) {
t.Parallel()

key, _ := testutils.NewPrivateKeyAndAddress(t)
wrapper, err := generateWrapper(t, key, key)
require.NoError(t, err)

ctx := testutils.Context(t)
err = wrapper.Start(ctx)
require.NoError(t, err)

t.Cleanup(func() {
assert.NoError(t, wrapper.Close())
})
}

func TestGatewayConnectorServiceWrapper_NonexistentKey(t *testing.T) {
t.Parallel()

key, _ := testutils.NewPrivateKeyAndAddress(t)
keystoreKey, _ := testutils.NewPrivateKeyAndAddress(t)
wrapper, err := generateWrapper(t, key, keystoreKey)
require.NoError(t, err)

ctx := testutils.Context(t)
err = wrapper.Start(ctx)
require.Error(t, err)
}

func ptr[T any](t T) *T { return &t }
Loading