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

(feat:CRE-86): Forwarder view - revisited #16674

Draft
wants to merge 13 commits into
base: task/CRE-338/keystone-forwarder-deployment-lbls
Choose a base branch
from
2 changes: 1 addition & 1 deletion deployment/ccip/changeset/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

var _ deployment.ViewState = ViewCCIP

func ViewCCIP(e deployment.Environment) (json.Marshaler, error) {
func ViewCCIP(e deployment.Environment, _ []byte) (json.Marshaler, error) {
state, err := LoadOnchainState(e)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion deployment/ccip/changeset/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ import (
func TestSmokeView(t *testing.T) {
t.Parallel()
tenv, _ := testhelpers.NewMemoryEnvironment(t, testhelpers.WithNumOfChains(3))
_, err := changeset.ViewCCIP(tenv.Env)
_, err := changeset.ViewCCIP(tenv.Env, nil)
require.NoError(t, err)
}
2 changes: 1 addition & 1 deletion deployment/changeset.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ type ChangesetOutput struct {

// ViewState produces a product specific JSON representation of
// the on and offchain state of the environment.
type ViewState func(e Environment) (json.Marshaler, error)
type ViewState func(e Environment, previousState []byte) (json.Marshaler, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make this backward compatibility, ie add a ViewStateV2
and make the []byte into json.Marshaller

2 changes: 1 addition & 1 deletion deployment/data-feeds/changeset/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

var _ deployment.ViewState = ViewDataFeeds

func ViewDataFeeds(e deployment.Environment) (json.Marshaler, error) {
func ViewDataFeeds(e deployment.Environment, _ []byte) (json.Marshaler, error) {
state, err := LoadOnchainState(e)
fmt.Println(state)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion deployment/exemplar/changeset/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (v *ExemplarView) MarshalJSON() ([]byte, error) {
}

// ViewExemplar extracts basic information from the environment
var ViewExemplar deployment.ViewState = func(e deployment.Environment) (json.Marshaler, error) {
var ViewExemplar deployment.ViewState = func(e deployment.Environment, _ []byte) (json.Marshaler, error) {
lggr := e.Logger
lggr.Info("Generating exemplar state view")

Expand Down
149 changes: 118 additions & 31 deletions deployment/keystone/changeset/state.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package changeset

import (
"context"
"errors"
"fmt"
"sync"
"time"

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

Expand Down Expand Up @@ -33,10 +36,15 @@ type GetContractSetsResponse struct {
ContractSets map[uint64]ContractSet
}

type ForwarderContract struct {
Contract *forwarder.KeystoneForwarder
TypeAndVersion deployment.TypeAndVersion
}

type ContractSet struct {
commonchangeset.MCMSWithTimelockState
OCR3 map[common.Address]*ocr3_capability.OCR3Capability
Forwarder *forwarder.KeystoneForwarder
Forwarder *ForwarderContract
CapabilitiesRegistry *capabilities_registry.CapabilitiesRegistry
WorkflowRegistry *workflow_registry.WorkflowRegistry
}
Expand All @@ -46,7 +54,7 @@ func (cs ContractSet) Convert() internal.ContractSet {
MCMSWithTimelockState: commonchangeset.MCMSWithTimelockState{
MCMSWithTimelockContracts: cs.MCMSWithTimelockContracts,
},
Forwarder: cs.Forwarder,
Forwarder: cs.Forwarder.Contract,
WorkflowRegistry: cs.WorkflowRegistry,
OCR3: cs.OCR3,
CapabilitiesRegistry: cs.CapabilitiesRegistry,
Expand All @@ -61,7 +69,7 @@ func (cs ContractSet) TransferableContracts() []common.Address {
}
}
if cs.Forwarder != nil {
out = append(out, cs.Forwarder.Address())
out = append(out, cs.Forwarder.Contract.Address())
}
if cs.CapabilitiesRegistry != nil {
out = append(out, cs.CapabilitiesRegistry.Address())
Expand All @@ -73,47 +81,123 @@ func (cs ContractSet) TransferableContracts() []common.Address {
}

// View is a view of the keystone chain
// It is best effort and logs errors
func (cs ContractSet) View(lggr logger.Logger) (KeystoneChainView, error) {
// It is best-effort, logs errors and generates the views in parallel.
func (cs ContractSet) View(ctx context.Context, prevView KeystoneChainView, lggr logger.Logger) (KeystoneChainView, error) {
out := NewKeystoneChainView()
var allErrs error
var wg sync.WaitGroup
errCh := make(chan error, 4) // We are generating 4 views concurrently

ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()

if cs.CapabilitiesRegistry != nil {
capRegView, err := common_v1_0.GenerateCapabilityRegistryView(cs.CapabilitiesRegistry)
if err != nil {
allErrs = errors.Join(allErrs, err)
lggr.Warn("failed to generate capability registry view: %w", err)
}
out.CapabilityRegistry[cs.CapabilitiesRegistry.Address().String()] = capRegView
wg.Add(1)
go func() {
defer wg.Done()
capRegView, err := common_v1_0.GenerateCapabilityRegistryView(cs.CapabilitiesRegistry)
if err != nil {
lggr.Warn("failed to generate capability registry view: %w", err)
errCh <- err
}
out.CapabilityRegistry[cs.CapabilitiesRegistry.Address().String()] = capRegView
}()
}

if cs.OCR3 != nil {
for addr, ocr3Cap := range cs.OCR3 {
oc := *ocr3Cap
addrCopy := addr
ocrView, err := GenerateOCR3ConfigView(oc)
if err != nil {
allErrs = errors.Join(allErrs, err)
// don't block view on single OCR3 not being configured
if errors.Is(err, ErrOCR3NotConfigured) {
lggr.Warnf("ocr3 not configured for address %s", addr)
} else {
lggr.Errorf("failed to generate OCR3 config view: %v", err)
wg.Add(1)
go func() {
defer wg.Done()
for addr, ocr3Cap := range cs.OCR3 {
select {
case <-ctx.Done():
return
default:
oc := *ocr3Cap
addrCopy := addr
ocrView, err := GenerateOCR3ConfigView(ctx, oc)
if err != nil {
// don't block view on single OCR3 not being configured
if errors.Is(err, ErrOCR3NotConfigured) {
lggr.Warnf("ocr3 not configured for address %s", addr)
} else {
lggr.Errorf("failed to generate OCR3 config view: %v", err)
errCh <- err
}
continue
}
out.OCRContracts[addrCopy.String()] = ocrView
}
}
out.OCRContracts[addrCopy.String()] = ocrView
}
}()
}

// Process the workflow registry and print if WorkflowRegistryError errors.
if cs.WorkflowRegistry != nil {
wrView, wrErrs := common_v1_0.GenerateWorkflowRegistryView(cs.WorkflowRegistry)
for _, err := range wrErrs {
allErrs = errors.Join(allErrs, err)
lggr.Errorf("WorkflowRegistry error: %v", err)
}
out.WorkflowRegistry[cs.WorkflowRegistry.Address().String()] = wrView
wg.Add(1)
go func() {
defer wg.Done()
wrView, wrErrs := common_v1_0.GenerateWorkflowRegistryView(cs.WorkflowRegistry)
for _, err := range wrErrs {
lggr.Errorf("WorkflowRegistry error: %v", err)
errCh <- err
}
out.WorkflowRegistry[cs.WorkflowRegistry.Address().String()] = wrView
}()
}

if cs.Forwarder != nil {
wg.Add(1)
go func() {
defer wg.Done()
fwrAddr := cs.Forwarder.Contract.Address().String()
var prevViews []ForwarderView
if prevView.Forwarders != nil {
pv, ok := prevView.Forwarders[fwrAddr]
if !ok {
prevViews = []ForwarderView{}
} else {
prevViews = pv
}
} else {
prevViews = []ForwarderView{}
}

select {
case <-ctx.Done():
errCh <- ctx.Err()
return
default:
fwrView, fwrErr := GenerateForwarderView(ctx, cs.Forwarder, prevViews)
if fwrErr != nil {
// don't block view on single forwarder not being configured
switch {
case errors.Is(fwrErr, ErrForwarderNotConfigured):
lggr.Warnf("forwarder not configured for address %s", cs.Forwarder.Contract.Address())
case errors.Is(fwrErr, context.Canceled), errors.Is(fwrErr, context.DeadlineExceeded):
lggr.Warnf("forwarder view generation cancelled for address %s", cs.Forwarder.Contract.Address())
errCh <- fwrErr
default:
lggr.Errorf("failed to generate forwarder view: %v", fwrErr)
errCh <- fwrErr
}
} else {
out.Forwarders[fwrAddr] = fwrView
}
}
}()
}

wg.Wait()
close(errCh)

var errList []error
// Collect all errors
for err := range errCh {
errList = append(errList, err)
}
allErrs = errors.Join(errList...)

return out, allErrs
}

Expand Down Expand Up @@ -179,7 +263,10 @@ func loadContractSet(lggr logger.Logger, chain deployment.Chain, addresses map[s
if err != nil {
return nil, fmt.Errorf("failed to create forwarder contract from address %s: %w", addr, err)
}
out.Forwarder = c
out.Forwarder = &ForwarderContract{
Contract: c,
TypeAndVersion: tv,
}
case OCR3Capability:
c, err := ocr3_capability.NewOCR3Capability(common.HexToAddress(addr), chain.Client)
if err != nil {
Expand Down
12 changes: 10 additions & 2 deletions deployment/keystone/changeset/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

var _ deployment.ViewState = ViewKeystone

func ViewKeystone(e deployment.Environment) (json.Marshaler, error) {
func ViewKeystone(e deployment.Environment, previousView []byte) (json.Marshaler, error) {
lggr := e.Logger
state, err := GetContractSets(e.Logger, &GetContractSetsRequest{
Chains: e.Chains,
Expand All @@ -23,6 +23,14 @@ func ViewKeystone(e deployment.Environment) (json.Marshaler, error) {
if err != nil {
return nil, fmt.Errorf("failed to get contract sets: %w", err)
}
var prevView KeystoneView
if len(previousView) == 0 {
prevView.Chains = make(map[string]KeystoneChainView)
} else if err = json.Unmarshal(previousView, &prevView); err != nil {
lggr.Warnf("failed to unmarshal previous keystone view: %v", err)
prevView.Chains = make(map[string]KeystoneChainView)
}

var viewErrs error
chainViews := make(map[string]KeystoneChainView)
for chainSel, contracts := range state.ContractSets {
Expand All @@ -40,7 +48,7 @@ func ViewKeystone(e deployment.Environment) (json.Marshaler, error) {
viewErrs = errors.Join(viewErrs, err2)
continue
}
v, err := contracts.View(e.Logger)
v, err := contracts.View(e.GetContext(), prevView.Chains[chainName], e.Logger)
if err != nil {
err2 := fmt.Errorf("failed to view chain %s: %w", chainName, err)
lggr.Error(err2)
Expand Down
Loading
Loading