-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CAPPL-19] Gateway Connector service wrapper (#14356)
* service wrapper * PR updates: simplify connector into signer and consolidate files * keep signer as handler with empty methods for now * lint * PR comments except for unit test * package lint * unit test v1 * fix unit tests * PR Review: fix config, separate tests * PR review: clean up pointers, service wrapper generation. * fix mocks return args issue * add logs to figure out Sign issue * fix keys issues * typos * remove redundant named import * update order of deps to try to fix strange lint error * remove handlegatewaymessage * change name to gwcommon * remove unused funcs and clean up var reuse * add clock to servicewrapper * fix bug in configs * fix Ready and healthreport * remove connectorSigner struct * remove privateKey from constructor, keep initialization in Start * lint --------- Co-authored-by: Bolek <[email protected]>
- Loading branch information
1 parent
356c70c
commit 51cb378
Showing
2 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
80
core/capabilities/gateway_connector/service_wrapper_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |