diff --git a/docker-compose.yml b/docker-compose.yml index 403eb57d..dea7310b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,7 +100,6 @@ services: }, 'models': { 'external': { - 'defaultCluster': '{remote_cluster}', 'defaultDatabase': 'default' }, 'transformations': { diff --git a/example.env b/example.env index ee309baf..a46a3e1a 100644 --- a/example.env +++ b/example.env @@ -1,5 +1,6 @@ # Network configuration NETWORK=mainnet +EXTERNAL_MODEL_CLUSTER={remote_cluster} # ClickHouse xatu configuration CLICKHOUSE_HOST=localhost diff --git a/internal/config/config.go b/internal/config/config.go index 5c70c518..da2caaf6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,6 +18,7 @@ type Config struct { ClickhouseUsername string ClickhousePassword string ClickhouseCluster string + ExternalModelCluster string } // Load reads configuration from environment variables and .env file @@ -31,11 +32,12 @@ func Load() (*Config, error) { } cfg := &Config{ - Network: getEnv("NETWORK", "mainnet"), - ClickhouseHost: getEnv("CLICKHOUSE_HOST", "localhost"), - ClickhouseUsername: getEnv("CLICKHOUSE_USERNAME", "default"), - ClickhousePassword: getEnv("CLICKHOUSE_PASSWORD", ""), - ClickhouseCluster: getEnv("CLICKHOUSE_CLUSTER", ""), + Network: getEnv("NETWORK", "mainnet"), + ClickhouseHost: getEnv("CLICKHOUSE_HOST", "localhost"), + ClickhouseUsername: getEnv("CLICKHOUSE_USERNAME", "default"), + ClickhousePassword: getEnv("CLICKHOUSE_PASSWORD", ""), + ClickhouseCluster: getEnv("CLICKHOUSE_CLUSTER", ""), + ExternalModelCluster: getEnv("EXTERNAL_MODEL_CLUSTER", ""), } // Parse numeric values @@ -73,19 +75,26 @@ func (c *Config) String() string { clusterDisplay = "(single-node)" } + externalClusterDisplay := c.ExternalModelCluster + if externalClusterDisplay == "" { + externalClusterDisplay = "(not set)" + } + return fmt.Sprintf(`Current Configuration: ====================== -Network: %s -ClickHouse Host: %s -ClickHouse Native Port: %d -ClickHouse Username: %s -ClickHouse Password: %s -ClickHouse Cluster: %s`, +Network: %s +ClickHouse Host: %s +ClickHouse Native Port: %d +ClickHouse Username: %s +ClickHouse Password: %s +ClickHouse Cluster: %s +External Model Cluster: %s`, c.Network, c.ClickhouseHost, c.ClickhouseNativePort, c.ClickhouseUsername, passwordDisplay, clusterDisplay, + externalClusterDisplay, ) } diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index f1e1397f..77993057 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -29,7 +29,7 @@ func PrepareAndRun(cfg *config.Config) error { migrationsDir := "migrations" // Copy and process migration files - if procErr := processMigrationFiles(migrationsDir, tempDir, cfg.Network); procErr != nil { + if procErr := processMigrationFiles(migrationsDir, tempDir, cfg.Network, cfg.ExternalModelCluster); procErr != nil { return fmt.Errorf("failed to process migration files: %w", procErr) } @@ -72,8 +72,8 @@ func PrepareAndRun(cfg *config.Config) error { return nil } -// processMigrationFiles copies migration files from source to dest, substituting ${NETWORK_NAME} -func processMigrationFiles(sourceDir, destDir, network string) error { +// processMigrationFiles copies migration files from source to dest, substituting ${NETWORK_NAME} and ${EXTERNAL_MODEL_CLUSTER} +func processMigrationFiles(sourceDir, destDir, network, externalCluster string) error { // Read all files from source directory entries, err := os.ReadDir(sourceDir) if err != nil { @@ -102,6 +102,8 @@ func processMigrationFiles(sourceDir, destDir, network string) error { // Replace ${NETWORK_NAME} with actual network processedContent := strings.ReplaceAll(string(content), "${NETWORK_NAME}", network) + // Replace ${EXTERNAL_MODEL_CLUSTER} with actual external cluster + processedContent = strings.ReplaceAll(processedContent, "${EXTERNAL_MODEL_CLUSTER}", externalCluster) // Write processed content to destination if err := os.WriteFile(destPath, []byte(processedContent), 0o600); err != nil { @@ -151,7 +153,7 @@ func GetMigrationStatus(cfg *config.Config) (version uint, dirty bool, err error }() // Copy migration files (needed for migrate instance) - if procErr := processMigrationFiles("migrations", tempDir, cfg.Network); procErr != nil { + if procErr := processMigrationFiles("migrations", tempDir, cfg.Network, cfg.ExternalModelCluster); procErr != nil { return 0, false, fmt.Errorf("failed to process migration files: %w", procErr) } @@ -184,11 +186,11 @@ func GetMigrationStatus(cfg *config.Config) (version uint, dirty bool, err error // CopyMigrationsToDirectory copies and processes migration files to a specific directory // This is useful for debugging or manual inspection -func CopyMigrationsToDirectory(sourceDir, destDir, network string) error { +func CopyMigrationsToDirectory(sourceDir, destDir, network, externalCluster string) error { // Create destination directory if it doesn't exist if err := os.MkdirAll(destDir, 0o750); err != nil { return fmt.Errorf("failed to create destination directory: %w", err) } - return processMigrationFiles(sourceDir, destDir, network) + return processMigrationFiles(sourceDir, destDir, network, externalCluster) } diff --git a/migrations/002_database.up.sql b/migrations/002_database.up.sql index 2a9fda6c..1b1a046d 100644 --- a/migrations/002_database.up.sql +++ b/migrations/002_database.up.sql @@ -1 +1,357 @@ CREATE DATABASE IF NOT EXISTS `${NETWORK_NAME}` ON CLUSTER '{cluster}'; + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_beacon_committee AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_beacon_committee'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_attestation AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_attestation'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_blob_sidecar AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_blob_sidecar'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_block_gossip AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_block_gossip'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_block'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_chain_reorg AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_chain_reorg'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_contribution_and_proof AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_contribution_and_proof'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_data_column_sidecar AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_data_column_sidecar'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_finalized_checkpoint AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_finalized_checkpoint'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_head AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_head'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_events_voluntary_exit AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_events_voluntary_exit'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_proposer_duty AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_proposer_duty'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v1_validator_attestation_data AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v1_validator_attestation_data'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v2_beacon_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v2_beacon_block'); + +CREATE VIEW ${NETWORK_NAME}.beacon_api_eth_v3_validator_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_api_eth_v3_validator_block'); + +CREATE VIEW ${NETWORK_NAME}.beacon_block_classification AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'beacon_block_classification'); + +CREATE VIEW ${NETWORK_NAME}.block_native_mempool_transaction AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'block_native_mempool_transaction'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_blob_sidecar AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_blob_sidecar'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_attester_slashing AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_attester_slashing'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_bls_to_execution_change AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_bls_to_execution_change'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_deposit AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_deposit'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_execution_transaction AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_execution_transaction'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_proposer_slashing AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_proposer_slashing'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_voluntary_exit AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_voluntary_exit'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_block_withdrawal AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_block_withdrawal'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_committee AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_committee'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_elaborated_attestation AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_elaborated_attestation'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_proposer_duty AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_proposer_duty'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_validators AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_validators'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_validators_pubkeys AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_validators_pubkeys'); + +CREATE VIEW ${NETWORK_NAME}.canonical_beacon_validators_withdrawal_credentials AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_beacon_validators_withdrawal_credentials'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_address_appearances AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_address_appearances'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_balance_diffs AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_balance_diffs'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_balance_reads AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_balance_reads'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_block'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_contracts AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_contracts'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_erc20_transfers AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_erc20_transfers'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_erc721_transfers AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_erc721_transfers'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_four_byte_counts AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_four_byte_counts'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_logs AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_logs'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_native_transfers AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_native_transfers'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_nonce_diffs AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_nonce_diffs'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_nonce_reads AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_nonce_reads'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_storage_diffs AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_storage_diffs'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_storage_reads AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_storage_reads'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_traces AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_traces'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_transaction AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_transaction'); + +CREATE VIEW ${NETWORK_NAME}.canonical_execution_transaction_structlog AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'canonical_execution_transaction_structlog'); + +CREATE VIEW ${NETWORK_NAME}.ethseer_validator_entity AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'ethseer_validator_entity'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_add_peer AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_add_peer'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_connected AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_connected'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_deliver_message AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_deliver_message'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_disconnected AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_disconnected'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_drop_rpc AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_drop_rpc'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_duplicate_message AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_duplicate_message'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_gossipsub_aggregate_and_proof AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_gossipsub_aggregate_and_proof'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_gossipsub_beacon_attestation AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_gossipsub_beacon_attestation'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_gossipsub_beacon_block AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_gossipsub_beacon_block'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_gossipsub_blob_sidecar AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_gossipsub_blob_sidecar'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_gossipsub_data_column_sidecar AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_gossipsub_data_column_sidecar'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_graft AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_graft'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_handle_metadata AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_handle_metadata'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_handle_status AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_handle_status'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_join AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_join'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_leave AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_leave'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_peer AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_peer'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_prune AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_prune'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_publish_message AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_publish_message'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_recv_rpc AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_recv_rpc'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_reject_message AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_reject_message'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_remove_peer AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_remove_peer'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_data_column_custody_probe AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_data_column_custody_probe'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_control_graft AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_control_graft'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_control_idontwant AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_control_idontwant'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_control_ihave AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_control_ihave'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_control_iwant AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_control_iwant'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_control_prune AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_control_prune'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_message AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_message'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_rpc_meta_subscription AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_rpc_meta_subscription'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_send_rpc AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_send_rpc'); + +CREATE VIEW ${NETWORK_NAME}.libp2p_synthetic_heartbeat AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'libp2p_synthetic_heartbeat'); + +CREATE VIEW ${NETWORK_NAME}.mempool_dumpster_transaction AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'mempool_dumpster_transaction'); + +CREATE VIEW ${NETWORK_NAME}.mempool_transaction AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'mempool_transaction'); + +CREATE VIEW ${NETWORK_NAME}.mev_relay_bid_trace AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'mev_relay_bid_trace'); + +CREATE VIEW ${NETWORK_NAME}.mev_relay_proposer_payload_delivered AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'mev_relay_proposer_payload_delivered'); + +CREATE VIEW ${NETWORK_NAME}.mev_relay_validator_registration AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'mev_relay_validator_registration'); + +CREATE VIEW ${NETWORK_NAME}.node_record_consensus AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'node_record_consensus'); + +CREATE VIEW ${NETWORK_NAME}.node_record_execution AS +SELECT * +FROM cluster('${EXTERNAL_MODEL_CLUSTER}', '${NETWORK_NAME}', 'node_record_execution');