Skip to content

Commit

Permalink
Merge pull request #11671 from smartcontractkit/BCF-2860-2.8.0-backport
Browse files Browse the repository at this point in the history
core/services/relay/evm: start RequestRoundTracker; report full health [2.8.0 backport]
  • Loading branch information
snehaagni authored Jan 3, 2024
2 parents d9e697b + dde004e commit d031129
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 28 deletions.
25 changes: 11 additions & 14 deletions core/services/relay/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/jmoiron/sqlx"
pkgerrors "github.com/pkg/errors"
"go.uber.org/multierr"

"github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator"
"github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median"
Expand Down Expand Up @@ -519,6 +518,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp
return nil, err
}
return &medianProvider{
lggr: lggr.Named("MedianProvider"),
configWatcher: configWatcher,
reportCodec: reportCodec,
contractTransmitter: contractTransmitter,
Expand All @@ -529,6 +529,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp
var _ commontypes.MedianProvider = (*medianProvider)(nil)

type medianProvider struct {
lggr logger.Logger
configWatcher *configWatcher
contractTransmitter ContractTransmitter
reportCodec median.ReportCodec
Expand All @@ -537,26 +538,22 @@ type medianProvider struct {
ms services.MultiStart
}

func (p *medianProvider) Name() string {
return "EVM.MedianProvider"
}
func (p *medianProvider) Name() string { return p.lggr.Name() }

func (p *medianProvider) Start(ctx context.Context) error {
return p.ms.Start(ctx, p.configWatcher, p.contractTransmitter)
return p.ms.Start(ctx, p.configWatcher, p.contractTransmitter, p.medianContract)
}

func (p *medianProvider) Close() error {
return p.ms.Close()
}
func (p *medianProvider) Close() error { return p.ms.Close() }

func (p *medianProvider) Ready() error {
return multierr.Combine(p.configWatcher.Ready(), p.contractTransmitter.Ready())
}
func (p *medianProvider) Ready() error { return nil }

func (p *medianProvider) HealthReport() map[string]error {
report := p.configWatcher.HealthReport()
services.CopyHealth(report, p.contractTransmitter.HealthReport())
return report
hp := map[string]error{p.Name(): p.Ready()}
services.CopyHealth(hp, p.configWatcher.HealthReport())
services.CopyHealth(hp, p.contractTransmitter.HealthReport())
services.CopyHealth(hp, p.medianContract.HealthReport())
return hp
}

func (p *medianProvider) ContractTransmitter() ocrtypes.ContractTransmitter {
Expand Down
23 changes: 19 additions & 4 deletions core/services/relay/evm/median.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/smartcontractkit/libocr/offchainreporting2plus/types"
ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"

"github.com/smartcontractkit/chainlink-common/pkg/services"

"github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm"
offchain_aggregator_wrapper "github.com/smartcontractkit/chainlink/v2/core/internal/gethwrappers2/generated/offchainaggregator"
"github.com/smartcontractkit/chainlink/v2/core/logger"
Expand All @@ -22,12 +24,15 @@ import (
var _ median.MedianContract = &medianContract{}

type medianContract struct {
services.StateMachine
lggr logger.Logger
configTracker types.ContractConfigTracker
contractCaller *ocr2aggregator.OCR2AggregatorCaller
requestRoundTracker *RequestRoundTracker
}

func newMedianContract(configTracker types.ContractConfigTracker, contractAddress common.Address, chain legacyevm.Chain, specID int32, db *sqlx.DB, lggr logger.Logger) (*medianContract, error) {
lggr = lggr.Named("MedianContract")
contract, err := offchain_aggregator_wrapper.NewOffchainAggregator(contractAddress, chain.Client())
if err != nil {
return nil, errors.Wrap(err, "could not instantiate NewOffchainAggregator")
Expand All @@ -44,6 +49,7 @@ func newMedianContract(configTracker types.ContractConfigTracker, contractAddres
}

return &medianContract{
lggr: lggr,
configTracker: configTracker,
contractCaller: contractCaller,
requestRoundTracker: NewRequestRoundTracker(
Expand All @@ -60,13 +66,22 @@ func newMedianContract(configTracker types.ContractConfigTracker, contractAddres
),
}, nil
}

func (oc *medianContract) Start() error {
return oc.requestRoundTracker.Start()
func (oc *medianContract) Start(context.Context) error {
return oc.StartOnce("MedianContract", func() error {
return oc.requestRoundTracker.Start()
})
}

func (oc *medianContract) Close() error {
return oc.requestRoundTracker.Close()
return oc.StopOnce("MedianContract", func() error {
return oc.requestRoundTracker.Close()
})
}

func (oc *medianContract) Name() string { return oc.lggr.Name() }

func (oc *medianContract) HealthReport() map[string]error {
return map[string]error{oc.Name(): oc.Ready()}
}

func (oc *medianContract) LatestTransmissionDetails(ctx context.Context) (ocrtypes.ConfigDigest, uint32, uint8, *big.Int, time.Time, error) {
Expand Down
27 changes: 24 additions & 3 deletions integration-tests/actions/ocr2_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import (

"github.com/smartcontractkit/chainlink-testing-framework/blockchain"
ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client"
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype"
"github.com/smartcontractkit/chainlink/v2/core/store/models"

"github.com/smartcontractkit/chainlink/integration-tests/client"
"github.com/smartcontractkit/chainlink/integration-tests/contracts"

"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype"
"github.com/smartcontractkit/chainlink/v2/core/store/models"
)

// DeployOCRv2Contracts deploys a number of OCRv2 contracts and configures them with defaults
Expand Down Expand Up @@ -379,3 +380,23 @@ func StartNewOCR2Round(
}
return nil
}

// WatchNewOCR2Round is the same as StartNewOCR2Round but does NOT explicitly request a new round
// as that can cause odd behavior in tandem with changing adapter values in OCR2
func WatchNewOCR2Round(
roundNumber int64,
ocrInstances []contracts.OffchainAggregatorV2,
client blockchain.EVMClient,
timeout time.Duration,
logger zerolog.Logger,
) error {
for i := 0; i < len(ocrInstances); i++ {
ocrRound := contracts.NewOffchainAggregatorV2RoundConfirmer(ocrInstances[i], big.NewInt(roundNumber), timeout, logger)
client.AddHeaderEventSubscription(ocrInstances[i].Address(), ocrRound)
err := client.WaitForEvents()
if err != nil {
return fmt.Errorf("failed to wait for event subscriptions of OCR instance %d: %w", i+1, err)
}
}
return nil
}
4 changes: 2 additions & 2 deletions integration-tests/smoke/forwarders_ocr2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestForwarderOCR2Basic(t *testing.T) {
err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, ocrInstances)
require.NoError(t, err, "Error configuring OCRv2 aggregator contracts")

err = actions.StartNewOCR2Round(1, ocrInstances, env.EVMClient, time.Minute*10, l)
err = actions.WatchNewOCR2Round(1, ocrInstances, env.EVMClient, time.Minute*10, l)
require.NoError(t, err)

answer, err := ocrInstances[0].GetLatestAnswer(testcontext.Get(t))
Expand All @@ -100,7 +100,7 @@ func TestForwarderOCR2Basic(t *testing.T) {
ocrRoundVal := (5 + i) % 10
err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, ocrRoundVal)
require.NoError(t, err)
err = actions.StartNewOCR2Round(int64(i), ocrInstances, env.EVMClient, time.Minute*10, l)
err = actions.WatchNewOCR2Round(int64(i), ocrInstances, env.EVMClient, time.Minute*10, l)
require.NoError(t, err)

answer, err = ocrInstances[0].GetLatestAnswer(testcontext.Get(t))
Expand Down
88 changes: 83 additions & 5 deletions integration-tests/smoke/ocr2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/smartcontractkit/chainlink-testing-framework/logging"
"github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext"

"github.com/smartcontractkit/chainlink/integration-tests/actions"
"github.com/smartcontractkit/chainlink/integration-tests/contracts"
"github.com/smartcontractkit/chainlink/integration-tests/docker/test_env"
Expand Down Expand Up @@ -70,7 +71,7 @@ func TestOCRv2Basic(t *testing.T) {
err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts)
require.NoError(t, err, "Error configuring OCRv2 aggregator contracts")

err = actions.StartNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l)
err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err, "Error starting new OCR2 round")
roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
Expand All @@ -81,7 +82,7 @@ func TestOCRv2Basic(t *testing.T) {

err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10)
require.NoError(t, err)
err = actions.StartNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l)
err = actions.WatchNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err)

roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(2))
Expand All @@ -92,6 +93,83 @@ func TestOCRv2Basic(t *testing.T) {
)
}

// Tests that just calling requestNewRound() will properly induce more rounds
func TestOCRv2Request(t *testing.T) {
t.Parallel()
l := logging.GetTestLogger(t)

env, err := test_env.NewCLTestEnvBuilder().
WithTestLogger(t).
WithGeth().
WithMockAdapter().
WithCLNodeConfig(node.NewConfig(node.NewBaseConfig(),
node.WithOCR2(),
node.WithP2Pv2(),
node.WithTracing(),
)).
WithCLNodes(6).
WithFunding(big.NewFloat(.1)).
WithStandardCleanup().
Build()
require.NoError(t, err)

env.ParallelTransactions(true)

nodeClients := env.ClCluster.NodeAPIs()
bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:]

linkToken, err := env.ContractDeployer.DeployLinkTokenContract()
require.NoError(t, err, "Deploying Link Token Contract shouldn't fail")

err = actions.FundChainlinkNodesLocal(workerNodes, env.EVMClient, big.NewFloat(.05))
require.NoError(t, err, "Error funding Chainlink nodes")

// Gather transmitters
var transmitters []string
for _, node := range workerNodes {
addr, err := node.PrimaryEthAddress()
if err != nil {
require.NoError(t, fmt.Errorf("error getting node's primary ETH address: %w", err))
}
transmitters = append(transmitters, addr)
}

ocrOffchainOptions := contracts.DefaultOffChainAggregatorOptions()
aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions)
require.NoError(t, err, "Error deploying OCRv2 aggregator contracts")

