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

chore: CNS-auto-sort-specs #1851

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/consumer_examples/full_consumer_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ endpoints:
- chain-id: ALFAJORES
api-interface: jsonrpc
network-address: 127.0.0.1:3347
- chain-id: ARB1
- chain-id: ARBITRUM
api-interface: jsonrpc
network-address: 127.0.0.1:3348
- chain-id: STRK
Expand All @@ -23,7 +23,7 @@ endpoints:
- chain-id: APT1
api-interface: rest
network-address: 127.0.0.1:3350
- chain-id: POLYGON1
- chain-id: POLYGON
api-interface: jsonrpc
network-address: 127.0.0.1:3351
- chain-id: OPTM
Expand Down
4 changes: 2 additions & 2 deletions config/provider_examples/all_endpoints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ endpoints:
node-urls:
- url: <enter-here>
- api-interface: jsonrpc
chain-id: ARB1
chain-id: ARBITRUM
network-address:
address: "127.0.0.1:2221"
node-urls:
Expand All @@ -48,7 +48,7 @@ endpoints:
node-urls:
- url: <enter-here>
- api-interface: jsonrpc
chain-id: POLYGON1
chain-id: POLYGON
network-address:
address: "127.0.0.1:2221"
node-urls:
Expand Down
2 changes: 1 addition & 1 deletion cookbook/projects/policy_all_chains_with_addon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Policy:
api_interface: "jsonrpc"
type: "POST"
add_on: "debug"
- chain_id: POLYGON1
- chain_id: POLYGON
requirements:
- collection:
api_interface: "jsonrpc"
Expand Down
4 changes: 2 additions & 2 deletions scripts/automation_scripts/updateSpecsCu.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
## CSV file expected structure:
Selector,Method name,Value,Multi10,spec,
Ethereum,eth_accounts,6,60,"ETH1 (ethereum mainnet), GTH1 (ethereum testnet goerli)",
Polygon,debug_getbadblocks,5,50,"POLYGON1 (polygon mainnet), POLYGON1T (polygon testnet)",
Arbitrum,eth_accounts,1,10,ARB1 (arbitrum mainnet),
Polygon,debug_getbadblocks,5,50,"POLYGON (polygon mainnet), POLYGONT (polygon testnet)",
Arbitrum,eth_accounts,1,10,ARBITRUM (arbitrum mainnet),

## Script Assumptions (or in what way you should feed it data so it'll work):
- spec file must be named with "spec_add" prefix
Expand Down
5 changes: 2 additions & 3 deletions scripts/init_chain_commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ screen -wipe
GASPRICE="0.00002ulava"

echo; echo "#### Sending proposal for specs ####"
specs=$(get_all_specs)
specs=$(get_all_spec_files)
lavad tx gov submit-legacy-proposal spec-add $specs --lava-dev-test -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE
cd ../../
echo; echo "#### Waiting 2 blocks ####"
wait_count_blocks 2

Expand Down Expand Up @@ -71,7 +70,7 @@ lavad tx subscription buy DefaultPlan $(lavad keys show user1 -a) --enable-auto-
# lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_extension.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE

# MANTLE
CHAINS="ETH1,SEP1,HOL1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARB1,ARBN,APT1,STRK,JUN1,COSMOSHUB,POLYGON1,EVMOS,OPTM,BASES,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH,AGR,AGRT,KOIIT,AVAXT,CELESTIATM"
CHAINS="ETH1,SEP1,HOL1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARBITRUM,ARBITRUMN,APT1,STRK,JUN1,COSMOSHUB,POLYGON,EVMOS,OPTM,BASES,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH,AGR,AGRT,KOIIT,AVAXT,CELESTIATM"
BASE_CHAINS="ETH1,LAV1"
# stake providers on all chains
echo; echo "#### Staking provider 1 ####"
Expand Down
6 changes: 3 additions & 3 deletions scripts/pre_setups/init_arbitrum_only_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ PROVIDER1_LISTENER="127.0.0.1:2221"

