Skip to content

Commit e3a1951

Browse files
CRE-531 - Local CRE env artifact (#18544)
* CRE-531 - Generates JSON artifact when booting the local CRE env * CRE-531 - Refactors artifact generation to allow for unit testing * CRE-531 - Uses address ref store interface * CRE-531 - Fixes lint
1 parent 8b54421 commit e3a1951

4 files changed

Lines changed: 265 additions & 2 deletions

File tree

core/scripts/cre/environment/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ logs/
1818
# CRE CLI settings
1919
.cre.settings.yaml
2020
cre.yaml
21-
*.state.yaml
21+
*.state.yaml
22+
23+
# dumped data
24+
env_artifact/**
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package environment
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
pkgerrors "github.com/pkg/errors"
8+
9+
capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb"
10+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
11+
cldf_deployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
12+
capabilities_registry "github.com/smartcontractkit/chainlink-evm/gethwrappers/keystone/generated/capabilities_registry_1_1_0"
13+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/jd"
14+
15+
"github.com/smartcontractkit/chainlink/deployment"
16+
libc "github.com/smartcontractkit/chainlink/system-tests/lib/conversions"
17+
crenode "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/node"
18+
"github.com/smartcontractkit/chainlink/system-tests/lib/cre/types"
19+
)
20+
21+
const (
22+
artifactDirName = "env_artifact"
23+
NOPAdminPrefix = "0xaadd000000000000000000000000000000"
24+
)
25+
26+
type EnvArtifact struct {
27+
AddressRefs []datastore.AddressRef `json:"address_refs"`
28+
AddressBook map[uint64]map[string]cldf_deployment.TypeAndVersion `json:"address_book"`
29+
JdConfig jd.Output `json:"jd_config"`
30+
Nodes NodesArtifact `json:"nodes"`
31+
DONs []DonArtifact `json:"dons"`
32+
Bootstrappers []BootstrapNodeArtifact `json:"bootstrappers"`
33+
NOPs []NOPArtifact `json:"nops"`
34+
}
35+
36+
type NodesArtifact struct {
37+
Nodes map[string]SimpleNodeArtifact `json:"nodes"`
38+
}
39+
40+
type SimpleNodeArtifact struct {
41+
Name string `json:"name"`
42+
}
43+
44+
type DonArtifact struct {
45+
DonName string `json:"don_name"`
46+
DonID int `json:"don_id"`
47+
F uint8 `json:"f"`
48+
BootstrapNodes []string `json:"bootstrap_nodes"`
49+
Capabilities []DONCapabilityArtifact `json:"capabilities,omitempty"`
50+
Nodes []FullNodeArtifact `json:"nodes"`
51+
}
52+
53+
type FullNodeArtifact struct {
54+
Name string `json:"name"`
55+
NOP string `json:"nop"`
56+
CSAKey string `json:"csa_key"`
57+
}
58+
59+
type DONCapabilityArtifact struct {
60+
Capability capabilities_registry.CapabilitiesRegistryCapability `json:"capability"`
61+
Config *DONCapabilityConfig `json:"config,omitempty"`
62+
}
63+
64+
type DONCapabilityConfig struct {
65+
*capabilitiespb.CapabilityConfig
66+
}
67+
68+
type BootstrapNodeArtifact struct {
69+
Name string `json:"name"`
70+
NOP string `json:"nop"`
71+
CSAKey string `json:"csa_key"`
72+
P2PID string `json:"p2p_id"`
73+
OCRUrl string `json:"ocr_url"`
74+
DON2DONUrl string `json:"don2d_url"`
75+
}
76+
77+
type NOPArtifact struct {
78+
ID int `json:"id"`
79+
Name string `json:"name"`
80+
Admin string `json:"admin"`
81+
}
82+
83+
func DumpArtifact(
84+
datastore datastore.AddressRefStore,
85+
addressBook cldf_deployment.AddressBook,
86+
jdOutput jd.Output,
87+
donTopology types.DonTopology,
88+
offchainClient cldf_deployment.OffchainClient,
89+
capabilityFactoryFns []types.DONCapabilityWithConfigFactoryFn,
90+
) error {
91+
artifact, err := GenerateArtifact(datastore, addressBook, jdOutput, donTopology, offchainClient, capabilityFactoryFns)
92+
if err != nil {
93+
return pkgerrors.Wrap(err, "failed to generate environment artifact")
94+
}
95+
96+
// Let's save the artifact to disk
97+
return persistArtifact(artifact)
98+
}
99+
100+
func GenerateArtifact(
101+
ds datastore.AddressRefStore,
102+
addressBook cldf_deployment.AddressBook,
103+
jdOutput jd.Output,
104+
donTopology types.DonTopology,
105+
offchainClient cldf_deployment.OffchainClient,
106+
capabilityFactoryFns []types.DONCapabilityWithConfigFactoryFn,
107+
) (*EnvArtifact, error) {
108+
var err error
109+
110+
addresses, err := addressBook.Addresses()
111+
if err != nil {
112+
return nil, pkgerrors.Wrap(err, "failed to get addresses from address book")
113+
}
114+
115+
addressRecords, err := ds.Fetch()
116+
if err != nil {
117+
return nil, pkgerrors.Wrap(err, "failed to fetch address records from datastore")
118+
}
119+
120+
artifact := EnvArtifact{
121+
JdConfig: jdOutput,
122+
AddressBook: addresses,
123+
AddressRefs: addressRecords,
124+
Nodes: NodesArtifact{
125+
Nodes: make(map[string]SimpleNodeArtifact),
126+
},
127+
DONs: make([]DonArtifact, 0),
128+
Bootstrappers: make([]BootstrapNodeArtifact, 0),
129+
NOPs: make([]NOPArtifact, 0),
130+
}
131+
132+
for i, don := range donTopology.DonsWithMetadata {
133+
donArtifact := DonArtifact{
134+
DonName: don.Name,
135+
DonID: int(don.ID),
136+
F: 0, // F will be calculated based on the number of worker nodes
137+
BootstrapNodes: make([]string, 0),
138+
Nodes: make([]FullNodeArtifact, 0),
139+
Capabilities: make([]DONCapabilityArtifact, 0),
140+
}
141+
142+
workerNodes, workerNodesErr := crenode.FindManyWithLabel(don.NodesMetadata, &types.Label{
143+
Key: crenode.NodeTypeKey,
144+
Value: types.WorkerNode,
145+
}, crenode.EqualLabels)
146+
if workerNodesErr != nil {
147+
return nil, pkgerrors.Wrap(workerNodesErr, "failed to find worker nodes")
148+
}
149+
150+
donArtifact.F = libc.MustSafeUint8((len(workerNodes) - 1) / 3)
151+
152+
for _, factoryFn := range capabilityFactoryFns {
153+
capabilities := factoryFn(don.Flags)
154+
for _, capability := range capabilities {
155+
donArtifact.Capabilities = append(donArtifact.Capabilities, DONCapabilityArtifact{
156+
Capability: capabilities_registry.CapabilitiesRegistryCapability{
157+
Version: capability.Capability.Version,
158+
LabelledName: capability.Capability.LabelledName,
159+
CapabilityType: capability.Capability.CapabilityType,
160+
},
161+
Config: &DONCapabilityConfig{capability.Config},
162+
})
163+
}
164+
}
165+
166+
nop := NOPArtifact{
167+
ID: i + 1, // NOP IDs start from 1
168+
Name: fmt.Sprintf("NOP for %s DON", don.Name),
169+
Admin: fmt.Sprintf("%s%06d", NOPAdminPrefix, i+1),
170+
}
171+
172+
var nodeIDs []string
173+
for _, node := range don.DON.Nodes {
174+
nodeIDs = append(nodeIDs, node.NodeID)
175+
}
176+
177+
nodeInfo, nodeInfoErr := deployment.NodeInfo(nodeIDs, offchainClient)
178+
if nodeInfoErr != nil {
179+
return nil, pkgerrors.Wrapf(nodeInfoErr, "failed to get node info for DON %s", don.Name)
180+
}
181+
182+
for _, node := range nodeInfo {
183+
if node.IsBootstrap {
184+
donArtifact.BootstrapNodes = append(donArtifact.BootstrapNodes, node.Name)
185+
artifact.Bootstrappers = append(artifact.Bootstrappers, BootstrapNodeArtifact{
186+
NOP: nop.Name,
187+
Name: node.Name,
188+
CSAKey: node.CSAKey,
189+
P2PID: node.PeerID.Raw(),
190+
OCRUrl: "", // TODO: this will be needed to distribute job specs
191+
DON2DONUrl: "",
192+
})
193+
continue
194+
}
195+
196+
artifact.Nodes.Nodes[node.NodeID] = SimpleNodeArtifact{Name: node.Name}
197+
donArtifact.Nodes = append(donArtifact.Nodes, FullNodeArtifact{
198+
NOP: nop.Name,
199+
Name: node.Name,
200+
CSAKey: node.CSAKey,
201+
})
202+
}
203+
204+
artifact.NOPs = append(artifact.NOPs, nop)
205+
artifact.DONs = append(artifact.DONs, donArtifact)
206+
}
207+
208+
// Let's save the artifact to disk
209+
if err = persistArtifact(&artifact); err != nil {
210+
return nil, pkgerrors.Wrap(err, "failed to persist environment artifact")
211+
}
212+
213+
return &artifact, nil
214+
}
215+
216+
func persistArtifact(artifact *EnvArtifact) error {
217+
err := os.MkdirAll(artifactDirName, 0755)
218+
if err != nil {
219+
return pkgerrors.Wrap(err, "failed to create directory for the environment artifact")
220+
}
221+
err = WriteJSONFile(artifactDirName+"/env_artifact.json", artifact)
222+
if err != nil {
223+
return pkgerrors.Wrap(err, "failed to write environment artifact to file")
224+
}
225+
226+
return nil
227+
}

system-tests/lib/cre/environment/environment.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func SetupTestEnvironment(
124124
}
125125
}()
126126

127-
stageGen := NewStageGen(7, "STAGE")
127+
stageGen := NewStageGen(8, "STAGE")
128128

129129
fmt.Print(libformat.PurpleText("%s", stageGen.Wrap("Starting MinIO")))
130130

@@ -499,6 +499,23 @@ func SetupTestEnvironment(
499499

500500
fmt.Print(libformat.PurpleText("%s", stageGen.WrapAndNext("OCR3 and Keystone contracts configured in %.2f seconds", stageGen.Elapsed().Seconds())))
501501

502+
fmt.Print(libformat.PurpleText("%s", stageGen.Wrap("Writing bootstrapping data into disk (address book, data store, etc...)")))
503+
504+
err = DumpArtifact(
505+
memoryDatastore.AddressRefStore,
506+
allChainsCLDEnvironment.ExistingAddresses, //nolint:staticcheck // won't migrate now
507+
*jdOutput,
508+
*fullCldOutput.DonTopology,
509+
fullCldOutput.Environment.Offchain,
510+
input.CapabilitiesContractFactoryFunctions,
511+
)
512+
if err != nil {
513+
testLogger.Error().Err(err).Msg("failed to generate artifact")
514+
fmt.Print(libformat.PurpleText("%s", stageGen.WrapAndNext("Failed to write bootstrapping data into disk in %.2f seconds", stageGen.Elapsed().Seconds())))
515+
} else {
516+
fmt.Print(libformat.PurpleText("%s", stageGen.WrapAndNext("Wrote bootstrapping data into disk in %.2f seconds", stageGen.Elapsed().Seconds())))
517+
}
518+
502519
// block on background stages
503520
backgroundStagesWaitGroup.Wait()
504521
close(backgroundStagesCh)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package environment
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
)
7+
8+
// WriteJSONFile marshals data into pretty JSON and writes it at path.
9+
func WriteJSONFile(path string, data any) error {
10+
b, err := json.MarshalIndent(data, "", " ")
11+
if err != nil {
12+
return err
13+
}
14+
15+
return os.WriteFile(path, b, 0600)
16+
}

0 commit comments

Comments
 (0)