err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false)
require.NoError(t, err, "Error creating OCRv2 jobs")

ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions)
require.NoError(t, err, "Error building OCRv2 config")

err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts)
require.NoError(t, err, "Error configuring OCRv2 aggregator contracts")

err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err, "Error starting new OCR2 round")
roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
require.Equal(t, int64(5), roundData.Answer.Int64(),
"Expected latest answer from OCR contract to be 5 but got %d",
roundData.Answer.Int64(),
)

// Keep the mockserver value the same and continually request new rounds
for round := 2; round <= 4; round++ {
err = actions.StartNewOCR2Round(int64(round), aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err, "Error starting new OCR2 round")
roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(int64(round)))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
require.Equal(t, int64(5), roundData.Answer.Int64(),
"Expected round %d answer from OCR contract to be 5 but got %d",
round,
roundData.Answer.Int64(),
)
}
}

func TestOCRv2JobReplacement(t *testing.T) {
l := logging.GetTestLogger(t)

Expand Down Expand Up @@ -144,7 +222,7 @@ func TestOCRv2JobReplacement(t *testing.T) {
err = actions.ConfigureOCRv2AggregatorContracts(env.EVMClient, ocrv2Config, aggregatorContracts)
require.NoError(t, err, "Error configuring OCRv2 aggregator contracts")

err = actions.StartNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l)
err = actions.WatchNewOCR2Round(1, aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err, "Error starting new OCR2 round")
roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
Expand All @@ -155,7 +233,7 @@ func TestOCRv2JobReplacement(t *testing.T) {

err = env.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10)
require.NoError(t, err)
err = actions.StartNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l)
err = actions.WatchNewOCR2Round(2, aggregatorContracts, env.EVMClient, time.Minute*5, l)
require.NoError(t, err)

roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(2))
Expand All @@ -174,7 +252,7 @@ func TestOCRv2JobReplacement(t *testing.T) {
err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, env.EVMClient.GetChainID().Uint64(), false)
require.NoError(t, err, "Error creating OCRv2 jobs")

err = actions.StartNewOCR2Round(3, aggregatorContracts, env.EVMClient, time.Minute*3, l)
err = actions.WatchNewOCR2Round(3, aggregatorContracts, env.EVMClient, time.Minute*3, l)
require.NoError(t, err, "Error starting new OCR2 round")
roundData, err = aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(3))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
Expand Down

0 comments on commit d031129

Please sign in to comment.