lavad tx subscription buy DefaultPlan $(lavad keys show user1 -a) -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE
wait_next_block
lavad tx pairing stake-provider "ARB1" $PROVIDERSTAKE "$PROVIDER1_LISTENER,1" 1 $(operator_address) -y --from servicer1 --provider-moniker "dummyMoniker" --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE
lavad tx pairing stake-provider "ARBITRUM" $PROVIDERSTAKE "$PROVIDER1_LISTENER,1" 1 $(operator_address) -y --from servicer1 --provider-moniker "dummyMoniker" --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE

sleep_until_next_epoch

screen -d -m -S provider1 bash -c "source ~/.bashrc; lavap rpcprovider \
$PROVIDER1_LISTENER ARB1 jsonrpc '$ARB1_HTTP' \
$PROVIDER1_LISTENER ARBITRUM jsonrpc '$ARBITRUM_HTTP' \
$EXTRA_PROVIDER_FLAGS --geolocation 1 --log_level debug --from servicer1 2>&1 | tee $LOGS_DIR/PROVIDER1.log" && sleep 0.25

screen -d -m -S consumers bash -c "source ~/.bashrc; lavap rpcconsumer \
127.0.0.1:3348 ARB1 jsonrpc \
127.0.0.1:3348 ARBITRUM jsonrpc \
$EXTRA_PORTAL_FLAGS --geolocation 1 --log_level debug --from user1 --allow-insecure-provider-dialing 2>&1 | tee $LOGS_DIR/CONSUMERS.log" && sleep 0.25

echo "--- setting up screens done ---"
Expand Down
4 changes: 2 additions & 2 deletions scripts/setup_providers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ $PROVIDER1_LISTENER HOL1 jsonrpc '$HOL_RPC_WS' \
$PROVIDER1_LISTENER FTM250 jsonrpc '$FTM_RPC_HTTP' \
$PROVIDER1_LISTENER CELO jsonrpc '$CELO_HTTP' \
$PROVIDER1_LISTENER ALFAJORES jsonrpc '$CELO_ALFAJORES_HTTP' \
$PROVIDER1_LISTENER ARB1 jsonrpc '$ARB1_HTTP' \
$PROVIDER1_LISTENER ARBITRUM jsonrpc '$ARBITRUM_HTTP' \
$PROVIDER1_LISTENER APT1 rest '$APTOS_REST' \
$PROVIDER1_LISTENER STRK jsonrpc '$STARKNET_RPC' \
$PROVIDER1_LISTENER POLYGON1 jsonrpc '$POLYGON_MAINNET_RPC' \
$PROVIDER1_LISTENER POLYGON jsonrpc '$POLYGON_MAINNET_RPC' \
$PROVIDER1_LISTENER OPTM jsonrpc '$OPTIMISM_RPC' \
$PROVIDER1_LISTENER BASE jsonrpc '$BASE_RPC' \
$PROVIDER1_LISTENER BSC jsonrpc '$BSC_RPC' \
Expand Down
2 changes: 1 addition & 1 deletion scripts/test/cli_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ wait_count_blocks 1 >/dev/null
(trace lavad tx pairing stake-provider ETH1 $PROVIDERSTAKE "$PROVIDER1_LISTENER,1" 1 $(operator_address) --provider-moniker "provider" $txoptions)>/dev/null
wait_count_blocks 1 >/dev/null

CHAINS="SEP1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARB1,ARBN,APT1,STRK,JUN1,COSMOSHUB,POLYGON1,EVMOS,OPTM,BASET,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR"
CHAINS="SEP1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARBITRUM,ARBITRUMN,APT1,STRK,JUN1,COSMOSHUB,POLYGON,EVMOS,OPTM,BASET,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR"
(trace lavad tx pairing bulk-stake-provider $CHAINS $PROVIDERSTAKE "$PROVIDER1_LISTENER,1" 1 $(operator_address) --provider-moniker "provider" $txoptions)>/dev/null

sleep_until_next_epoch >/dev/null
Expand Down
2 changes: 1 addition & 1 deletion scripts/test/inich_100_providers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ lavad tx subscription buy DefaultPlan "$(lavad keys show user1 -a)" --enable-aut
# lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_addon.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE

# MANTLE
CHAINS="ETH1,SEP1,HOL1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARB1,ARBN,APT1,STRK,JUN1,COSMOSHUB,POLYGON1,EVMOS,OPTM,BASES,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH,AGR,AGRT,KOIIT,AVAXT,CELESTIATM"
CHAINS="ETH1,SEP1,HOL1,OSMOSIS,FTM250,CELO,LAV1,OSMOSIST,ALFAJORES,ARBITRUM,ARBITRUMN,APT1,STRK,JUN1,COSMOSHUB,POLYGON,EVMOS,OPTM,BASES,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH,AGR,AGRT,KOIIT,AVAXT,CELESTIATM"
BASE_CHAINS="ETH1,LAV1"
# stake providers on all chains
echo; echo "#### Staking provider 1 ####"
Expand Down
2 changes: 1 addition & 1 deletion scripts/test/test_consumer.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash

lavad test rpcconsumer http://127.0.0.1:3333 ETH1 jsonrpc http://127.0.0.1:3360 LAV1 rest http://127.0.0.1:3361 LAV1 tendermintrpc 127.0.0.1:3362 LAV1 grpc http://127.0.0.1:3348 ARB1 jsonrpc http://127.0.0.1:3350 APT1 rest --chain-id lava
lavad test rpcconsumer http://127.0.0.1:3333 ETH1 jsonrpc http://127.0.0.1:3360 LAV1 rest http://127.0.0.1:3361 LAV1 tendermintrpc 127.0.0.1:3362 LAV1 grpc http://127.0.0.1:3348 ARBITRUM jsonrpc http://127.0.0.1:3350 APT1 rest --chain-id lava
8 changes: 7 additions & 1 deletion scripts/useful_commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ validate_env() {
# Array of variables to check
required_vars=(
ETH_RPC_WS SEP_RPC_WS HOL_RPC_WS FTM_RPC_HTTP CELO_HTTP
CELO_ALFAJORES_HTTP ARB1_HTTP APTOS_REST STARKNET_RPC POLYGON_MAINNET_RPC
CELO_ALFAJORES_HTTP ARBITRUM_HTTP APTOS_REST STARKNET_RPC POLYGON_MAINNET_RPC
OPTIMISM_RPC BASE_RPC BSC_RPC SOLANA_RPC SUI_RPC OSMO_REST OSMO_RPC OSMO_GRPC
LAVA_REST LAVA_RPC LAVA_RPC_WS LAVA_GRPC GAIA_REST GAIA_RPC GAIA_GRPC JUNO_REST
JUNO_RPC JUNO_GRPC EVMOS_RPC EVMOS_TENDERMINTRPC EVMOS_REST EVMOS_GRPC CANTO_RPC
Expand Down Expand Up @@ -231,3 +231,9 @@ get_all_specs() {
# Combine arrays and output as a comma-separated string
(IFS=,; echo "${priority_specs[*]},${other_specs[*]}")
}

get_all_spec_files() {
# Find all json files and join them with commas
find "$(pwd)/specs/mainnet-1/specs" "$(pwd)/specs/testnet-2/specs" -name "*.json" 2>/dev/null | paste -sd "," -
}

6 changes: 6 additions & 0 deletions x/spec/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ func handleSpecProposal(ctx sdk.Context, k keeper.Keeper, p *types.SpecAddPropos

var events []event

// Sort specs by hierarchy to ensure dependencies are processed first
sortedSpecs, err := types.SortSpecsByHierarchy(p.Specs)
if err != nil {
return utils.LavaFormatWarning("failed to sort specs", err)
}
p.Specs = sortedSpecs
for _, spec := range p.Specs {
_, found := k.GetSpec(ctx, spec.Index)

Expand Down
122 changes: 122 additions & 0 deletions x/spec/types/sort_spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package types_test

import (
"testing"

"github.com/lavanet/lava/v4/x/spec/types"
"github.com/stretchr/testify/require"
)

func TestSortSpecsByHierarchy(t *testing.T) {
tests := []struct {
name string
specs []types.Spec
expectedOrder []string
expectError bool
}{
{
name: "empty specs",
specs: []types.Spec{},
expectedOrder: []string{},
expectError: false,
},
{
name: "single spec no imports",
specs: []types.Spec{
{Index: "spec1", Imports: []string{}},
},
expectedOrder: []string{"spec1"},
expectError: false,
},
{
name: "linear dependency",
specs: []types.Spec{
{Index: "spec2", Imports: []string{"spec1"}},
{Index: "spec1", Imports: []string{}},
{Index: "spec3", Imports: []string{"spec2"}},
},
expectedOrder: []string{"spec1", "spec2", "spec3"},
expectError: false,
},
{
name: "multiple independent specs",
specs: []types.Spec{
{Index: "spec1", Imports: []string{}},
{Index: "spec2", Imports: []string{}},
{Index: "spec3", Imports: []string{}},
},
expectedOrder: []string{"spec1", "spec2", "spec3"},
expectError: false,
},
{
name: "complex dependencies",
specs: []types.Spec{
{Index: "spec3", Imports: []string{"spec1", "spec2"}},
{Index: "spec2", Imports: []string{"spec1"}},
{Index: "spec1", Imports: []string{}},
{Index: "spec4", Imports: []string{"spec2", "spec3"}},
},
expectedOrder: []string{"spec1", "spec2", "spec3", "spec4"},
expectError: false,
},
{
name: "circular dependency",
specs: []types.Spec{
{Index: "spec1", Imports: []string{"spec2"}},
{Index: "spec2", Imports: []string{"spec1"}},
},
expectedOrder: nil,
expectError: true,
},
{
name: "self dependency",
specs: []types.Spec{
{Index: "spec1", Imports: []string{"spec1"}},
},
expectedOrder: nil,
expectError: true,
},
{
name: "complex circular dependency",
specs: []types.Spec{
{Index: "spec1", Imports: []string{"spec3"}},
{Index: "spec2", Imports: []string{"spec1"}},
{Index: "spec3", Imports: []string{"spec2"}},
},
expectedOrder: nil,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sorted, err := types.SortSpecsByHierarchy(tt.specs)

if tt.expectError {
require.Error(t, err)
require.Nil(t, sorted)
} else {
require.NoError(t, err)
require.NotNil(t, sorted)
require.Equal(t, len(tt.expectedOrder), len(sorted))

// Check if the order matches expected
for i, expectedIndex := range tt.expectedOrder {
require.Equal(t, expectedIndex, sorted[i].Index)
}

// Verify that dependencies are satisfied
processed := make(map[string]bool)
for _, spec := range sorted {
// Check that all imports are already processed
for _, imp := range spec.Imports {
require.True(t, processed[imp],
"Spec %s depends on %s which hasn't been processed yet",
spec.Index, imp)
}
processed[spec.Index] = true
}
}
})
}
}
57 changes: 57 additions & 0 deletions x/spec/types/sort_specs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package types

import (
fmt "fmt"
)

// sortSpecsByHierarchy sorts specs based on their import dependencies
// Returns a sorted list of specs where specs with no imports come first,
// followed by specs that import from already processed specs
func SortSpecsByHierarchy(specs []Spec) ([]Spec, error) {
// Create a list to store sorted specs
sorted := make([]Spec, 0, len(specs))

// Create a set to track processed specs
processed := make(map[string]bool)
// Create a set to track all specs
specsExists := make(map[string]bool)
for _, spec := range specs {
specsExists[spec.Index] = true
}

// Helper function to check if all imports are processed
canProcess := func(imports []string) bool {
for _, imp := range imports {
if !processed[imp] && specsExists[imp] {
return false
}
}
return true
}

// Keep processing until all specs are sorted
for len(sorted) < len(specs) {
progress := false

for _, spec := range specs {
// Skip if already processed
if processed[spec.Index] {
continue
}

// If spec has no imports or all its imports are processed
if len(spec.Imports) == 0 || canProcess(spec.Imports) {
sorted = append(sorted, spec)
processed[spec.Index] = true
progress = true
}
}

// If no progress was made in this iteration, we have a circular dependency
if !progress {
return nil, fmt.Errorf("circular dependency detected in specs imports")
}
}

return sorted, nil
}
Loading