-
Notifications
You must be signed in to change notification settings - Fork 3
[CLD-2461]: feat(datastore): new WriteMetadata util #1007
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package datastore | ||
|
|
||
| import "fmt" | ||
|
|
||
| // MetadataBundle is address refs plus contract, chain, and env metadata. | ||
| type MetadataBundle struct { | ||
| // Addresses are the contract addresses that were deployed. | ||
| Addresses []AddressRef | ||
| // Contracts defines any metadata pertaining to contracts that were deployed. | ||
| Contracts []ContractMetadata | ||
| // Chain defines any metadata pertaining to the chain that was operated against. | ||
| Chain *ChainMetadata | ||
| // Env defines any metadata pertaining to the environment that was deployed to. | ||
| Env *EnvMetadata | ||
| } | ||
|
|
||
| // WriteMetadataToDatastore adds address refs and upserts contract and chain metadata and sets env metadata. | ||
| func WriteMetadataToDatastore(ds MutableDataStore, bundle MetadataBundle) error { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of extending the interface of |
||
| for _, ref := range bundle.Addresses { | ||
| if err := ds.Addresses().Add(ref); err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not |
||
| return fmt.Errorf("failed to add %s %v at %s on chain %d to datastore: %w", | ||
| ref.Type, ref.Version, ref.Address, ref.ChainSelector, err) | ||
| } | ||
| } | ||
|
graham-chainlink marked this conversation as resolved.
|
||
| for _, contract := range bundle.Contracts { | ||
| if err := ds.ContractMetadata().Upsert(contract); err != nil { | ||
| return fmt.Errorf("failed to upsert contract metadata for %s on chain %d to datastore: %w", | ||
| contract.Address, contract.ChainSelector, err) | ||
| } | ||
| } | ||
| if bundle.Chain != nil { | ||
| if err := ds.ChainMetadata().Upsert(*bundle.Chain); err != nil { | ||
| return fmt.Errorf("failed to upsert chain metadata for chain %d to datastore: %w", | ||
| bundle.Chain.ChainSelector, err) | ||
| } | ||
| } | ||
| if bundle.Env != nil { | ||
| if err := ds.EnvMetadata().Set(*bundle.Env); err != nil { | ||
| return fmt.Errorf("failed to set env metadata to datastore: %w", err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| package datastore | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/Masterminds/semver/v3" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestWriteMetadataToDatastore(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| contractOne := ContractMetadata{ | ||
| Address: "0xaaa", | ||
| ChainSelector: 1, | ||
| Metadata: testMetadata{Field: "contract-one", ChainSelector: 1}, | ||
| } | ||
| contractTwo := ContractMetadata{ | ||
| Address: "0xbbb", | ||
| ChainSelector: 2, | ||
| Metadata: testMetadata{Field: "contract-two", ChainSelector: 2}, | ||
| } | ||
| chainMD := ChainMetadata{ | ||
| ChainSelector: 1, | ||
| Metadata: testMetadata{Field: "chain", ChainSelector: 1}, | ||
| } | ||
| envMD := EnvMetadata{ | ||
| Metadata: testMetadata{Field: "env", ChainSelector: 0}, | ||
| } | ||
|
|
||
| t.Run("via mutable interface", func(t *testing.T) { | ||
| t.Parallel() | ||
| var ds MutableDataStore = NewMemoryDataStore() | ||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{})) | ||
| }) | ||
|
|
||
| t.Run("writes addresses and contract metadata", func(t *testing.T) { | ||
| t.Parallel() | ||
| ds := NewMemoryDataStore() | ||
| ref := AddressRef{ | ||
| Address: "0xabc", | ||
| ChainSelector: 1, | ||
| Type: "Timelock", | ||
| Version: semver.MustParse("1.0.0"), | ||
| } | ||
| contractMD := ContractMetadata{ | ||
| Address: "0xabc", | ||
| ChainSelector: 1, | ||
| Metadata: testMetadata{Field: "contract", ChainSelector: 1}, | ||
| } | ||
|
|
||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{ | ||
| Addresses: []AddressRef{ref}, | ||
| Contracts: []ContractMetadata{contractMD}, | ||
| })) | ||
|
|
||
| refs, err := ds.Addresses().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, refs, 1) | ||
|
|
||
| contracts, err := ds.ContractMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, contracts, 1) | ||
|
|
||
| err = WriteMetadataToDatastore(ds, MetadataBundle{Addresses: []AddressRef{ref}}) | ||
| require.Error(t, err) | ||
| require.ErrorIs(t, err, ErrAddressRefExists) | ||
| }) | ||
|
|
||
| t.Run("memory datastore convenience method", func(t *testing.T) { | ||
| t.Parallel() | ||
| ds := NewMemoryDataStore() | ||
| require.NoError(t, ds.WriteMetadata(MetadataBundle{})) | ||
| }) | ||
|
|
||
| t.Run("empty bundle is a no-op", func(t *testing.T) { | ||
| t.Parallel() | ||
| ds := NewMemoryDataStore() | ||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{})) | ||
|
|
||
| contracts, err := ds.ContractMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Empty(t, contracts) | ||
|
|
||
| chains, err := ds.ChainMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Empty(t, chains) | ||
|
|
||
| _, err = ds.EnvMetadata().Get() | ||
| require.ErrorIs(t, err, ErrEnvMetadataNotSet) | ||
| }) | ||
|
|
||
| t.Run("writes contracts chain and env", func(t *testing.T) { | ||
| t.Parallel() | ||
| ds := NewMemoryDataStore() | ||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{ | ||
| Contracts: []ContractMetadata{contractOne, contractTwo}, | ||
| Chain: &chainMD, | ||
| Env: &envMD, | ||
| })) | ||
|
|
||
| contracts, err := ds.ContractMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, contracts, 2) | ||
|
|
||
| chains, err := ds.ChainMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, chains, 1) | ||
| require.Equal(t, uint64(1), chains[0].ChainSelector) | ||
|
|
||
| gotEnv, err := ds.EnvMetadata().Get() | ||
| require.NoError(t, err) | ||
| gotEnvMD, err := As[testMetadata](gotEnv.Metadata) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "env", gotEnvMD.Field) | ||
| }) | ||
|
|
||
| t.Run("upserts overwrite existing records", func(t *testing.T) { | ||
| t.Parallel() | ||
| ds := NewMemoryDataStore() | ||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{ | ||
| Contracts: []ContractMetadata{contractOne}, | ||
| Chain: &chainMD, | ||
| Env: &envMD, | ||
| })) | ||
|
|
||
| updatedContract := contractOne | ||
| updatedContract.Metadata = testMetadata{Field: "updated", ChainSelector: 1} | ||
| updatedChain := ChainMetadata{ | ||
| ChainSelector: 1, | ||
| Metadata: testMetadata{Field: "updated-chain", ChainSelector: 1}, | ||
| } | ||
| updatedEnv := EnvMetadata{ | ||
| Metadata: testMetadata{Field: "updated-env", ChainSelector: 0}, | ||
| } | ||
|
|
||
| require.NoError(t, WriteMetadataToDatastore(ds, MetadataBundle{ | ||
| Contracts: []ContractMetadata{updatedContract}, | ||
| Chain: &updatedChain, | ||
| Env: &updatedEnv, | ||
| })) | ||
|
|
||
| contracts, err := ds.ContractMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, contracts, 1) | ||
| gotContractMD, err := As[testMetadata](contracts[0].Metadata) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "updated", gotContractMD.Field) | ||
|
|
||
| chains, err := ds.ChainMetadata().Fetch() | ||
| require.NoError(t, err) | ||
| require.Len(t, chains, 1) | ||
| gotChainMD, err := As[testMetadata](chains[0].Metadata) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "updated-chain", gotChainMD.Field) | ||
|
|
||
| gotEnv, err := ds.EnvMetadata().Get() | ||
| require.NoError(t, err) | ||
| gotEnvMD, err := As[testMetadata](gotEnv.Metadata) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "updated-env", gotEnvMD.Field) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get why Chain isn't a slice here, but as we generalize this CCIP-specific logic into a broadly available API, could this become a limitation? It's technically possible to have multiple ChainMetadata records per domain/environment, and other users might need that.