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

add migration and orm changes for additional lgs fields and delegate #10722

2 changes: 2 additions & 0 deletions core/config/docs/core.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ FeedsManager = true # Default
LogPoller = false # Default
# UICSAKeys enables CSA Keys in the UI.
UICSAKeys = false # Default
# EAL is an experimental feature that exposes API endpoints for reading and writing to blockchains.
EAL = false # Default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it an acronym?


[Database]
# DefaultIdleInTxSessionTimeout is the maximum time allowed for a transaction to be open and idle before timing out. See Postgres `idle_in_transaction_session_timeout` for more details.
Expand Down
1 change: 1 addition & 0 deletions core/config/feature_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ type Feature interface {
FeedsManager() bool
UICSAKeys() bool
LogPoller() bool
EAL() bool
}
4 changes: 4 additions & 0 deletions core/config/toml/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ type Feature struct {
FeedsManager *bool
LogPoller *bool
UICSAKeys *bool
EAL *bool
}

func (f *Feature) setFrom(f2 *Feature) {
Expand All @@ -298,6 +299,9 @@ func (f *Feature) setFrom(f2 *Feature) {
if v := f2.UICSAKeys; v != nil {
f.UICSAKeys = v
}
if v := f2.EAL; v != nil {
f.EAL = v
}
}

type Database struct {
Expand Down
25 changes: 24 additions & 1 deletion core/services/chainlink/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keeper"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
lgsservice "github.com/smartcontractkit/chainlink/v2/core/services/legacygasstation"
"github.com/smartcontractkit/chainlink/v2/core/services/ocr"
"github.com/smartcontractkit/chainlink/v2/core/services/ocr2"
"github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap"
Expand Down Expand Up @@ -103,6 +104,8 @@ type Application interface {
ID() uuid.UUID

SecretGenerator() SecretGenerator

LegacyGasStationRequestRouter() lgsservice.RequestRouter
}

// ChainlinkApplication contains fields for the JobSubscriber, Scheduler,
Expand Down Expand Up @@ -135,6 +138,7 @@ type ChainlinkApplication struct {
secretGenerator SecretGenerator
profiler *pyroscope.Profiler
loopRegistry *plugins.LoopRegistry
lgsRequestRouter lgsservice.RequestRouter

started bool
startStopMu sync.Mutex
Expand Down Expand Up @@ -324,8 +328,22 @@ func NewApplication(opts ApplicationOpts) (Application, error) {
legacyEVMChains,
keyStore.Eth(),
globalLogger),
job.LegacyGasStationServer: lgsservice.NewServerDelegate(
globalLogger,
legacyEVMChains,
keyStore.Eth(),
db,
cfg.Database(),
),
job.LegacyGasStationSidecar: lgsservice.NewSidecarDelegate(
globalLogger,
legacyEVMChains,
keyStore.Eth(),
db,
),
}
webhookJobRunner = delegates[job.Webhook].(*webhook.Delegate).WebhookJobRunner()
lgsRequestRouter = delegates[job.LegacyGasStationServer].(*lgsservice.Delegate).RequestRouter()
)

// Flux monitor requires ethereum just to boot, silence errors with a null delegate
Expand Down Expand Up @@ -468,7 +486,8 @@ func NewApplication(opts ApplicationOpts) (Application, error) {
profiler: profiler,
loopRegistry: loopRegistry,

sqlxDB: opts.SqlxDB,
sqlxDB: opts.SqlxDB,
lgsRequestRouter: lgsRequestRouter,

// NOTE: Can keep things clean by putting more things in srvcs instead of manually start/closing
srvcs: srvcs,
Expand Down Expand Up @@ -668,6 +687,10 @@ func (app *ChainlinkApplication) SecretGenerator() SecretGenerator {
return app.secretGenerator
}

func (app *ChainlinkApplication) LegacyGasStationRequestRouter() lgsservice.RequestRouter {
return app.lgsRequestRouter
}

// WakeSessionReaper wakes up the reaper to do its reaping.
func (app *ChainlinkApplication) WakeSessionReaper() {
app.SessionReaper.WakeUp()
Expand Down
4 changes: 4 additions & 0 deletions core/services/chainlink/config_feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ func (f *featureConfig) LogPoller() bool {
func (f *featureConfig) UICSAKeys() bool {
return *f.c.UICSAKeys
}

func (f *featureConfig) EAL() bool {
return *f.c.EAL
}
1 change: 1 addition & 0 deletions core/services/chainlink/config_general_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func TestTOMLGeneralConfig_Defaults(t *testing.T) {
assert.False(t, config.CosmosEnabled())
assert.False(t, config.SolanaEnabled())
assert.False(t, config.StarkNetEnabled())
assert.False(t, config.Feature().EAL())
assert.Equal(t, false, config.JobPipeline().ExternalInitiatorsEnabled())
assert.Equal(t, 15*time.Minute, config.WebServer().SessionTimeout().Duration())
}
Expand Down
2 changes: 2 additions & 0 deletions core/services/chainlink/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ func TestConfig_Marshal(t *testing.T) {
FeedsManager: ptr(true),
LogPoller: ptr(true),
UICSAKeys: ptr(true),
EAL: ptr(true),
}
full.Database = toml.Database{
DefaultIdleInTxSessionTimeout: models.MustNewDuration(time.Minute),
Expand Down Expand Up @@ -654,6 +655,7 @@ Headers = ['Authorization: token', 'X-SomeOther-Header: value with spaces | and
FeedsManager = true
LogPoller = true
UICSAKeys = true
EAL = true
`},
{"Database", Config{Core: toml.Core{Database: full.Database}}, `[Database]
DefaultIdleInTxSessionTimeout = '1m0s'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s'
FeedsManager = true
LogPoller = false
UICSAKeys = false
EAL = false

[Database]
DefaultIdleInTxSessionTimeout = '1h0m0s'
Expand Down
1 change: 1 addition & 0 deletions core/services/chainlink/testdata/config-full.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ShutdownGracePeriod = '10s'
FeedsManager = true
LogPoller = true
UICSAKeys = true
EAL = true

[Database]
DefaultIdleInTxSessionTimeout = '1m0s'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ShutdownGracePeriod = '5s'
FeedsManager = true
LogPoller = false
UICSAKeys = false
EAL = false

[Database]
DefaultIdleInTxSessionTimeout = '1h0m0s'
Expand Down
117 changes: 117 additions & 0 deletions core/services/eal/blockchain_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package eal

import (
"context"
"math/big"

"github.com/pkg/errors"

"github.com/ethereum/go-ethereum"
gethcommon "github.com/ethereum/go-ethereum/common"
libcommon "github.com/smartcontractkit/capital-markets-projects/lib/common"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
)

var _ libcommon.BlockchainClientInterface = &BlockchainClient{}

type BlockchainClient struct {
lggr logger.Logger
txm txmgr.TxManager
gethks keystore.Eth
fromAddresses []ethkey.EIP55Address
chainID uint64
cfg config.EVM
client client.Client
}

func NewBlockchainClient(
lggr logger.Logger,
txm txmgr.TxManager,
gethks keystore.Eth,
fromAddresses []ethkey.EIP55Address,
chainID uint64,
cfg config.EVM,
client client.Client,
) (*BlockchainClient, error) {
return &BlockchainClient{
lggr: lggr,
txm: txm,
gethks: gethks,
fromAddresses: fromAddresses,
chainID: chainID,
cfg: cfg,
client: client,
}, nil
}

func (c *BlockchainClient) EstimateGas(
ctx context.Context,
address gethcommon.Address,
payload []byte,
) (uint32, error) {
fromAddresses := c.sendingKeys()
fromAddress, err := c.gethks.GetRoundRobinAddress(big.NewInt(0).SetUint64(c.chainID), fromAddresses...)
if err != nil {
return 0, err
}
c.lggr.Debugw("estimate gas details",
"toAddress", address,
"fromAddress", fromAddress,
)
gasLimit, err := c.client.EstimateGas(ctx, ethereum.CallMsg{
From: fromAddress,
To: &address,
Data: payload,
})
// TODO: change errors to constants in eal lib
if err != nil {
return 0, errors.Wrap(err, "failed to estimate gas")
}

if gasLimit > uint64(c.cfg.GasEstimator().LimitMax()) {
return 0, errors.New("estimated gas limit exceeds max")
}
// safe cast because gas estimator limit max is uint32
return uint32(gasLimit), nil
}

// SimulateTransaction makes eth_call to simulate transaction
// TODO: look into accepting optional parameters (gas, gasPrice, value)
func (c *BlockchainClient) SimulateTransaction(
ctx context.Context,
address gethcommon.Address,
payload []byte,
gasLimit uint32,
) error {
fromAddresses := c.sendingKeys()
fromAddress, err := c.gethks.GetRoundRobinAddress(big.NewInt(0).SetUint64(c.chainID), fromAddresses...)
if err != nil {
return err
}
c.lggr.Debugw("eth_call details",
"toAddress", address,
"fromAddress", fromAddress,
"gasLimit", gasLimit,
)
_, err = c.client.CallContract(ctx, ethereum.CallMsg{
To: &address,
From: fromAddress,
Data: payload,
Gas: uint64(gasLimit),
}, nil /*blocknumber*/)

return err
}

func (c *BlockchainClient) sendingKeys() []gethcommon.Address {
var addresses []gethcommon.Address
for _, a := range c.fromAddresses {
addresses = append(addresses, a.Address())
}
return addresses
}
54 changes: 54 additions & 0 deletions core/services/job/job_orm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/smartcontractkit/chainlink-relay/pkg/types"

"github.com/smartcontractkit/chainlink/v2/core/services/legacygasstation"
"github.com/smartcontractkit/chainlink/v2/core/services/relay"

"github.com/smartcontractkit/chainlink/v2/core/assets"
Expand Down Expand Up @@ -395,6 +396,59 @@ func TestORM_DeleteJob_DeletesAssociatedRecords(t *testing.T) {
cltest.AssertCount(t, db, "jobs", 0)
})

t.Run("it creates and deletes records for legacy gas station server job", func(t *testing.T) {
jb, err := legacygasstation.ValidatedServerSpec(
testspecs.GenerateLegacyGasStationServerSpec(testspecs.LegacyGasStationServerSpecParams{}).Toml())
require.NoError(t, err)

err = jobORM.CreateJob(&jb)
require.NoError(t, err)
savedJob, err := jobORM.FindJob(testutils.Context(t), jb.ID)
require.NoError(t, err)
require.Equal(t, jb.ID, savedJob.ID)
require.Equal(t, jb.Type, savedJob.Type)
require.Equal(t, jb.LegacyGasStationServerSpec.ID, savedJob.LegacyGasStationServerSpec.ID)
require.Equal(t, jb.LegacyGasStationServerSpec.ForwarderAddress, savedJob.LegacyGasStationServerSpec.ForwarderAddress)
require.Equal(t, jb.LegacyGasStationServerSpec.EVMChainID, savedJob.LegacyGasStationServerSpec.EVMChainID)
require.Equal(t, jb.LegacyGasStationServerSpec.CCIPChainSelector, savedJob.LegacyGasStationServerSpec.CCIPChainSelector)
require.Equal(t, jb.LegacyGasStationServerSpec.FromAddresses, savedJob.LegacyGasStationServerSpec.FromAddresses)
err = jobORM.DeleteJob(jb.ID)
require.NoError(t, err)
_, err = jobORM.FindJob(testutils.Context(t), jb.ID)
require.Error(t, err)
})

t.Run("it creates and deletes records for legacy gas station sidecar jobs", func(t *testing.T) {
jb, err := legacygasstation.ValidatedSidecarSpec(
testspecs.GenerateLegacyGasStationSidecarSpec(testspecs.LegacyGasStationSidecarSpecParams{
ClientCertificate: ptr("clientCertificate"),
ClientKey: ptr("clientKey"),
}).Toml())
require.NoError(t, err)

err = jobORM.CreateJob(&jb)
require.NoError(t, err)
savedJob, err := jobORM.FindJob(testutils.Context(t), jb.ID)
require.NoError(t, err)
require.Equal(t, jb.ID, savedJob.ID)
require.Equal(t, jb.Type, savedJob.Type)
require.Equal(t, jb.LegacyGasStationSidecarSpec.ID, savedJob.LegacyGasStationSidecarSpec.ID)
require.Equal(t, jb.LegacyGasStationSidecarSpec.ForwarderAddress, savedJob.LegacyGasStationSidecarSpec.ForwarderAddress)
require.Equal(t, jb.LegacyGasStationSidecarSpec.OffRampAddress, savedJob.LegacyGasStationSidecarSpec.OffRampAddress)
require.Equal(t, jb.LegacyGasStationSidecarSpec.LookbackBlocks, savedJob.LegacyGasStationSidecarSpec.LookbackBlocks)
require.Equal(t, jb.LegacyGasStationSidecarSpec.PollPeriod, savedJob.LegacyGasStationSidecarSpec.PollPeriod)
require.Equal(t, jb.LegacyGasStationSidecarSpec.RunTimeout, savedJob.LegacyGasStationSidecarSpec.RunTimeout)
require.Equal(t, jb.LegacyGasStationSidecarSpec.EVMChainID, savedJob.LegacyGasStationSidecarSpec.EVMChainID)
require.Equal(t, jb.LegacyGasStationSidecarSpec.CCIPChainSelector, savedJob.LegacyGasStationSidecarSpec.CCIPChainSelector)
require.Equal(t, jb.LegacyGasStationSidecarSpec.StatusUpdateURL, savedJob.LegacyGasStationSidecarSpec.StatusUpdateURL)
require.Equal(t, jb.LegacyGasStationSidecarSpec.ClientCertificate, savedJob.LegacyGasStationSidecarSpec.ClientCertificate)
require.Equal(t, jb.LegacyGasStationSidecarSpec.ClientKey, savedJob.LegacyGasStationSidecarSpec.ClientKey)
err = jobORM.DeleteJob(jb.ID)
require.NoError(t, err)
_, err = jobORM.FindJob(testutils.Context(t), jb.ID)
require.Error(t, err)
})

t.Run("does not allow to delete external initiators if they have referencing external_initiator_webhook_specs", func(t *testing.T) {
// create new db because this will rollback transaction and poison it
db := pgtest.NewSqlxDB(t)
Expand Down
9 changes: 9 additions & 0 deletions core/services/job/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,15 @@ type LegacyGasStationSidecarSpec struct {
// CCIPChainSelector is the CCIP chain selector that corresponds to EVMChainID param
CCIPChainSelector *utils.Big `toml:"ccipChainSelector"`

// StatusUpdateURL is the endpoint URL where the sidecar posts status updates
StatusUpdateURL string `toml:"statusUpdateURL"`

// ClientCertificate is the x.509 certificate used for mTLS connection with StatusUpdateURL
ClientCertificate *string `toml:"clientCertificate"`

// ClientCertificate is the x.509 key used for mTLS connection with StatusUpdateURL
ClientKey *string `toml:"clientKey"`

// CreatedAt is the time this job was created.
CreatedAt time.Time `toml:"-"`

Expand Down
4 changes: 2 additions & 2 deletions core/services/job/orm.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ func (o *orm) CreateJob(jb *Job, qopts ...pg.QOpt) error {
return errors.New("evm chain id must be defined")
}
var specID int32
sql := `INSERT INTO legacy_gas_station_sidecar_specs (forwarder_address, off_ramp_address, lookback_blocks, poll_period, run_timeout, evm_chain_id, ccip_chain_selector, created_at, updated_at)
VALUES (:forwarder_address, :off_ramp_address, :lookback_blocks, :poll_period, :run_timeout, :evm_chain_id, :ccip_chain_selector, NOW(), NOW())
sql := `INSERT INTO legacy_gas_station_sidecar_specs (forwarder_address, off_ramp_address, lookback_blocks, poll_period, run_timeout, evm_chain_id, ccip_chain_selector, status_update_url, client_certificate, client_key, created_at, updated_at)
VALUES (:forwarder_address, :off_ramp_address, :lookback_blocks, :poll_period, :run_timeout, :evm_chain_id, :ccip_chain_selector, :status_update_url, :client_certificate, :client_key, NOW(), NOW())
RETURNING id;`
if err := pg.PrepareQueryRowx(tx, sql, &specID, jb.LegacyGasStationSidecarSpec); err != nil {
return errors.Wrap(err, "failed to create LegacyGasStationSidecar spec")
Expand Down
Loading