diff --git a/core/taskengine/engine.go b/core/taskengine/engine.go index 329fcb5a..186272cb 100644 --- a/core/taskengine/engine.go +++ b/core/taskengine/engine.go @@ -31,6 +31,7 @@ import ( "google.golang.org/grpc/status" grpcstatus "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" avsproto "github.com/AvaProtocol/EigenLayer-AVS/protobuf" "github.com/ethereum/go-ethereum/core/types" @@ -200,6 +201,7 @@ func New(db storage.Storage, config *config.Config, queue *apqueue.Queue, logger // Initialize TokenEnrichmentService // Always try to initialize, even without RPC, so we can serve whitelist data + logger.Debug("initializing TokenEnrichmentService", "has_rpc", rpcConn != nil) tokenService, err := NewTokenEnrichmentService(rpcConn, logger) if err != nil { logger.Warn("Failed to initialize TokenEnrichmentService", "error", err) @@ -252,16 +254,23 @@ func (n *Engine) MustStart() error { if e != nil { panic(e) } + + loadedCount := 0 for _, item := range kvs { task := &model.Task{ Task: &avsproto.Task{}, } err := protojson.Unmarshal(item.Value, task) if err == nil { - n.tasks[string(item.Key)] = task + n.tasks[task.Id] = task + loadedCount++ + } else { + n.logger.Warn("Failed to unmarshal task during startup", "storage_key", string(item.Key), "error", err) } } + n.logger.Info("🚀 Engine started successfully", "active_tasks_loaded", loadedCount) + // Start the batch notification processor go n.processBatchedNotifications() @@ -941,12 +950,45 @@ func (n *Engine) AggregateChecksResultWithState(address string, payload *avsprot // Get task information to determine execution state task, exists := n.tasks[payload.TaskId] if !exists { - return &ExecutionState{ - RemainingExecutions: 0, - TaskStillActive: false, - Status: "not_found", - Message: "Task not found", - }, fmt.Errorf("task %s not found", payload.TaskId) + // Task not found in memory - try database lookup + dbTask, dbErr := n.GetTaskByID(payload.TaskId) + if dbErr != nil { + // Task not found in database either - this is likely a stale operator notification + // Log at DEBUG level to reduce noise in production + n.logger.Debug("Operator notified about non-existent task (likely stale data)", + "task_id", payload.TaskId, + "operator", address, + "memory_task_count", len(n.tasks)) + + // Clean up stale task tracking for this operator + n.lock.Lock() + if state, exists := n.trackSyncedTasks[address]; exists { + delete(state.TaskID, payload.TaskId) + n.logger.Debug("Cleaned up stale task from operator tracking", + "task_id", payload.TaskId, + "operator", address) + } + n.lock.Unlock() + + return &ExecutionState{ + RemainingExecutions: 0, + TaskStillActive: false, + Status: "not_found", + Message: "Task no longer exists - operator should stop monitoring", + }, nil // Return nil error to avoid spam + } + + // Task found in database but not in memory - add it to memory and continue + n.lock.Lock() + n.tasks[dbTask.Id] = dbTask + n.lock.Unlock() + task = dbTask + + n.logger.Info("Task recovered from database and added to memory", + "task_id", payload.TaskId, + "operator", address, + "task_status", task.Status, + "memory_task_count_after", len(n.tasks)) } // Check if task is still runnable @@ -988,27 +1030,48 @@ func (n *Engine) AggregateChecksResultWithState(address string, payload *avsprot } // Enrich EventTrigger output if TokenEnrichmentService is available and it's a Transfer event - if payload.TriggerType == avsproto.TriggerType_TRIGGER_TYPE_EVENT && n.tokenEnrichmentService != nil { - if eventOutput := triggerData.Output.(*avsproto.EventTrigger_Output); eventOutput != nil { - if evmLog := eventOutput.GetEvmLog(); evmLog != nil { - n.logger.Debug("enriching EventTrigger output from operator", - "task_id", payload.TaskId, - "tx_hash", evmLog.TransactionHash, - "block_number", evmLog.BlockNumber) - - // Fetch full event data from the blockchain using the minimal data from operator - if enrichedEventOutput, err := n.enrichEventTriggerFromOperatorData(evmLog); err == nil { - // Replace the minimal event output with the enriched one - triggerData.Output = enrichedEventOutput - n.logger.Debug("successfully enriched EventTrigger output", + if payload.TriggerType == avsproto.TriggerType_TRIGGER_TYPE_EVENT { + n.logger.Debug("processing event trigger", + "task_id", payload.TaskId, + "has_token_service", n.tokenEnrichmentService != nil) + + if n.tokenEnrichmentService != nil { + if eventOutput := triggerData.Output.(*avsproto.EventTrigger_Output); eventOutput != nil { + if evmLog := eventOutput.GetEvmLog(); evmLog != nil { + n.logger.Debug("enriching EventTrigger output from operator", "task_id", payload.TaskId, - "has_transfer_log", enrichedEventOutput.GetTransferLog() != nil) + "tx_hash", evmLog.TransactionHash, + "block_number", evmLog.BlockNumber, + "log_index", evmLog.Index, + "address", evmLog.Address, + "topics_count", len(evmLog.Topics), + "data_length", len(evmLog.Data)) + + // Fetch full event data from the blockchain using the minimal data from operator + if enrichedEventOutput, err := n.enrichEventTriggerFromOperatorData(evmLog); err == nil { + // Replace the minimal event output with the enriched one + triggerData.Output = enrichedEventOutput + n.logger.Debug("successfully enriched EventTrigger output", + "task_id", payload.TaskId, + "has_transfer_log", enrichedEventOutput.GetTransferLog() != nil, + "has_evm_log", enrichedEventOutput.GetEvmLog() != nil) + } else { + n.logger.Warn("failed to enrich EventTrigger output, using minimal data", + "task_id", payload.TaskId, + "error", err) + } } else { - n.logger.Warn("failed to enrich EventTrigger output, using minimal data", + n.logger.Debug("EventTrigger output has no EvmLog data", "task_id", payload.TaskId, - "error", err) + "has_transfer_log", eventOutput.GetTransferLog() != nil) } + } else { + n.logger.Debug("EventTrigger output is nil", + "task_id", payload.TaskId) } + } else { + n.logger.Debug("TokenEnrichmentService not available for event enrichment", + "task_id", payload.TaskId) } } @@ -1018,6 +1081,26 @@ func (n *Engine) AggregateChecksResultWithState(address string, payload *avsprot ExecutionID: ulid.Make().String(), } + // For event triggers, if we have enriched data, convert it to a map format that survives JSON serialization + if triggerData.Type == avsproto.TriggerType_TRIGGER_TYPE_EVENT { + if eventOutput, ok := triggerData.Output.(*avsproto.EventTrigger_Output); ok { + // Convert the enriched protobuf data to a map that will survive JSON serialization + enrichedDataMap := buildTriggerDataMapFromProtobuf(triggerData.Type, eventOutput, n.logger) + + // Store the enriched data as a map instead of protobuf structure + // This ensures the enriched data survives JSON serialization/deserialization + queueTaskData.TriggerOutput = map[string]interface{}{ + "enriched_data": enrichedDataMap, + "trigger_type": triggerData.Type.String(), + } + + n.logger.Debug("stored enriched event trigger data for queue execution", + "task_id", payload.TaskId, + "has_token_symbol", enrichedDataMap["tokenSymbol"] != nil, + "has_value_formatted", enrichedDataMap["valueFormatted"] != nil) + } + } + data, err := json.Marshal(queueTaskData) if err != nil { n.logger.Error("error serialize trigger to json", err) @@ -1443,8 +1526,10 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n vm.WithLogger(n.logger).WithDb(n.db) // Add input variables to VM for template processing - for key, value := range inputVariables { - vm.AddVar(key, value) + // Apply dual-access mapping to enable both camelCase and snake_case field access + processedInputVariables := ProcessInputVariablesWithDualAccess(inputVariables) + for key, processedValue := range processedInputVariables { + vm.AddVar(key, processedValue) } // Step 6: Add trigger data as "trigger" variable for convenient access in JavaScript @@ -1454,6 +1539,30 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n // Add the trigger variable with the actual trigger name for JavaScript access vm.AddVar(sanitizeTriggerNameForJS(trigger.GetName()), map[string]any{"data": triggerDataMap}) + // Step 6.5: Extract and add trigger input data if available (REPLICATE EXACT LOGIC FROM RunTask) + triggerInputData := ExtractTriggerInputData(task.Trigger) + // Store the extracted trigger input data for reuse in Step 8 (trigger step creation) + extractedTriggerInputForStep := triggerInputData + if triggerInputData != nil { + // Get the trigger variable name and add input data (EXACT SAME LOGIC AS RunTask) + triggerVarName := sanitizeTriggerNameForJS(task.Trigger.GetName()) + vm.mu.Lock() + existingTriggerVar := vm.vars[triggerVarName] + if existingMap, ok := existingTriggerVar.(map[string]any); ok { + // Apply dual-access mapping to trigger input data + processedTriggerInput := CreateDualAccessMap(triggerInputData) + existingMap["input"] = processedTriggerInput + vm.vars[triggerVarName] = existingMap + } else { + // Create new trigger variable with input data + processedTriggerInput := CreateDualAccessMap(triggerInputData) + vm.vars[triggerVarName] = map[string]any{ + "input": processedTriggerInput, + } + } + vm.mu.Unlock() + } + // Step 7: Compile the workflow if err = vm.Compile(); err != nil { return nil, fmt.Errorf("failed to compile workflow for simulation: %w", err) @@ -1466,6 +1575,35 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n triggerInputs = append(triggerInputs, key) } + // Use the trigger input data extracted in Step 6.5 to avoid calling ExtractTriggerInputData twice + var triggerInputProto *structpb.Value + if extractedTriggerInputForStep != nil { + n.logger.Info("✅ SimulateTask: Successfully extracted trigger input data", "trigger_id", task.Trigger.Id, "input_data", extractedTriggerInputForStep) + triggerInputProto, err = structpb.NewValue(extractedTriggerInputForStep) + if err != nil { + n.logger.Warn("Failed to convert trigger input data to protobuf", "error", err) + // Try a fallback approach: convert to JSON and back to ensure proper formatting + jsonBytes, jsonErr := json.Marshal(extractedTriggerInputForStep) + if jsonErr == nil { + var cleanData interface{} + if unmarshalErr := json.Unmarshal(jsonBytes, &cleanData); unmarshalErr == nil { + triggerInputProto, err = structpb.NewValue(cleanData) + if err == nil { + n.logger.Info("✅ Successfully converted trigger input data using JSON fallback") + } + } + } + } + } else { + n.logger.Warn("❌ SimulateTask: No trigger input data found", "trigger_id", task.Trigger.Id, "trigger_type", task.Trigger.GetType()) + // Debug: Check if trigger has input field at all + if task.Trigger.GetInput() != nil { + n.logger.Info("🔍 SimulateTask: Trigger has input field", "trigger_id", task.Trigger.Id, "input_field_present", true) + } else { + n.logger.Info("🔍 SimulateTask: Trigger has no input field", "trigger_id", task.Trigger.Id, "input_field_present", false) + } + } + triggerStep := &avsproto.Execution_Step{ Id: task.Trigger.Id, // Use new 'id' field Success: true, @@ -1476,6 +1614,7 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n Inputs: triggerInputs, // Use inputVariables keys as trigger inputs Type: queueData.TriggerType.String(), // Use trigger type as string Name: task.Trigger.Name, // Use new 'name' field + Input: triggerInputProto, // Include extracted trigger input data for debugging } // Set trigger output data in the step using shared function @@ -1484,11 +1623,6 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n // Add trigger step to execution logs vm.ExecutionLogs = append(vm.ExecutionLogs, triggerStep) - // Step 8.5: Update VM trigger variable with actual execution results - // This ensures subsequent nodes can access the trigger's actual output via eventTrigger.data - actualTriggerDataMap := buildTriggerDataMapFromProtobuf(queueData.TriggerType, triggerOutputProto) - vm.AddVar(sanitizeTriggerNameForJS(trigger.GetName()), map[string]any{"data": actualTriggerDataMap}) - // Step 9: Run the workflow nodes runErr := vm.Run() nodeEndTime := time.Now() @@ -1775,14 +1909,14 @@ func (n *Engine) DeleteTaskByUser(user *model.User, taskID string) (bool, error) task, err := n.GetTask(user, taskID) if err != nil { - n.logger.Info("❌ Task not found for deletion", "task_id", taskID, "error", err) + n.logger.Warn("❌ Task not found for deletion", "task_id", taskID, "error", err) return false, grpcstatus.Errorf(codes.NotFound, TaskNotFoundError) } n.logger.Info("✅ Retrieved task for deletion", "task_id", taskID, "status", task.Status) if task.Status == avsproto.TaskStatus_Executing { - n.logger.Info("❌ Cannot delete executing task", "task_id", taskID, "status", task.Status) + n.logger.Warn("❌ Cannot delete executing task", "task_id", taskID, "status", task.Status) return false, fmt.Errorf("Only non executing task can be deleted") } @@ -2406,21 +2540,35 @@ func getStringMapKeys(m map[string]interface{}) []string { // enrichEventTriggerFromOperatorData fetches full event data from blockchain and enriches it with token metadata func (n *Engine) enrichEventTriggerFromOperatorData(minimalEvmLog *avsproto.Evm_Log) (*avsproto.EventTrigger_Output, error) { if minimalEvmLog.TransactionHash == "" { + n.logger.Debug("enrichment failed: transaction hash is empty") return nil, fmt.Errorf("transaction hash is required for enrichment") } // Get RPC client (using the global rpcConn variable) if rpcConn == nil { + n.logger.Debug("enrichment failed: RPC client not available") return nil, fmt.Errorf("RPC client not available") } + n.logger.Debug("starting event enrichment", + "tx_hash", minimalEvmLog.TransactionHash, + "log_index", minimalEvmLog.Index, + "block_number", minimalEvmLog.BlockNumber) + // Fetch transaction receipt to get the full event logs ctx := context.Background() receipt, err := rpcConn.TransactionReceipt(ctx, common.HexToHash(minimalEvmLog.TransactionHash)) if err != nil { + n.logger.Debug("enrichment failed: could not fetch transaction receipt", + "tx_hash", minimalEvmLog.TransactionHash, + "error", err) return nil, fmt.Errorf("failed to fetch transaction receipt: %w", err) } + n.logger.Debug("fetched transaction receipt", + "tx_hash", minimalEvmLog.TransactionHash, + "logs_count", len(receipt.Logs)) + // Find the specific log that matches the operator's data var targetLog *types.Log for _, log := range receipt.Logs { @@ -2431,10 +2579,27 @@ func (n *Engine) enrichEventTriggerFromOperatorData(minimalEvmLog *avsproto.Evm_ } if targetLog == nil { + n.logger.Debug("enrichment failed: log not found in transaction", + "tx_hash", minimalEvmLog.TransactionHash, + "expected_log_index", minimalEvmLog.Index, + "available_log_indices", func() []uint32 { + indices := make([]uint32, len(receipt.Logs)) + for i, log := range receipt.Logs { + indices[i] = uint32(log.Index) + } + return indices + }()) return nil, fmt.Errorf("log with index %d not found in transaction %s", minimalEvmLog.Index, minimalEvmLog.TransactionHash) } + n.logger.Debug("found target log", + "tx_hash", minimalEvmLog.TransactionHash, + "log_index", targetLog.Index, + "address", targetLog.Address.Hex(), + "topics_count", len(targetLog.Topics), + "data_length", len(targetLog.Data)) + // Create enriched EVM log with full data enrichedEvmLog := &avsproto.Evm_Log{ Address: targetLog.Address.Hex(), @@ -2459,12 +2624,26 @@ func (n *Engine) enrichEventTriggerFromOperatorData(minimalEvmLog *avsproto.Evm_ isTransferEvent := len(targetLog.Topics) > 0 && targetLog.Topics[0].Hex() == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + n.logger.Debug("checking if transfer event", + "is_transfer", isTransferEvent, + "topics_count", len(targetLog.Topics), + "first_topic", func() string { + if len(targetLog.Topics) > 0 { + return targetLog.Topics[0].Hex() + } + return "none" + }()) + if isTransferEvent && len(targetLog.Topics) >= 3 { + n.logger.Debug("processing as transfer event") + // Get block timestamp for transfer_log header, err := rpcConn.HeaderByNumber(ctx, big.NewInt(int64(targetLog.BlockNumber))) var blockTimestamp uint64 if err == nil { blockTimestamp = header.Time * 1000 // Convert to milliseconds + } else { + n.logger.Debug("could not fetch block header for timestamp", "error", err) } // Extract from and to addresses from topics @@ -2488,10 +2667,22 @@ func (n *Engine) enrichEventTriggerFromOperatorData(minimalEvmLog *avsproto.Evm_ LogIndex: uint32(targetLog.Index), } + n.logger.Debug("created transfer log", + "from", fromAddr, + "to", toAddr, + "value", value, + "token_address", targetLog.Address.Hex()) + // Enrich with token metadata if err := n.tokenEnrichmentService.EnrichTransferLog(enrichedEvmLog, transferLog); err != nil { n.logger.Warn("failed to enrich transfer log with token metadata", "error", err) // Continue without enrichment - partial data is better than no data + } else { + n.logger.Debug("successfully enriched transfer log", + "token_name", transferLog.TokenName, + "token_symbol", transferLog.TokenSymbol, + "token_decimals", transferLog.TokenDecimals, + "value_formatted", transferLog.ValueFormatted) } // Use the oneof TransferLog field @@ -2499,12 +2690,17 @@ func (n *Engine) enrichEventTriggerFromOperatorData(minimalEvmLog *avsproto.Evm_ TransferLog: transferLog, } } else { + n.logger.Debug("processing as regular EVM log event") // Regular event (not a transfer) - use the oneof EvmLog field enrichedOutput.OutputType = &avsproto.EventTrigger_Output_EvmLog{ EvmLog: enrichedEvmLog, } } + n.logger.Debug("enrichment completed successfully", + "has_transfer_log", enrichedOutput.GetTransferLog() != nil, + "has_evm_log", enrichedOutput.GetEvmLog() != nil) + return enrichedOutput, nil } @@ -2936,9 +3132,17 @@ func buildTriggerDataMap(triggerType avsproto.TriggerType, triggerOutput map[str triggerDataMap["gasUsed"] = gasUsed } case avsproto.TriggerType_TRIGGER_TYPE_EVENT: - // Copy all event trigger data - for k, v := range triggerOutput { - triggerDataMap[k] = v + // Handle event trigger data with special processing for transfer_log + if transferLogData, hasTransferLog := triggerOutput["transfer_log"].(map[string]interface{}); hasTransferLog { + // Flatten transfer_log data to top level for JavaScript access + for k, v := range transferLogData { + triggerDataMap[k] = v + } + } else { + // For non-transfer events, copy all event trigger data + for k, v := range triggerOutput { + triggerDataMap[k] = v + } } default: // For unknown trigger types, copy all output data @@ -3035,13 +3239,14 @@ func buildExecutionStepOutputData(triggerType avsproto.TriggerType, triggerOutpu // Parameters: // - triggerType: the type of trigger being processed // - triggerOutputProto: the protobuf trigger output structure (e.g., *avsproto.BlockTrigger_Output) +// - logger: optional logger for debugging (can be nil) // // Returns: // - map[string]interface{}: JavaScript-accessible trigger data map // // This function differs from buildTriggerDataMap in that it works with structured protobuf data // rather than raw trigger output maps, making it suitable for VM initialization. -func buildTriggerDataMapFromProtobuf(triggerType avsproto.TriggerType, triggerOutputProto interface{}) map[string]interface{} { +func buildTriggerDataMapFromProtobuf(triggerType avsproto.TriggerType, triggerOutputProto interface{}, logger sdklogging.Logger) map[string]interface{} { triggerDataMap := make(map[string]interface{}) if triggerOutputProto == nil { @@ -3082,30 +3287,51 @@ func buildTriggerDataMapFromProtobuf(triggerType avsproto.TriggerType, triggerOu // Check if we have transfer log data in the event output if transferLogData := eventOutput.GetTransferLog(); transferLogData != nil { // Use transfer log data to populate rich trigger data matching runTrigger format - triggerDataMap["token_name"] = transferLogData.TokenName - triggerDataMap["token_symbol"] = transferLogData.TokenSymbol - triggerDataMap["token_decimals"] = transferLogData.TokenDecimals - triggerDataMap["transaction_hash"] = transferLogData.TransactionHash + // Use camelCase field names for JavaScript compatibility + triggerDataMap["tokenName"] = transferLogData.TokenName + triggerDataMap["tokenSymbol"] = transferLogData.TokenSymbol + triggerDataMap["tokenDecimals"] = transferLogData.TokenDecimals + triggerDataMap["transactionHash"] = transferLogData.TransactionHash triggerDataMap["address"] = transferLogData.Address - triggerDataMap["block_number"] = transferLogData.BlockNumber - triggerDataMap["block_timestamp"] = transferLogData.BlockTimestamp - triggerDataMap["from_address"] = transferLogData.FromAddress - triggerDataMap["to_address"] = transferLogData.ToAddress + triggerDataMap["blockNumber"] = transferLogData.BlockNumber + triggerDataMap["blockTimestamp"] = transferLogData.BlockTimestamp + triggerDataMap["fromAddress"] = transferLogData.FromAddress + triggerDataMap["toAddress"] = transferLogData.ToAddress triggerDataMap["value"] = transferLogData.Value - triggerDataMap["value_formatted"] = transferLogData.ValueFormatted - triggerDataMap["transaction_index"] = transferLogData.TransactionIndex - triggerDataMap["log_index"] = transferLogData.LogIndex - } else if evmLog := eventOutput.GetEvmLog(); evmLog != nil { - // Fall back to basic EVM log data matching runTrigger format - triggerDataMap["block_number"] = evmLog.BlockNumber - triggerDataMap["log_index"] = evmLog.Index - triggerDataMap["tx_hash"] = evmLog.TransactionHash - triggerDataMap["address"] = evmLog.Address - triggerDataMap["topics"] = evmLog.Topics - triggerDataMap["data"] = evmLog.Data - triggerDataMap["block_hash"] = evmLog.BlockHash - triggerDataMap["transaction_index"] = evmLog.TransactionIndex - triggerDataMap["removed"] = evmLog.Removed + triggerDataMap["valueFormatted"] = transferLogData.ValueFormatted + triggerDataMap["transactionIndex"] = transferLogData.TransactionIndex + triggerDataMap["logIndex"] = transferLogData.LogIndex + } else if evmLogData := eventOutput.GetEvmLog(); evmLogData != nil { + // Use EVM log data for regular events + triggerDataMap["address"] = evmLogData.Address + triggerDataMap["topics"] = evmLogData.Topics + triggerDataMap["data"] = evmLogData.Data + triggerDataMap["blockNumber"] = evmLogData.BlockNumber + triggerDataMap["transactionHash"] = evmLogData.TransactionHash + triggerDataMap["transactionIndex"] = evmLogData.TransactionIndex + triggerDataMap["blockHash"] = evmLogData.BlockHash + triggerDataMap["logIndex"] = evmLogData.Index + triggerDataMap["removed"] = evmLogData.Removed + } + } else if enrichedDataMap, ok := triggerOutputProto.(map[string]interface{}); ok { + // Handle the new enriched data format that survives JSON serialization + if enrichedData, hasEnrichedData := enrichedDataMap["enriched_data"].(map[string]interface{}); hasEnrichedData { + // Copy all enriched data to the trigger data map + for k, v := range enrichedData { + triggerDataMap[k] = v + } + if logger != nil { + logger.Debug("loaded enriched event trigger data from queue", + "has_token_symbol", triggerDataMap["tokenSymbol"] != nil, + "has_value_formatted", triggerDataMap["valueFormatted"] != nil) + } + } else { + // Fallback: copy all data from the map + for k, v := range enrichedDataMap { + if k != "trigger_type" { // Skip metadata + triggerDataMap[k] = v + } + } } } default: diff --git a/core/taskengine/engine_trigger_output_test.go b/core/taskengine/engine_trigger_output_test.go index 7d0a332a..030ed399 100644 --- a/core/taskengine/engine_trigger_output_test.go +++ b/core/taskengine/engine_trigger_output_test.go @@ -239,26 +239,26 @@ func TestBuildTriggerDataMapFromProtobufEventTriggerComprehensive(t *testing.T) description: "Should map all TransferLog fields including the critical log_index field", verifyFunc: func(t *testing.T, result map[string]interface{}) { expected := map[string]interface{}{ - "token_name": "Test Token", - "token_symbol": "TEST", - "token_decimals": uint32(18), - "transaction_hash": "0x1234567890abcdef", - "address": "0xabcdef1234567890", - "block_number": uint64(12345678), - "block_timestamp": uint64(1672531200), - "from_address": "0x1111111111111111", - "to_address": "0x2222222222222222", - "value": "1000000000000000000", - "value_formatted": "1.0", - "transaction_index": uint32(5), - "log_index": uint32(3), - "type": "TRIGGER_TYPE_EVENT", + "tokenName": "Test Token", + "tokenSymbol": "TEST", + "tokenDecimals": uint32(18), + "transactionHash": "0x1234567890abcdef", + "address": "0xabcdef1234567890", + "blockNumber": uint64(12345678), + "blockTimestamp": uint64(1672531200), + "fromAddress": "0x1111111111111111", + "toAddress": "0x2222222222222222", + "value": "1000000000000000000", + "valueFormatted": "1.0", + "transactionIndex": uint32(5), + "logIndex": uint32(3), + "type": "TRIGGER_TYPE_EVENT", } require.Equal(t, expected, result, "All TransferLog fields should be properly mapped") - require.Contains(t, result, "log_index", "log_index field should be present") - require.Equal(t, uint32(3), result["log_index"], "log_index should have correct value") + require.Contains(t, result, "logIndex", "logIndex field should be present") + require.Equal(t, uint32(3), result["logIndex"], "logIndex should have correct value") }, }, { @@ -281,29 +281,29 @@ func TestBuildTriggerDataMapFromProtobufEventTriggerComprehensive(t *testing.T) description: "Should map all EvmLog fields including log_index", verifyFunc: func(t *testing.T, result map[string]interface{}) { expected := map[string]interface{}{ - "block_number": uint64(12345678), - "log_index": uint32(3), - "tx_hash": "0x1234567890abcdef", - "address": "0xabcdef1234567890", - "topics": []string{"0xtopic1", "0xtopic2", "0xtopic3"}, - "data": "0xdeadbeef", - "block_hash": "0xblockhash123456", - "transaction_index": uint32(5), - "removed": false, - "type": "TRIGGER_TYPE_EVENT", + "blockNumber": uint64(12345678), + "logIndex": uint32(3), + "transactionHash": "0x1234567890abcdef", + "address": "0xabcdef1234567890", + "topics": []string{"0xtopic1", "0xtopic2", "0xtopic3"}, + "data": "0xdeadbeef", + "blockHash": "0xblockhash123456", + "transactionIndex": uint32(5), + "removed": false, + "type": "TRIGGER_TYPE_EVENT", } require.Equal(t, expected, result, "All EvmLog fields should be properly mapped") - require.Contains(t, result, "log_index", "log_index field should be present") - require.Equal(t, uint32(3), result["log_index"], "log_index should have correct value") + require.Contains(t, result, "logIndex", "logIndex field should be present") + require.Equal(t, uint32(3), result["logIndex"], "logIndex should have correct value") }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result := buildTriggerDataMapFromProtobuf(avsproto.TriggerType_TRIGGER_TYPE_EVENT, test.input) + result := buildTriggerDataMapFromProtobuf(avsproto.TriggerType_TRIGGER_TYPE_EVENT, test.input, nil) require.NotNil(t, result, "buildTriggerDataMapFromProtobuf should never return nil") @@ -325,7 +325,7 @@ func TestBuildTriggerDataMapFromProtobufFieldCompleteness(t *testing.T) { } for _, triggerType := range triggerTypes { - result := buildTriggerDataMapFromProtobuf(triggerType, nil) + result := buildTriggerDataMapFromProtobuf(triggerType, nil, nil) require.Contains(t, result, "type", "All trigger types should include type field") require.Equal(t, triggerType.String(), result["type"], "Type field should match trigger type") } diff --git a/core/taskengine/executor.go b/core/taskengine/executor.go index a1174c60..583f6332 100644 --- a/core/taskengine/executor.go +++ b/core/taskengine/executor.go @@ -7,6 +7,7 @@ import ( "time" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" "github.com/AvaProtocol/EigenLayer-AVS/model" avsproto "github.com/AvaProtocol/EigenLayer-AVS/protobuf" @@ -160,6 +161,28 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData) vm.WithLogger(x.logger).WithDb(x.db) initialTaskStatus := task.Status + // Extract and add trigger input data if available + triggerInputData := ExtractTriggerInputData(task.Trigger) + if triggerInputData != nil { + // Get the trigger variable name and add input data + triggerVarName := sanitizeTriggerNameForJS(task.Trigger.GetName()) + vm.mu.Lock() + existingTriggerVar := vm.vars[triggerVarName] + if existingMap, ok := existingTriggerVar.(map[string]any); ok { + // Apply dual-access mapping to trigger input data + processedTriggerInput := ProcessInputVariableWithDualAccess(triggerInputData) + existingMap["input"] = processedTriggerInput + vm.vars[triggerVarName] = existingMap + } else { + // Create new trigger variable with input data + processedTriggerInput := ProcessInputVariableWithDualAccess(triggerInputData) + vm.vars[triggerVarName] = map[string]any{ + "input": processedTriggerInput, + } + } + vm.mu.Unlock() + } + if err != nil { return nil, fmt.Errorf("vm failed to initialize: %w", err) } @@ -189,6 +212,16 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData) // This ensures regular workflows have complete execution history (trigger + nodes) // Create trigger step similar to SimulateTask + // Extract trigger input data using the proper extraction function + triggerInputData := ExtractTriggerInputData(task.Trigger) + var triggerInputProto *structpb.Value + if triggerInputData != nil { + triggerInputProto, err = structpb.NewValue(triggerInputData) + if err != nil { + x.logger.Warn("Failed to convert trigger input data to protobuf", "error", err) + } + } + triggerStep := &avsproto.Execution_Step{ Id: task.Trigger.Id, Success: true, @@ -199,6 +232,7 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData) Inputs: []string{}, // Empty inputs for trigger steps Type: queueData.TriggerType.String(), Name: task.Trigger.Name, + Input: triggerInputProto, // Include extracted trigger input data for debugging } // Set trigger output data in the step based on trigger type @@ -307,7 +341,7 @@ func (x *TaskExecutor) RunTask(task *model.Task, queueData *QueueExecutionData) if executionSuccess { x.logger.Info("successfully executing task", "task_id", task.Id, "triggermark", queueData) } else { - x.logger.Info("task execution completed with step failures", "task_id", task.Id, "failed_steps", failedStepCount) + x.logger.Warn("task execution completed with step failures", "task_id", task.Id, "failed_steps", failedStepCount) } return execution, nil diff --git a/core/taskengine/executor_test.go b/core/taskengine/executor_test.go index 11c14404..849fd022 100644 --- a/core/taskengine/executor_test.go +++ b/core/taskengine/executor_test.go @@ -39,7 +39,7 @@ func TestExecutorRunTaskSucess(t *testing.T) { Id: "a1", Type: "if", // The test data is of this transaction https://sepolia.etherscan.io/tx/0x53beb2163994510e0984b436ebc828dc57e480ee671cfbe7ed52776c2a4830c8 which is 3.45 token - Expression: "Number(triggertest.data.value_formatted) >= 3", + Expression: "Number(triggertest.data.valueFormatted) >= 3", }, }, }, @@ -390,7 +390,7 @@ func TestExecutorRunTaskReturnAllExecutionData(t *testing.T) { { Id: "condition1", Type: "if", - Expression: "Number(triggertest.data.value_formatted) >= 3", + Expression: "Number(triggertest.data.valueFormatted) >= 3", }, }, }, diff --git a/core/taskengine/run_node_immediately.go b/core/taskengine/run_node_immediately.go index f4b586ae..67d8f5fd 100644 --- a/core/taskengine/run_node_immediately.go +++ b/core/taskengine/run_node_immediately.go @@ -720,8 +720,10 @@ func (n *Engine) runProcessingNodeWithInputs(nodeType string, nodeConfig map[str vm.WithLogger(n.logger).WithDb(n.db) // Add input variables to VM for template processing and node access - for key, value := range inputVariables { - vm.AddVar(key, value) + // Apply dual-access mapping to enable both camelCase and snake_case field access + processedInputVariables := ProcessInputVariablesWithDualAccess(inputVariables) + for key, processedValue := range processedInputVariables { + vm.AddVar(key, processedValue) } // Create node from type and config @@ -730,8 +732,8 @@ func (n *Engine) runProcessingNodeWithInputs(nodeType string, nodeConfig map[str return nil, fmt.Errorf("failed to create node: %w", err) } - // Execute the node - executionStep, err := vm.RunNodeWithInputs(node, inputVariables) + // Execute the node with processed input variables + executionStep, err := vm.RunNodeWithInputs(node, processedInputVariables) if err != nil { return nil, fmt.Errorf("node execution failed: %w", err) } @@ -1071,8 +1073,7 @@ func (n *Engine) RunNodeImmediatelyRPC(user *model.User, req *avsproto.RunNodeWi // Log successful execution if n.logger != nil { - n.logger.Info("RunNodeImmediatelyRPC: Executed successfully", "nodeTypeStr", nodeTypeStr, "originalNodeType", req.NodeType, "configKeys", getStringMapKeys(nodeConfig), "inputKeys", getStringMapKeys(inputVariables)) - + n.logger.Info("RunNodeImmediatelyRPC: Executed successfully", "nodeTypeStr", nodeTypeStr, "originalNodeType", req.NodeType) } // Convert result to the appropriate protobuf output type @@ -1500,6 +1501,12 @@ func (n *Engine) RunTriggerRPC(user *model.User, req *avsproto.RunTriggerReq) (* triggerConfig[k] = v.AsInterface() } + // Extract trigger input data from the request + triggerInput := make(map[string]interface{}) + for k, v := range req.TriggerInput { + triggerInput[k] = v.AsInterface() + } + // Convert TriggerType enum to string triggerTypeStr := TriggerTypeToString(req.TriggerType) if triggerTypeStr == "" { @@ -1516,8 +1523,8 @@ func (n *Engine) RunTriggerRPC(user *model.User, req *avsproto.RunTriggerReq) (* return resp, nil } - // Execute the trigger immediately (triggers don't accept input variables) - result, err := n.runTriggerImmediately(triggerTypeStr, triggerConfig, nil) + // Execute the trigger immediately with trigger input data + result, err := n.runTriggerImmediately(triggerTypeStr, triggerConfig, triggerInput) if err != nil { if n.logger != nil { // Categorize errors to avoid unnecessary stack traces for expected validation errors @@ -1572,7 +1579,7 @@ func (n *Engine) RunTriggerRPC(user *model.User, req *avsproto.RunTriggerReq) (* // Log successful execution if n.logger != nil { - n.logger.Info("RunTriggerRPC: Executed successfully", "triggerTypeStr", triggerTypeStr, "originalTriggerType", req.TriggerType, "configKeys", getStringMapKeys(triggerConfig)) + n.logger.Info("RunTriggerRPC: Executed successfully", "triggerTypeStr", triggerTypeStr, "originalTriggerType", req.TriggerType) } // Convert result to the appropriate protobuf output type diff --git a/core/taskengine/smart_variable_resolution_test.go b/core/taskengine/smart_variable_resolution_test.go index 6156410e..4dff09c2 100644 --- a/core/taskengine/smart_variable_resolution_test.go +++ b/core/taskengine/smart_variable_resolution_test.go @@ -288,6 +288,222 @@ func TestSmartVariableResolution(t *testing.T) { }) } +// TestDualAccessVariableSupport tests that both camelCase and snake_case field access +// work for direct JavaScript variable access (not just template variables). +// +// This test verifies that the createDualAccessMap function properly enables: +// 1. Direct JS destructuring: const {tokenSymbol, token_symbol} = eventTrigger.data +// 2. Direct JS property access: eventTrigger.data.tokenSymbol AND eventTrigger.data.token_symbol +// 3. Template variables: {{eventTrigger.data.tokenSymbol}} AND {{eventTrigger.data.token_symbol}} +// +// This solves the original issue where deployed tasks returned NaN/undefined because +// JavaScript code expected camelCase but data had snake_case (or vice versa). +func TestDualAccessVariableSupport(t *testing.T) { + engine := createTestEngineForSmartResolution(t) + + t.Run("DirectJavaScriptVariableAccess", func(t *testing.T) { + // Test that both camelCase and snake_case work in direct JavaScript code + // This simulates the user's original issue with custom code destructuring + + config := map[string]interface{}{ + "lang": "JavaScript", + "source": ` + // Test direct property access (both naming conventions should work) + const tokenSymbolCamel = eventTrigger.data.tokenSymbol; + const tokenSymbolSnake = eventTrigger.data.token_symbol; + const valueFormattedCamel = eventTrigger.data.valueFormatted; + const valueFormattedSnake = eventTrigger.data.value_formatted; + + // Test destructuring (both naming conventions should work) + const {tokenSymbol, token_symbol, valueFormatted, value_formatted} = eventTrigger.data; + + // Return results to verify both work + return { + tokenSymbolCamel, + tokenSymbolSnake, + valueFormattedCamel, + valueFormattedSnake, + destructuredTokenSymbol: tokenSymbol, + destructuredTokenSymbolSnake: token_symbol, + destructuredValueFormatted: valueFormatted, + destructuredValueFormattedSnake: value_formatted + }; + `, + } + + // Simulate trigger data with camelCase field names (as it comes from buildTriggerDataMapFromProtobuf) + inputVariables := map[string]interface{}{ + "eventTrigger": map[string]interface{}{ + "data": map[string]interface{}{ + "tokenSymbol": "USDC", + "valueFormatted": "3.45", + "blockNumber": 12345678, + "fromAddress": "0x1111111111111111", + "toAddress": "0x2222222222222222", + }, + }, + } + + result, err := engine.RunNodeImmediately(NodeTypeCustomCode, config, inputVariables) + + assert.NoError(t, err) + assert.NotNil(t, result) + + // Verify that both camelCase and snake_case access work + assert.Equal(t, "USDC", result["tokenSymbolCamel"]) + assert.Equal(t, "USDC", result["tokenSymbolSnake"]) + assert.Equal(t, "3.45", result["valueFormattedCamel"]) + assert.Equal(t, "3.45", result["valueFormattedSnake"]) + + // Verify destructuring works for both naming conventions + assert.Equal(t, "USDC", result["destructuredTokenSymbol"]) + assert.Equal(t, "USDC", result["destructuredTokenSymbolSnake"]) + assert.Equal(t, "3.45", result["destructuredValueFormatted"]) + assert.Equal(t, "3.45", result["destructuredValueFormattedSnake"]) + }) + + t.Run("NodeOutputDualAccess", func(t *testing.T) { + // Test that node outputs also support dual access + // This ensures the SetOutputVarForStep dual-access mapping works + + // First, create a REST API node that returns data with camelCase fields + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + // Return camelCase field names + fmt.Fprintf(w, `{ + "responseData": "test_response", + "statusCode": 200, + "apiKey": "secret_key_123" + }`) + })) + defer mockServer.Close() + + restConfig := map[string]interface{}{ + "url": mockServer.URL, + "method": "GET", + } + + // Execute REST API node to get output data + restResult, err := engine.RunNodeImmediately(NodeTypeRestAPI, restConfig, map[string]interface{}{}) + assert.NoError(t, err) + assert.NotNil(t, restResult) + + // Now test a custom code node that accesses the REST API output using both naming conventions + customCodeConfig := map[string]interface{}{ + "lang": "JavaScript", + "source": ` + // Test accessing REST API output data using both camelCase and snake_case + const responseDataCamel = apiNode.data.responseData; + const responseDataSnake = apiNode.data.response_data; + const statusCodeCamel = apiNode.data.statusCode; + const statusCodeSnake = apiNode.data.status_code; + const apiKeyCamel = apiNode.data.apiKey; + const apiKeySnake = apiNode.data.api_key; + + return { + responseDataCamel, + responseDataSnake, + statusCodeCamel, + statusCodeSnake, + apiKeyCamel, + apiKeySnake + }; + `, + } + + // Simulate the REST API node output being available to the custom code node + inputVariables := map[string]interface{}{ + "apiNode": map[string]interface{}{ + "data": restResult, // This should have dual-access mapping applied + }, + } + + customResult, err := engine.RunNodeImmediately(NodeTypeCustomCode, customCodeConfig, inputVariables) + + assert.NoError(t, err) + assert.NotNil(t, customResult) + + // Verify that both camelCase and snake_case access work for node outputs + // Note: The actual values depend on the REST API response structure + // We're mainly testing that both naming conventions resolve to the same values + if customResult["responseDataCamel"] != nil { + assert.Equal(t, customResult["responseDataCamel"], customResult["responseDataSnake"]) + } + if customResult["statusCodeCamel"] != nil { + assert.Equal(t, customResult["statusCodeCamel"], customResult["statusCodeSnake"]) + } + if customResult["apiKeyCamel"] != nil { + assert.Equal(t, customResult["apiKeyCamel"], customResult["apiKeySnake"]) + } + }) +} + +// TestDualAccessDebug is a debug test to understand what's happening with dual-access +func TestDualAccessDebug(t *testing.T) { + engine := createTestEngineForSmartResolution(t) + + t.Run("DebugVariableAccess", func(t *testing.T) { + config := map[string]interface{}{ + "lang": "JavaScript", + "source": ` + // Check if eventTrigger exists and return debug info + return { + debug: "test", + eventTriggerExists: typeof eventTrigger !== 'undefined', + eventTriggerData: typeof eventTrigger !== 'undefined' ? eventTrigger.data : null, + eventTriggerType: typeof eventTrigger, + eventTriggerKeys: typeof eventTrigger !== 'undefined' ? Object.keys(eventTrigger) : [], + eventTriggerDataKeys: typeof eventTrigger !== 'undefined' && eventTrigger.data ? Object.keys(eventTrigger.data) : [], + globalKeys: Object.keys(this), + // Try to access specific fields + tokenSymbolDirect: typeof eventTrigger !== 'undefined' && eventTrigger.data ? eventTrigger.data.tokenSymbol : "not_found", + tokenSymbolSnake: typeof eventTrigger !== 'undefined' && eventTrigger.data ? eventTrigger.data.token_symbol : "not_found" + }; + `, + } + + inputVariables := map[string]interface{}{ + "eventTrigger": map[string]interface{}{ + "data": map[string]interface{}{ + "tokenSymbol": "USDC", + "valueFormatted": "3.45", + }, + }, + } + + result, err := engine.RunNodeImmediately(NodeTypeCustomCode, config, inputVariables) + + assert.NoError(t, err) + assert.NotNil(t, result) + + // Print the result for debugging + t.Logf("Debug result: %+v", result) + }) +} + +// TestCreateDualAccessMap tests the CreateDualAccessMap function directly +func TestCreateDualAccessMap(t *testing.T) { + // Test with camelCase input + input := map[string]interface{}{ + "tokenSymbol": "USDC", + "valueFormatted": "3.45", + "blockNumber": 12345, + } + + result := CreateDualAccessMap(input) + + // Should have both camelCase and snake_case versions + assert.Equal(t, "USDC", result["tokenSymbol"]) + assert.Equal(t, "USDC", result["token_symbol"]) + assert.Equal(t, "3.45", result["valueFormatted"]) + assert.Equal(t, "3.45", result["value_formatted"]) + assert.Equal(t, 12345, result["blockNumber"]) + assert.Equal(t, 12345, result["block_number"]) + + t.Logf("CreateDualAccessMap result: %+v", result) +} + // createTestEngineForSmartResolution creates an engine for testing func createTestEngineForSmartResolution(t *testing.T) *Engine { logger := testutil.GetLogger() diff --git a/core/taskengine/trigger/event.go b/core/taskengine/trigger/event.go index 548f79e7..c173e3ba 100644 --- a/core/taskengine/trigger/event.go +++ b/core/taskengine/trigger/event.go @@ -768,7 +768,7 @@ func (t *EventTrigger) buildFilterQueries() []QueryInfo { for taskID, count := range taskQueryCounts { if count > 1 { - t.logger.Warn("⚠️ Task has multiple unique queries - may receive duplicate events", + t.logger.Info("⚠️ Task has multiple unique queries - may receive duplicate events", "task_id", taskID, "unique_query_count", count, "recommendation", "Consider combining queries to reduce duplicates") diff --git a/core/taskengine/trigger_data_flattening_test.go b/core/taskengine/trigger_data_flattening_test.go new file mode 100644 index 00000000..fd61d28a --- /dev/null +++ b/core/taskengine/trigger_data_flattening_test.go @@ -0,0 +1,417 @@ +package taskengine + +import ( + "testing" + + "github.com/AvaProtocol/EigenLayer-AVS/core/testutil" + avsproto "github.com/AvaProtocol/EigenLayer-AVS/protobuf" + "github.com/AvaProtocol/EigenLayer-AVS/storage" + "github.com/stretchr/testify/assert" +) + +// TestBuildTriggerDataMapEventTriggerFlattening tests the specific fix for flattening transfer_log data +// This test verifies that the buildTriggerDataMap function correctly flattens nested transfer_log data +// to the top level, which resolves the NaN and undefined values issue in simulateTask. +func TestBuildTriggerDataMapEventTriggerFlattening(t *testing.T) { + // Test data with nested transfer_log structure (as it comes from runEventTriggerImmediately) + triggerOutput := map[string]interface{}{ + "found": true, + "queriesCount": 2, + "totalSearched": 5000, + "totalEvents": 1, + "transfer_log": map[string]interface{}{ + "tokenName": "USDC", + "tokenSymbol": "USDC", + "tokenDecimals": uint32(6), + "transactionHash": "0x1b0b9bee55e3a824dedd1dcfaad1790e19e0a68d6717e385a960092077f8b6a1", + "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "blockNumber": uint64(8560047), + "blockTimestamp": uint64(1750061412000), + "fromAddress": "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", + "toAddress": "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", + "value": "0x00000000000000000000000000000000000000000000000000000000004c4b40", + "valueFormatted": "5", + "transactionIndex": uint32(63), + "logIndex": uint32(83), + }, + } + + // Test buildTriggerDataMap with event trigger + result := buildTriggerDataMap(avsproto.TriggerType_TRIGGER_TYPE_EVENT, triggerOutput) + + // Verify that transfer_log data is flattened to top level + assert.Equal(t, "USDC", result["tokenName"], "tokenName should be at top level") + assert.Equal(t, "USDC", result["tokenSymbol"], "tokenSymbol should be at top level") + assert.Equal(t, uint32(6), result["tokenDecimals"], "tokenDecimals should be at top level") + assert.Equal(t, "0x1b0b9bee55e3a824dedd1dcfaad1790e19e0a68d6717e385a960092077f8b6a1", result["transactionHash"], "transactionHash should be at top level") + assert.Equal(t, "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", result["address"], "address should be at top level") + assert.Equal(t, uint64(8560047), result["blockNumber"], "blockNumber should be at top level") + assert.Equal(t, uint64(1750061412000), result["blockTimestamp"], "blockTimestamp should be at top level") + assert.Equal(t, "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", result["fromAddress"], "fromAddress should be at top level") + assert.Equal(t, "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", result["toAddress"], "toAddress should be at top level") + assert.Equal(t, "0x00000000000000000000000000000000000000000000000000000000004c4b40", result["value"], "value should be at top level") + assert.Equal(t, "5", result["valueFormatted"], "valueFormatted should be at top level") + assert.Equal(t, uint32(63), result["transactionIndex"], "transactionIndex should be at top level") + assert.Equal(t, uint32(83), result["logIndex"], "logIndex should be at top level") + + // Verify that the nested transfer_log object is NOT present at top level + assert.NotContains(t, result, "transfer_log", "transfer_log should not be present as nested object") + + // Test with non-transfer event (should copy all data as-is) + nonTransferOutput := map[string]interface{}{ + "found": true, + "someField": "someValue", + "anotherField": 123, + } + + nonTransferResult := buildTriggerDataMap(avsproto.TriggerType_TRIGGER_TYPE_EVENT, nonTransferOutput) + assert.Equal(t, true, nonTransferResult["found"]) + assert.Equal(t, "someValue", nonTransferResult["someField"]) + assert.Equal(t, 123, nonTransferResult["anotherField"]) +} + +// TestBuildTriggerDataMapFromProtobufConsistency tests that both buildTriggerDataMap and +// buildTriggerDataMapFromProtobuf produce consistent field names for JavaScript access. +func TestBuildTriggerDataMapFromProtobufConsistency(t *testing.T) { + // Create protobuf transfer log data + transferLogProto := &avsproto.EventTrigger_TransferLogOutput{ + TokenName: "USDC", + TokenSymbol: "USDC", + TokenDecimals: 6, + TransactionHash: "0x1b0b9bee55e3a824dedd1dcfaad1790e19e0a68d6717e385a960092077f8b6a1", + Address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + BlockNumber: 8560047, + BlockTimestamp: 1750061412000, + FromAddress: "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", + ToAddress: "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", + Value: "0x00000000000000000000000000000000000000000000000000000000004c4b40", + ValueFormatted: "5", + TransactionIndex: 63, + LogIndex: 83, + } + + eventOutputProto := &avsproto.EventTrigger_Output{ + OutputType: &avsproto.EventTrigger_Output_TransferLog{ + TransferLog: transferLogProto, + }, + } + + // Test buildTriggerDataMapFromProtobuf + protobufResult := buildTriggerDataMapFromProtobuf(avsproto.TriggerType_TRIGGER_TYPE_EVENT, eventOutputProto, nil) + + // Create raw trigger output data (as it would come from runEventTriggerImmediately) + rawTriggerOutput := map[string]interface{}{ + "found": true, + "queriesCount": 2, + "totalSearched": 5000, + "totalEvents": 1, + "transfer_log": map[string]interface{}{ + "tokenName": "USDC", + "tokenSymbol": "USDC", + "tokenDecimals": uint32(6), + "transactionHash": "0x1b0b9bee55e3a824dedd1dcfaad1790e19e0a68d6717e385a960092077f8b6a1", + "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + "blockNumber": uint64(8560047), + "blockTimestamp": uint64(1750061412000), + "fromAddress": "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", + "toAddress": "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", + "value": "0x00000000000000000000000000000000000000000000000000000000004c4b40", + "valueFormatted": "5", + "transactionIndex": uint32(63), + "logIndex": uint32(83), + }, + } + + // Test buildTriggerDataMap + rawResult := buildTriggerDataMap(avsproto.TriggerType_TRIGGER_TYPE_EVENT, rawTriggerOutput) + + // Verify that both functions produce the same field names for JavaScript access + expectedFields := []string{ + "tokenName", "tokenSymbol", "tokenDecimals", "transactionHash", + "address", "blockNumber", "blockTimestamp", "fromAddress", + "toAddress", "value", "valueFormatted", "transactionIndex", "logIndex", + } + + for _, field := range expectedFields { + // Both results should have the same field names + assert.Contains(t, protobufResult, field, "buildTriggerDataMapFromProtobuf should have field: %s", field) + assert.Contains(t, rawResult, field, "buildTriggerDataMap should have field: %s", field) + + // Both results should have the same values for these fields + assert.Equal(t, protobufResult[field], rawResult[field], "Field %s should have same value in both results", field) + } + + // Verify that neither result has the nested transfer_log structure + assert.NotContains(t, protobufResult, "transfer_log", "buildTriggerDataMapFromProtobuf should not have nested transfer_log") + assert.NotContains(t, rawResult, "transfer_log", "buildTriggerDataMap should not have nested transfer_log") +} + +// TestJavaScriptFieldAccessPattern tests that the field names work correctly for JavaScript destructuring +func TestJavaScriptFieldAccessPattern(t *testing.T) { + // This test simulates the JavaScript destructuring pattern used in the client code: + // const { tokenSymbol, valueFormatted, fromAddress, toAddress, blockTimestamp } = eventTrigger.data; + + triggerOutput := map[string]interface{}{ + "found": true, + "transfer_log": map[string]interface{}{ + "tokenSymbol": "USDC", + "valueFormatted": "5", + "fromAddress": "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", + "toAddress": "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", + "blockTimestamp": uint64(1750061412000), + }, + } + + result := buildTriggerDataMap(avsproto.TriggerType_TRIGGER_TYPE_EVENT, triggerOutput) + + // Simulate JavaScript destructuring - these fields should all be available at top level + tokenSymbol, hasTokenSymbol := result["tokenSymbol"] + valueFormatted, hasValueFormatted := result["valueFormatted"] + fromAddress, hasFromAddress := result["fromAddress"] + toAddress, hasToAddress := result["toAddress"] + blockTimestamp, hasBlockTimestamp := result["blockTimestamp"] + + // All fields should be present + assert.True(t, hasTokenSymbol, "tokenSymbol should be available for JavaScript destructuring") + assert.True(t, hasValueFormatted, "valueFormatted should be available for JavaScript destructuring") + assert.True(t, hasFromAddress, "fromAddress should be available for JavaScript destructuring") + assert.True(t, hasToAddress, "toAddress should be available for JavaScript destructuring") + assert.True(t, hasBlockTimestamp, "blockTimestamp should be available for JavaScript destructuring") + + // All fields should have the correct values (not NaN or undefined) + assert.Equal(t, "USDC", tokenSymbol, "tokenSymbol should not be undefined") + assert.Equal(t, "5", valueFormatted, "valueFormatted should not be undefined") + assert.Equal(t, "0xc60e71bd0f2e6d8832Fea1a2d56091C48493C788", fromAddress, "fromAddress should not be undefined") + assert.Equal(t, "0xfE66125343Aabda4A330DA667431eC1Acb7BbDA9", toAddress, "toAddress should not be undefined") + assert.Equal(t, uint64(1750061412000), blockTimestamp, "blockTimestamp should not be NaN") + + t.Logf("✅ All JavaScript destructuring fields are available:") + t.Logf(" tokenSymbol: %v", tokenSymbol) + t.Logf(" valueFormatted: %v", valueFormatted) + t.Logf(" fromAddress: %v", fromAddress) + t.Logf(" toAddress: %v", toAddress) + t.Logf(" blockTimestamp: %v", blockTimestamp) +} + +// TestFallbackVariableResolutionConsistency tests that the fallback variable resolution +// (where subsequent nodes can use tokenName to match token_name from previous nodes) +// works consistently across all three execution paths: +// 1. run_node_immediately (RunNodeImmediatelyRPC) +// 2. simulateTask (SimulateTask) +// 3. runTask (actual task execution) +func TestFallbackVariableResolutionConsistency(t *testing.T) { + db := testutil.TestMustDB() + defer storage.Destroy(db.(*storage.BadgerStorage)) + + config := testutil.GetAggregatorConfig() + engine := New(db, config, nil, testutil.GetLogger()) + + // Test data with snake_case field names (as would come from protobuf/gRPC) + testData := map[string]interface{}{ + "token_name": "USDC", + "token_symbol": "USDC", + "token_decimals": 6, + "block_number": 12345, + "tx_hash": "0x123abc", + } + + // Create input variables that include a node with snake_case data + inputVariables := map[string]interface{}{ + "eventTrigger": map[string]interface{}{ + "data": testData, + }, + } + + // Test custom code that tries to access both camelCase and snake_case versions + customCodeConfig := map[string]interface{}{ + "source": ` + // Test accessing snake_case (original) + const tokenNameSnake = eventTrigger.data.token_name; + const tokenSymbolSnake = eventTrigger.data.token_symbol; + const tokenDecimalsSnake = eventTrigger.data.token_decimals; + + // Test accessing camelCase (fallback) + const tokenNameCamel = eventTrigger.data.tokenName; + const tokenSymbolCamel = eventTrigger.data.tokenSymbol; + const tokenDecimalsCamel = eventTrigger.data.tokenDecimals; + + return { + snake_case_access: { + token_name: tokenNameSnake, + token_symbol: tokenSymbolSnake, + token_decimals: tokenDecimalsSnake + }, + camel_case_access: { + tokenName: tokenNameCamel, + tokenSymbol: tokenSymbolCamel, + tokenDecimals: tokenDecimalsCamel + }, + both_should_work: tokenNameSnake === tokenNameCamel && tokenSymbolSnake === tokenSymbolCamel + }; + `, + } + + t.Run("RunNodeImmediately", func(t *testing.T) { + result, err := engine.RunNodeImmediately("customCode", customCodeConfig, inputVariables) + assert.NoError(t, err) + assert.NotNil(t, result) + + // Verify both access patterns work + assert.Equal(t, "USDC", result["snake_case_access"].(map[string]interface{})["token_name"]) + assert.Equal(t, "USDC", result["camel_case_access"].(map[string]interface{})["tokenName"]) + assert.True(t, result["both_should_work"].(bool), "Both snake_case and camelCase access should return the same values") + }) + + t.Run("SimulateTask", func(t *testing.T) { + // Create a simple task with manual trigger and custom code node + trigger := &avsproto.TaskTrigger{ + Id: "trigger1", + Name: "manualTrigger", + Type: avsproto.TriggerType_TRIGGER_TYPE_MANUAL, + TriggerType: &avsproto.TaskTrigger_Manual{Manual: true}, + } + + nodes := []*avsproto.TaskNode{ + { + Id: "node1", + Name: "testCustomCode", + TaskType: &avsproto.TaskNode_CustomCode{ + CustomCode: &avsproto.CustomCodeNode{ + Config: &avsproto.CustomCodeNode_Config{ + Source: customCodeConfig["source"].(string), + }, + }, + }, + }, + } + + edges := []*avsproto.TaskEdge{ + { + Id: "edge1", + Source: "trigger1", + Target: "node1", + }, + } + + user := testutil.TestUser1() + execution, err := engine.SimulateTask(user, trigger, nodes, edges, inputVariables) + assert.NoError(t, err) + assert.NotNil(t, execution) + assert.True(t, execution.Success, "Simulation should succeed") + + // Find the custom code step + var customCodeStep *avsproto.Execution_Step + for _, step := range execution.Steps { + if step.Type == avsproto.NodeType_NODE_TYPE_CUSTOM_CODE.String() { + customCodeStep = step + break + } + } + assert.NotNil(t, customCodeStep, "Should find custom code execution step") + assert.True(t, customCodeStep.Success, "Custom code step should succeed") + + // Extract the result from the custom code output + customCodeOutput := customCodeStep.GetCustomCode() + assert.NotNil(t, customCodeOutput) + result := customCodeOutput.Data.AsInterface().(map[string]interface{}) + + // Verify both access patterns work + assert.Equal(t, "USDC", result["snake_case_access"].(map[string]interface{})["token_name"]) + assert.Equal(t, "USDC", result["camel_case_access"].(map[string]interface{})["tokenName"]) + assert.True(t, result["both_should_work"].(bool), "Both snake_case and camelCase access should return the same values") + }) + + // Note: Testing actual runTask would require setting up a full task in storage and queue system, + // which is more complex. The key insight is that runTask uses the same VM and preprocessing + // infrastructure as SimulateTask, so if SimulateTask works, runTask should work too. + // The main difference was in the branch node preprocessing, which we've now fixed. +} + +// TestBranchNodeFallbackResolution specifically tests that branch nodes can use +// the fallback variable resolution in their condition expressions +func TestBranchNodeFallbackResolution(t *testing.T) { + db := testutil.TestMustDB() + defer storage.Destroy(db.(*storage.BadgerStorage)) + + config := testutil.GetAggregatorConfig() + engine := New(db, config, nil, testutil.GetLogger()) + + // Test data with snake_case field names + testData := map[string]interface{}{ + "token_name": "USDC", + "token_amount": 1000, + } + + inputVariables := map[string]interface{}{ + "eventTrigger": map[string]interface{}{ + "data": testData, + }, + } + + // Test branch node that uses camelCase in condition (should fallback to snake_case) + branchConfig := map[string]interface{}{ + "conditions": []map[string]interface{}{ + { + "id": "condition1", + "type": "if", + "expression": "eventTrigger.data.tokenName === 'USDC' && eventTrigger.data.tokenAmount > 500", + }, + }, + } + + t.Run("BranchNodeFallbackResolution", func(t *testing.T) { + result, err := engine.RunNodeImmediately("branch", branchConfig, inputVariables) + assert.NoError(t, err) + assert.NotNil(t, result) + if result != nil && result["success"] != nil { + assert.True(t, result["success"].(bool), "Branch condition should evaluate to true using fallback resolution") + } + }) +} + +// TestContractReadFallbackResolution tests that contract read nodes can use +// fallback variable resolution in their configuration +func TestContractReadFallbackResolution(t *testing.T) { + db := testutil.TestMustDB() + defer storage.Destroy(db.(*storage.BadgerStorage)) + + config := testutil.GetAggregatorConfig() + engine := New(db, config, nil, testutil.GetLogger()) + + // Test data with snake_case field names + testData := map[string]interface{}{ + "contract_address": "0x1234567890123456789012345678901234567890", + } + + inputVariables := map[string]interface{}{ + "eventTrigger": map[string]interface{}{ + "data": testData, + }, + } + + // Test contract read that uses camelCase template (should fallback to snake_case) + contractReadConfig := map[string]interface{}{ + "contractAddress": "{{eventTrigger.data.contractAddress}}", // camelCase template + "contractAbi": `[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]`, + "methodCalls": []interface{}{ + map[string]interface{}{ + "methodName": "decimals", + "callData": "0x313ce567", + }, + }, + } + + t.Run("ContractReadFallbackResolution", func(t *testing.T) { + // This test will fail with RPC connection error, but we can check that the preprocessing worked + // by examining the error message - it should contain the resolved address, not the template + result, err := engine.RunNodeImmediately("contractRead", contractReadConfig, inputVariables) + + // We expect an error due to RPC connection, but the address should be resolved + assert.Error(t, err) + assert.Contains(t, err.Error(), "0x1234567890123456789012345678901234567890", + "Error should contain the resolved contract address, indicating template preprocessing worked") + + // The result might be nil due to the RPC error, which is expected + _ = result + }) +} diff --git a/core/taskengine/vm.go b/core/taskengine/vm.go index bf829d44..1d43ce33 100644 --- a/core/taskengine/vm.go +++ b/core/taskengine/vm.go @@ -8,19 +8,18 @@ import ( "time" "unicode" - sdklogging "github.com/Layr-Labs/eigensdk-go/logging" - - "github.com/oklog/ulid/v2" - + "github.com/dop251/goja" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/oklog/ulid/v2" + "google.golang.org/protobuf/types/known/structpb" "github.com/AvaProtocol/EigenLayer-AVS/core/config" "github.com/AvaProtocol/EigenLayer-AVS/core/taskengine/macros" "github.com/AvaProtocol/EigenLayer-AVS/model" avsproto "github.com/AvaProtocol/EigenLayer-AVS/protobuf" "github.com/AvaProtocol/EigenLayer-AVS/storage" - "github.com/dop251/goja" + sdklogging "github.com/Layr-Labs/eigensdk-go/logging" ) type VMState string @@ -59,9 +58,55 @@ func (c *CommonProcessor) SetOutputVarForStep(stepID string, data any) { if c.vm.vars == nil { c.vm.vars = make(map[string]any) } - c.vm.vars[nodeNameVar] = map[string]any{ - "data": data, + + // Apply dual-access mapping to node output data if it's a map + // This enables both camelCase and snake_case field access for node outputs + // Example: apiNode.data.responseData AND apiNode.data.response_data both work + var processedData any = data + if dataMap, ok := data.(map[string]interface{}); ok { + processedData = CreateDualAccessMap(dataMap) + } + + // Get existing variable or create new one + existingVar := c.vm.vars[nodeNameVar] + var nodeVar map[string]any + if existingMap, ok := existingVar.(map[string]any); ok { + nodeVar = existingMap + } else { + nodeVar = make(map[string]any) + } + + // Set the output data + nodeVar["data"] = processedData + c.vm.vars[nodeNameVar] = nodeVar +} + +func (c *CommonProcessor) SetInputVarForStep(stepID string, inputData any) { + c.vm.mu.Lock() + defer c.vm.mu.Unlock() + nodeNameVar := c.vm.getNodeNameAsVarLocked(stepID) + if c.vm.vars == nil { + c.vm.vars = make(map[string]any) + } + + // Apply dual-access mapping to input data if it's a map + var processedInput any = inputData + if inputMap, ok := inputData.(map[string]interface{}); ok { + processedInput = CreateDualAccessMap(inputMap) + } + + // Get existing variable or create new one + existingVar := c.vm.vars[nodeNameVar] + var nodeVar map[string]any + if existingMap, ok := existingVar.(map[string]any); ok { + nodeVar = existingMap + } else { + nodeVar = make(map[string]any) } + + // Set the input data + nodeVar["input"] = processedInput + c.vm.vars[nodeNameVar] = nodeVar } func (c *CommonProcessor) GetOutputVar(stepID string) any { @@ -312,17 +357,44 @@ func NewVMWithDataAndTransferLog(task *model.Task, triggerData *TriggerData, sma } // Use shared function to build trigger data map from the TransferLog protobuf - triggerDataMap = buildTriggerDataMapFromProtobuf(avsproto.TriggerType_TRIGGER_TYPE_EVENT, v.parsedTriggerData.Event) + triggerDataMap = buildTriggerDataMapFromProtobuf(avsproto.TriggerType_TRIGGER_TYPE_EVENT, v.parsedTriggerData.Event, v.logger) } else { // Use shared function to build trigger data map from protobuf trigger outputs - triggerDataMap = buildTriggerDataMapFromProtobuf(triggerData.Type, triggerData.Output) + triggerDataMap = buildTriggerDataMapFromProtobuf(triggerData.Type, triggerData.Output, v.logger) + } + + // Create dual-access map to support both camelCase and snake_case field access + // This enables both template fallback ({{trigger.data.token_symbol}}) and direct JS access + // (const {tokenSymbol} = eventTrigger.data AND const {token_symbol} = eventTrigger.data) + dualAccessTriggerData := CreateDualAccessMap(triggerDataMap) + + // Extract trigger input data and add it to the trigger variable + triggerVarData := map[string]any{"data": dualAccessTriggerData} + if task != nil && task.Trigger != nil { + triggerInputData := ExtractTriggerInputData(task.Trigger) + if triggerInputData != nil { + // Create dual-access map for trigger input data as well + dualAccessTriggerInput := CreateDualAccessMap(triggerInputData) + triggerVarData["input"] = dualAccessTriggerInput + } } - v.AddVar(triggerNameStd, map[string]any{"data": triggerDataMap}) + + v.AddVar(triggerNameStd, triggerVarData) } } else if task != nil { // Fallback if triggerData is nil but task is not triggerNameStd, err := v.GetTriggerNameAsVar() if err == nil { - v.AddVar(triggerNameStd, map[string]any{"data": map[string]any{}}) // Empty data map + // Extract trigger input data even when triggerData is nil + triggerVarData := map[string]any{"data": map[string]any{}} // Empty data map + if task.Trigger != nil { + triggerInputData := ExtractTriggerInputData(task.Trigger) + if triggerInputData != nil { + // Create dual-access map for trigger input data + dualAccessTriggerInput := CreateDualAccessMap(triggerInputData) + triggerVarData["input"] = dualAccessTriggerInput + } + } + v.AddVar(triggerNameStd, triggerVarData) } } @@ -766,6 +838,13 @@ func (v *VM) executeNode(node *avsproto.TaskNode) (*Step, error) { return nil, fmt.Errorf("executeNode called with nil node") } + // Extract and set input data for this node (making it available as node_name.input) + inputData := ExtractNodeInputData(node) + if inputData != nil { + processor := &CommonProcessor{vm: v} + processor.SetInputVarForStep(node.Id, inputData) + } + var nextStep *Step // This is the *next step in the plan to jump to*, not the execution log step var err error @@ -852,7 +931,7 @@ func (v *VM) runRestApi(stepID string, nodeValue *avsproto.RestAPINode) (*avspro p := NewRestProrcessor(v) // v is passed, CommonProcessor uses v.AddVar executionLog, err := p.Execute(stepID, nodeValue) // p.Execute should use SetOutputVarForStep v.mu.Lock() - executionLog.Inputs = v.collectInputKeysForLog() // Uses locked v.vars + executionLog.Inputs = v.collectInputKeysForLog(stepID) // Pass stepID to exclude current node's variables v.mu.Unlock() // v.addExecutionLog(executionLog) // Caller will add return executionLog, err // RestAPI node doesn't dictate a jump @@ -874,7 +953,7 @@ func (v *VM) runGraphQL(stepID string, node *avsproto.GraphQLQueryNode) (*avspro _ = dataOutput // Use dataOutput if needed later v.mu.Lock() if executionLog != nil { // Guard against nil log - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) // Caller will add @@ -892,7 +971,7 @@ func (v *VM) runContractRead(stepID string, node *avsproto.ContractReadNode) (*a executionLog, err := processor.Execute(stepID, node) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() return executionLog, err @@ -918,7 +997,7 @@ func (v *VM) runContractRead(stepID string, node *avsproto.ContractReadNode) (*a executionLog, err = processor.Execute(stepID, node) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) @@ -947,7 +1026,7 @@ func (v *VM) runContractWrite(stepID string, node *avsproto.ContractWriteNode) ( executionLog, err = processor.Execute(stepID, node) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) @@ -983,12 +1062,21 @@ func (v *VM) runCustomCode(stepID string, node *avsproto.CustomCodeNode) (*avspr if v.logger != nil { v.logger.Error("runCustomCode: CustomCodeNode Config is nil", "stepID", stepID) } + // Get the node's input data + var nodeInput *structpb.Value + v.mu.Lock() + if taskNode, exists := v.TaskNodes[stepID]; exists { + nodeInput = taskNode.Input + } + v.mu.Unlock() + return &avsproto.Execution_Step{ Id: stepID, // Use new 'id' field Success: false, Error: "CustomCodeNode Config is nil", StartAt: time.Now().UnixMilli(), EndAt: time.Now().UnixMilli(), + Input: nodeInput, // Include node input data for debugging }, fmt.Errorf("CustomCodeNode Config is nil") } @@ -997,7 +1085,7 @@ func (v *VM) runCustomCode(stepID string, node *avsproto.CustomCodeNode) (*avspr executionLog, err := r.Execute(stepID, node) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) @@ -1018,7 +1106,7 @@ func (v *VM) runBranch(stepID string, nodeValue *avsproto.BranchNode) (*avsproto // BranchProcessor.Execute should ideally populate its own executionLog.Inputs. // If not, this is a fallback. if executionLog.Inputs == nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() } @@ -1036,7 +1124,7 @@ func (v *VM) runLoop(stepID string, nodeValue *avsproto.LoopNode) (*avsproto.Exe executionLog, err := p.Execute(stepID, nodeValue) // Loop processor internally calls RunNodeWithInputs v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) @@ -1048,7 +1136,7 @@ func (v *VM) runFilter(stepID string, nodeValue *avsproto.FilterNode) (*avsproto executionLog, err := p.Execute(stepID, nodeValue) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() // v.addExecutionLog(executionLog) @@ -1070,7 +1158,7 @@ func (v *VM) runEthTransfer(stepID string, node *avsproto.ETHTransferNode) (*avs executionLog, err := processor.Execute(stepID, node) v.mu.Lock() if executionLog != nil { - executionLog.Inputs = v.collectInputKeysForLog() + executionLog.Inputs = v.collectInputKeysForLog(stepID) } v.mu.Unlock() return executionLog, err @@ -1501,22 +1589,85 @@ func (v *VM) looksLikeVariableReference(content string) bool { return false } -func (v *VM) collectInputKeysForLog() []string { +func (v *VM) collectInputKeysForLog(excludeStepID string) []string { // This function assumes v.mu is already locked by the caller - inputKeys := make([]string, 0, len(v.vars)) - for k := range v.vars { + inputKeys := make([]string, 0, len(v.vars)*2) // Allocate space for both .data and .input + + // Get the variable name for the current node to exclude it + var excludeVarName string + if excludeStepID != "" { + excludeVarName = v.getNodeNameAsVarLocked(excludeStepID) + } + + // Debug logging to understand what's happening + if v.logger != nil { + v.logger.Info("🔍 collectInputKeysForLog DEBUG", + "excludeStepID", excludeStepID, + "excludeVarName", excludeVarName, + "totalVars", len(v.vars)) + } + + for k, value := range v.vars { if !contains(macros.MacroFuncs, k) { // `contains` is a global helper - varname := k - if varname == APContextVarName { // Specific handling for apContext - varname = APContextConfigVarsPath - } else if varname == WorkflowContextVarName { // Specific handling for workflowContext - varname = WorkflowContextVarName // Use as-is, no .data suffix + // Debug log each variable being processed + if v.logger != nil { + v.logger.Info("🔍 Processing variable", "key", k, "exclude", excludeVarName, "match", k == excludeVarName) + } + + // Skip the current node's own variables from its inputsList + if excludeVarName != "" && k == excludeVarName { + if v.logger != nil { + v.logger.Info("🚫 Excluding current node variable", "key", k) + } + continue + } + + // Skip system variables that shouldn't appear as inputs + if k == APContextVarName { + inputKeys = append(inputKeys, APContextConfigVarsPath) + } else if k == WorkflowContextVarName { + inputKeys = append(inputKeys, WorkflowContextVarName) // Use as-is, no .data suffix + } else if k == "triggerConfig" { + // Skip triggerConfig system variable - it shouldn't appear in inputsList + if v.logger != nil { + v.logger.Info("🚫 Excluding triggerConfig system variable") + } + continue } else { - varname = fmt.Sprintf("%s.%s", varname, DataSuffix) + // For regular variables, check if they have data and/or input fields + if valueMap, ok := value.(map[string]any); ok { + // Check for .data field + if _, hasData := valueMap["data"]; hasData { + dataKey := fmt.Sprintf("%s.%s", k, DataSuffix) + inputKeys = append(inputKeys, dataKey) + if v.logger != nil { + v.logger.Info("✅ Added data field", "key", dataKey) + } + } + // Check for .input field + if _, hasInput := valueMap["input"]; hasInput { + inputKey := fmt.Sprintf("%s.input", k) + inputKeys = append(inputKeys, inputKey) + if v.logger != nil { + v.logger.Info("✅ Added input field", "key", inputKey) + } + } + } else { + // For non-map variables (simple scalars like input variables), use the variable name as-is + // This fixes the issue where input variables like "userToken" were incorrectly becoming "userToken.data" + inputKeys = append(inputKeys, k) + if v.logger != nil { + v.logger.Info("✅ Added scalar variable", "key", k) + } + } } - inputKeys = append(inputKeys, varname) } } + + if v.logger != nil { + v.logger.Info("🔍 Final inputKeys", "keys", inputKeys, "count", len(inputKeys)) + } + return inputKeys } @@ -1538,7 +1689,15 @@ func (v *VM) CollectInputs() map[string]string { } else if varname == WorkflowContextVarName { varname = WorkflowContextVarName // Use as-is, no .data suffix } else { - varname = fmt.Sprintf("%s.%s", varname, DataSuffix) + // Check if this is a map variable with .data field + if valueMap, ok := value.(map[string]any); ok { + if _, hasData := valueMap["data"]; hasData { + // Only add .data suffix for variables that actually have a data field + varname = fmt.Sprintf("%s.%s", varname, DataSuffix) + } + // For map variables without .data field, use as-is + } + // For non-map variables (simple scalars like input variables), use as-is } inputs[varname] = valueStr } @@ -1562,6 +1721,7 @@ func (v *VM) RunNodeWithInputs(node *avsproto.TaskNode, inputVariables map[strin Error: "BlockTrigger nodes require real blockchain data - mock data not supported", StartAt: time.Now().UnixMilli(), EndAt: time.Now().UnixMilli(), + Input: node.Input, // Include node input data for debugging }, fmt.Errorf("BlockTrigger nodes require real blockchain data - mock data not supported") } @@ -1856,9 +2016,26 @@ func CreateNodeFromType(nodeType string, config map[string]interface{}, nodeID s branchConfig := &avsproto.BranchNode_Config{} // Handle conditions from client - conditionsData, ok := config["conditions"].([]interface{}) - if !ok || len(conditionsData) == 0 { - return nil, fmt.Errorf("branch node requires conditions configuration") + conditionsValue, exists := config["conditions"] + if !exists { + return nil, fmt.Errorf("branch node requires conditions configuration - no conditions field found") + } + + conditionsData, ok := conditionsValue.([]interface{}) + if !ok { + // Try to convert from []map[string]interface{} to []interface{} + if conditionsSlice, ok := conditionsValue.([]map[string]interface{}); ok { + conditionsData = make([]interface{}, len(conditionsSlice)) + for i, condition := range conditionsSlice { + conditionsData[i] = condition + } + } else { + return nil, fmt.Errorf("branch node requires conditions configuration - invalid type: %T", conditionsValue) + } + } + + if len(conditionsData) == 0 { + return nil, fmt.Errorf("branch node requires conditions configuration - empty conditions array") } conditions := make([]*avsproto.BranchNode_Condition, len(conditionsData)) @@ -2059,13 +2236,15 @@ func (v *VM) createExecutionStep(nodeId string, success bool, errorMsg string, l v.mu.Lock() defer v.mu.Unlock() - // Look up the node to get its type and name + // Look up the node to get its type, name, and input data var nodeType string = "UNSPECIFIED" var nodeName string = "unknown" + var nodeInput *structpb.Value if node, exists := v.TaskNodes[nodeId]; exists { nodeType = node.Type.String() nodeName = node.Name + nodeInput = node.Input } step := &avsproto.Execution_Step{ @@ -2077,6 +2256,7 @@ func (v *VM) createExecutionStep(nodeId string, success bool, errorMsg string, l EndAt: startTime, // Will be updated by caller if needed Type: nodeType, // Use new 'type' field as string Name: nodeName, // Use new 'name' field + Input: nodeInput, // Include node input data for debugging } return step @@ -2126,3 +2306,205 @@ func (v *VM) AnalyzeExecutionResult() (bool, string, int) { return false, errorMessage, failedCount } + +// GetNodeDataForExecution retrieves node name and input data for a given stepID +// This helper function extracts the repetitive locking pattern used across all vm_runner files +// to get node information from TaskNodes map. +// +// Returns: +// - nodeName: The name of the node (defaults to "unknown" if not found) +// - nodeInput: The input data of the node (can be nil) +func (v *VM) GetNodeDataForExecution(stepID string) (nodeName string, nodeInput *structpb.Value) { + nodeName = "unknown" // default value + + v.mu.Lock() + defer v.mu.Unlock() + + if taskNode, exists := v.TaskNodes[stepID]; exists { + nodeName = taskNode.Name + nodeInput = taskNode.Input + } + + return nodeName, nodeInput +} + +// ProcessInputVariableWithDualAccess processes a single input variable, applying dual-access mapping +// to data fields when needed. This extracts the repetitive logic used throughout the codebase +// for processing input variables in VM contexts. +// +// The function: +// 1. Checks if the value is a map with a "data" field +// 2. If so, applies CreateDualAccessMap to the data field +// 3. Returns the processed value ready for VM.AddVar() +// +// This eliminates code duplication across engine.go, run_node_immediately.go, executor.go, etc. +func ProcessInputVariableWithDualAccess(value interface{}) interface{} { + // Apply dual-access mapping if the value is a map with a "data" field + if valueMap, ok := value.(map[string]interface{}); ok { + if dataField, hasData := valueMap["data"]; hasData { + if dataMap, isDataMap := dataField.(map[string]interface{}); isDataMap { + // Apply dual-access mapping to the data field + dualAccessData := CreateDualAccessMap(dataMap) + // Create a new map with the dual-access data + processedValue := make(map[string]interface{}) + for k, v := range valueMap { + if k == "data" { + processedValue[k] = dualAccessData + } else { + processedValue[k] = v + } + } + return processedValue + } + } + } + // Return original value if no processing needed + return value +} + +// ProcessInputVariablesWithDualAccess processes a map of input variables, applying dual-access mapping +// where needed. This is a convenience function for batch processing. +func ProcessInputVariablesWithDualAccess(inputVariables map[string]interface{}) map[string]interface{} { + if inputVariables == nil { + return nil + } + + processedInputVariables := make(map[string]interface{}) + for key, value := range inputVariables { + processedInputVariables[key] = ProcessInputVariableWithDualAccess(value) + } + return processedInputVariables +} + +// CreateDualAccessMap creates a map with both camelCase and snake_case field names +// pointing to the same values. This enables JavaScript code to access fields using +// either naming convention, providing fallback support for direct variable access. +// +// Example: +// +// input: {"tokenSymbol": "USDC", "blockNumber": 123} +// output: {"tokenSymbol": "USDC", "token_symbol": "USDC", "blockNumber": 123, "block_number": 123} +// +// This solves the issue where: +// - Templates use fallback: {{trigger.data.token_symbol}} -> tries token_symbol, then tokenSymbol +// - Direct JS access needs both: const {tokenSymbol} = data AND const {token_symbol} = data +func CreateDualAccessMap(data map[string]interface{}) map[string]interface{} { + if data == nil { + return nil + } + + result := make(map[string]interface{}) + + // First, copy all original fields + for key, value := range data { + result[key] = value + } + + // Then, add the alternate naming convention for each field + for key, value := range data { + // Check if the key contains underscores (snake_case) + if strings.Contains(key, "_") { + // Convert snake_case to camelCase and add it + camelKey := convertToCamelCase(key) + if camelKey != key && result[camelKey] == nil { + result[camelKey] = value + } + } else { + // Check if the key contains uppercase letters (camelCase) + hasCamelCase := false + for _, r := range key { + if unicode.IsUpper(r) { + hasCamelCase = true + break + } + } + + if hasCamelCase { + // Convert camelCase to snake_case and add it + snakeKey := convertToSnakeCase(key) + if snakeKey != key && result[snakeKey] == nil { + result[snakeKey] = value + } + } + } + } + + return result +} + +// ExtractNodeInputData extracts input data from a TaskNode protobuf message +func ExtractNodeInputData(taskNode *avsproto.TaskNode) map[string]interface{} { + if taskNode == nil || taskNode.Input == nil { + return nil + } + + // Convert protobuf.Value to Go native types + inputInterface := taskNode.Input.AsInterface() + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + return inputMap + } + return nil +} + +// ExtractTriggerInputData extracts input data from a TaskTrigger protobuf message +func ExtractTriggerInputData(trigger *avsproto.TaskTrigger) map[string]interface{} { + if trigger == nil { + return nil + } + + fmt.Printf("🔍 ExtractTriggerInputData: trigger type = %T, trigger.GetInput() = %+v\n", trigger.GetTriggerType(), trigger.GetInput()) + + // Check each trigger type and extract input from the correct nested object + switch trigger.GetTriggerType().(type) { + case *avsproto.TaskTrigger_Block: + blockTrigger := trigger.GetBlock() + if blockTrigger != nil && blockTrigger.GetInput() != nil { + inputInterface := blockTrigger.GetInput().AsInterface() + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + return inputMap + } + } + case *avsproto.TaskTrigger_Cron: + cronTrigger := trigger.GetCron() + if cronTrigger != nil && cronTrigger.GetInput() != nil { + inputInterface := cronTrigger.GetInput().AsInterface() + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + return inputMap + } + } + case *avsproto.TaskTrigger_Event: + eventTrigger := trigger.GetEvent() + if eventTrigger != nil && eventTrigger.GetInput() != nil { + inputInterface := eventTrigger.GetInput().AsInterface() + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + return inputMap + } + } + case *avsproto.TaskTrigger_FixedTime: + fixedTimeTrigger := trigger.GetFixedTime() + if fixedTimeTrigger != nil && fixedTimeTrigger.GetInput() != nil { + inputInterface := fixedTimeTrigger.GetInput().AsInterface() + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + return inputMap + } + } + case *avsproto.TaskTrigger_Manual: + fmt.Printf("🔍 ExtractTriggerInputData: Processing Manual trigger, trigger.GetInput() = %+v\n", trigger.GetInput()) + // Manual triggers use the top-level TaskTrigger.input field + // since the trigger_type is just a boolean, not a nested object + if trigger.GetInput() != nil { + inputInterface := trigger.GetInput().AsInterface() + fmt.Printf("🔍 ExtractTriggerInputData: Manual trigger inputInterface = %+v, type = %T\n", inputInterface, inputInterface) + if inputMap, ok := inputInterface.(map[string]interface{}); ok { + fmt.Printf("🔍 ExtractTriggerInputData: Manual trigger returning inputMap = %+v\n", inputMap) + return inputMap + } else { + fmt.Printf("🔍 ExtractTriggerInputData: Manual trigger inputInterface is not map[string]interface{}, got %T\n", inputInterface) + } + } else { + fmt.Printf("🔍 ExtractTriggerInputData: Manual trigger has no input field\n") + } + return nil + } + return nil +} diff --git a/core/taskengine/vm_runner_branch.go b/core/taskengine/vm_runner_branch.go index a670e50a..e89e0523 100644 --- a/core/taskengine/vm_runner_branch.go +++ b/core/taskengine/vm_runner_branch.go @@ -81,13 +81,8 @@ func (r *BranchProcessor) Validate(node *avsproto.BranchNode) error { func (r *BranchProcessor) Execute(stepID string, node *avsproto.BranchNode) (*avsproto.Execution_Step, *Step, error) { t0 := time.Now().UnixMilli() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) executionStep := &avsproto.Execution_Step{ Id: stepID, @@ -95,6 +90,7 @@ func (r *BranchProcessor) Execute(stepID string, node *avsproto.BranchNode) (*av StartAt: t0, Type: avsproto.NodeType_NODE_TYPE_BRANCH.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var log strings.Builder @@ -179,12 +175,7 @@ func (r *BranchProcessor) Execute(stepID string, node *avsproto.BranchNode) (*av } // Preprocess the expression using the VM's current variable context - processedExpression := condition.Expression - if strings.Contains(processedExpression, "{{") { - processedExpression = r.vm.preprocessText(processedExpression) - } else { - processedExpression = r.vm.preprocessTextWithVariableMapping(condition.Expression) - } + processedExpression := r.vm.preprocessTextWithVariableMapping(condition.Expression) log.WriteString("Processed expression for '" + condition.Id + "': " + processedExpression + "\n") // Check if expression became empty after preprocessing (indicates variable resolution failure) diff --git a/core/taskengine/vm_runner_contract_read.go b/core/taskengine/vm_runner_contract_read.go index 791cdf0b..03cdeb29 100644 --- a/core/taskengine/vm_runner_contract_read.go +++ b/core/taskengine/vm_runner_contract_read.go @@ -90,7 +90,11 @@ func (r *ContractReadProcessor) buildStructuredData(method *abi.Method, result [ // executeMethodCall executes a single method call and returns the result func (r *ContractReadProcessor) executeMethodCall(ctx context.Context, contractAbi *abi.ABI, contractAddress common.Address, methodCall *avsproto.ContractReadNode_MethodCall) *avsproto.ContractReadNode_MethodResult { - calldata := common.FromHex(methodCall.CallData) + // Preprocess template variables in method call data + preprocessedCallData := r.vm.preprocessTextWithVariableMapping(methodCall.CallData) + methodName := r.vm.preprocessTextWithVariableMapping(methodCall.MethodName) + + calldata := common.FromHex(preprocessedCallData) msg := ethereum.CallMsg{ To: &contractAddress, Data: calldata, @@ -102,7 +106,7 @@ func (r *ContractReadProcessor) executeMethodCall(ctx context.Context, contractA return &avsproto.ContractReadNode_MethodResult{ Success: false, Error: fmt.Sprintf("contract call failed: %v", err), - MethodName: methodCall.MethodName, + MethodName: methodName, Data: []*avsproto.ContractReadNode_MethodResult_StructuredField{}, } } @@ -123,7 +127,7 @@ func (r *ContractReadProcessor) executeMethodCall(ctx context.Context, contractA "calldata", fmt.Sprintf("0x%x", calldata), "output_length", len(output), "output_hex", fmt.Sprintf("0x%x", output), - "method_name", methodCall.MethodName, + "method_name", methodName, ) } @@ -133,17 +137,17 @@ func (r *ContractReadProcessor) executeMethodCall(ctx context.Context, contractA return &avsproto.ContractReadNode_MethodResult{ Success: false, Error: fmt.Sprintf("failed to detect method from ABI: %v", err), - MethodName: methodCall.MethodName, + MethodName: methodName, Data: []*avsproto.ContractReadNode_MethodResult_StructuredField{}, } } // Validate that the provided methodName matches the actual method detected from callData - if method.Name != methodCall.MethodName { + if method.Name != methodName { return &avsproto.ContractReadNode_MethodResult{ Success: false, - Error: fmt.Sprintf("method name mismatch: callData corresponds to '%s' but methodName is '%s'. Please verify the function selector matches the intended method", method.Name, methodCall.MethodName), - MethodName: methodCall.MethodName, + Error: fmt.Sprintf("method name mismatch: callData corresponds to '%s' but methodName is '%s'. Please verify the function selector matches the intended method", method.Name, methodName), + MethodName: methodName, Data: []*avsproto.ContractReadNode_MethodResult_StructuredField{}, } } @@ -203,13 +207,8 @@ func (r *ContractReadProcessor) Execute(stepID string, node *avsproto.ContractRe ctx := context.Background() t0 := time.Now().UnixMilli() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) s := &avsproto.Execution_Step{ Id: stepID, @@ -220,6 +219,7 @@ func (r *ContractReadProcessor) Execute(stepID string, node *avsproto.ContractRe StartAt: t0, Type: avsproto.NodeType_NODE_TYPE_CONTRACT_READ.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var err error @@ -250,14 +250,18 @@ func (r *ContractReadProcessor) Execute(stepID string, node *avsproto.ContractRe return s, err } + // Preprocess template variables in configuration + contractAddress := r.vm.preprocessTextWithVariableMapping(config.ContractAddress) + contractAbi := r.vm.preprocessTextWithVariableMapping(config.ContractAbi) + // Parse the ABI - parsedABI, err := abi.JSON(strings.NewReader(config.ContractAbi)) + parsedABI, err := abi.JSON(strings.NewReader(contractAbi)) if err != nil { err = fmt.Errorf("failed to parse ABI: %w", err) return s, err } - contractAddr := common.HexToAddress(config.ContractAddress) + contractAddr := common.HexToAddress(contractAddress) var results []*avsproto.ContractReadNode_MethodResult // Execute each method call serially diff --git a/core/taskengine/vm_runner_contract_write.go b/core/taskengine/vm_runner_contract_write.go index becc24c8..9c3359ae 100644 --- a/core/taskengine/vm_runner_contract_write.go +++ b/core/taskengine/vm_runner_contract_write.go @@ -290,13 +290,8 @@ func (r *ContractWriteProcessor) decodeReturnData(methodName string, contractABI func (r *ContractWriteProcessor) Execute(stepID string, node *avsproto.ContractWriteNode) (*avsproto.Execution_Step, error) { t0 := time.Now().UnixMilli() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) s := &avsproto.Execution_Step{ Id: stepID, @@ -307,6 +302,7 @@ func (r *ContractWriteProcessor) Execute(stepID string, node *avsproto.ContractW StartAt: t0, Type: avsproto.NodeType_NODE_TYPE_CONTRACT_WRITE.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var log strings.Builder diff --git a/core/taskengine/vm_runner_customcode.go b/core/taskengine/vm_runner_customcode.go index 91b0f837..6d511a87 100644 --- a/core/taskengine/vm_runner_customcode.go +++ b/core/taskengine/vm_runner_customcode.go @@ -185,13 +185,8 @@ func containsReturnStatement(code string) bool { func (r *JSProcessor) Execute(stepID string, node *avsproto.CustomCodeNode) (*avsproto.Execution_Step, error) { t0 := time.Now() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) s := &avsproto.Execution_Step{ Id: stepID, @@ -202,6 +197,7 @@ func (r *JSProcessor) Execute(stepID string, node *avsproto.CustomCodeNode) (*av StartAt: t0.UnixMilli(), Type: avsproto.NodeType_NODE_TYPE_CUSTOM_CODE.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var sb strings.Builder diff --git a/core/taskengine/vm_runner_eth_transfer.go b/core/taskengine/vm_runner_eth_transfer.go index 09a0f580..b2989d95 100644 --- a/core/taskengine/vm_runner_eth_transfer.go +++ b/core/taskengine/vm_runner_eth_transfer.go @@ -30,13 +30,8 @@ func NewETHTransferProcessor(vm *VM, ethClient *ethclient.Client, smartWalletCon func (p *ETHTransferProcessor) Execute(stepID string, node *avsproto.ETHTransferNode) (*avsproto.Execution_Step, error) { startTime := time.Now() - // Look up the task node to get the name - var nodeName string = "unknown" - p.vm.mu.Lock() - if taskNode, exists := p.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - p.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := p.vm.GetNodeDataForExecution(stepID) // Create execution log executionLog := &avsproto.Execution_Step{ @@ -45,6 +40,7 @@ func (p *ETHTransferProcessor) Execute(stepID string, node *avsproto.ETHTransfer Success: false, Type: avsproto.NodeType_NODE_TYPE_ETH_TRANSFER.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } // Get configuration @@ -56,8 +52,9 @@ func (p *ETHTransferProcessor) Execute(stepID string, node *avsproto.ETHTransfer return executionLog, err } - destination := config.GetDestination() - amountStr := config.GetAmount() + // Preprocess template variables in configuration + destination := p.vm.preprocessTextWithVariableMapping(config.GetDestination()) + amountStr := p.vm.preprocessTextWithVariableMapping(config.GetAmount()) if destination == "" { err := fmt.Errorf("destination address is required for ETH transfer") diff --git a/core/taskengine/vm_runner_filter.go b/core/taskengine/vm_runner_filter.go index 97b0b828..3d314214 100644 --- a/core/taskengine/vm_runner_filter.go +++ b/core/taskengine/vm_runner_filter.go @@ -58,13 +58,8 @@ func (r *FilterProcessor) wrapExpressionForExecution(cleanExpression string) str func (r *FilterProcessor) Execute(stepID string, node *avsproto.FilterNode) (*avsproto.Execution_Step, error) { t0 := time.Now() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) executionLogStep := &avsproto.Execution_Step{ Id: stepID, @@ -75,6 +70,7 @@ func (r *FilterProcessor) Execute(stepID string, node *avsproto.FilterNode) (*av StartAt: t0.UnixMilli(), Type: avsproto.NodeType_NODE_TYPE_FILTER.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var logBuilder strings.Builder diff --git a/core/taskengine/vm_runner_graphql_query.go b/core/taskengine/vm_runner_graphql_query.go index 0d91d0c5..4dce6eae 100644 --- a/core/taskengine/vm_runner_graphql_query.go +++ b/core/taskengine/vm_runner_graphql_query.go @@ -37,13 +37,8 @@ func (r *GraphqlQueryProcessor) Execute(stepID string, node *avsproto.GraphQLQue ctx := context.Background() t0 := time.Now().UnixMilli() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) step := &avsproto.Execution_Step{ Id: stepID, @@ -54,6 +49,7 @@ func (r *GraphqlQueryProcessor) Execute(stepID string, node *avsproto.GraphQLQue StartAt: t0, Type: avsproto.NodeType_NODE_TYPE_GRAPHQL_QUERY.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var err error diff --git a/core/taskengine/vm_runner_loop.go b/core/taskengine/vm_runner_loop.go index 63597ded..dbea27f0 100644 --- a/core/taskengine/vm_runner_loop.go +++ b/core/taskengine/vm_runner_loop.go @@ -28,13 +28,8 @@ func NewLoopProcessor(vm *VM) *LoopProcessor { func (r *LoopProcessor) Execute(stepID string, node *avsproto.LoopNode) (*avsproto.Execution_Step, error) { t0 := time.Now().UnixMilli() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) s := &avsproto.Execution_Step{ Id: stepID, @@ -45,6 +40,7 @@ func (r *LoopProcessor) Execute(stepID string, node *avsproto.LoopNode) (*avspro StartAt: t0, Type: avsproto.NodeType_NODE_TYPE_LOOP.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var log strings.Builder diff --git a/core/taskengine/vm_runner_rest.go b/core/taskengine/vm_runner_rest.go index 7ed0375e..a1109e07 100644 --- a/core/taskengine/vm_runner_rest.go +++ b/core/taskengine/vm_runner_rest.go @@ -32,13 +32,8 @@ func NewRestProrcessor(vm *VM) *RestProcessor { func (r *RestProcessor) Execute(stepID string, node *avsproto.RestAPINode) (*avsproto.Execution_Step, error) { t0 := time.Now() - // Look up the task node to get the name - var nodeName string = "unknown" - r.vm.mu.Lock() - if taskNode, exists := r.vm.TaskNodes[stepID]; exists { - nodeName = taskNode.Name - } - r.vm.mu.Unlock() + // Get node data using helper function to reduce duplication + nodeName, nodeInput := r.vm.GetNodeDataForExecution(stepID) executionLogStep := &avsproto.Execution_Step{ Id: stepID, @@ -49,6 +44,7 @@ func (r *RestProcessor) Execute(stepID string, node *avsproto.RestAPINode) (*avs StartAt: t0.UnixMilli(), Type: avsproto.NodeType_NODE_TYPE_REST_API.String(), Name: nodeName, + Input: nodeInput, // Include node input data for debugging } var logBuilder strings.Builder diff --git a/core/taskengine/vm_secrets_test.go b/core/taskengine/vm_secrets_test.go index 203689b0..866443c0 100644 --- a/core/taskengine/vm_secrets_test.go +++ b/core/taskengine/vm_secrets_test.go @@ -8,6 +8,24 @@ import ( avsproto "github.com/AvaProtocol/EigenLayer-AVS/protobuf" ) +// getNestedStringMapKeys is a helper function to collect keys from a nested string map for testing purposes +func getNestedStringMapKeys(m map[string]map[string]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +// testGetStringMapKeys is a helper function to collect keys from a string map for testing purposes +func testGetStringMapKeys(m map[string]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + // TestSecretAccessPath verifies that secrets are correctly accessible via apContext.configVars // and that the access path hasn't changed func TestSecretAccessPath(t *testing.T) { @@ -54,7 +72,8 @@ func TestSecretAccessPath(t *testing.T) { // Test 3: Verify configVars exists in apContext configVars, exists := apContextMap[ConfigVarsPath] if !exists { - t.Errorf("configVars not found in apContext, available keys: %v", getMapKeys(apContextMap)) + keys := getNestedStringMapKeys(apContextMap) + t.Errorf("configVars not found in apContext, available keys: %v", keys) return } @@ -62,7 +81,8 @@ func TestSecretAccessPath(t *testing.T) { for key, expectedValue := range testSecrets { actualValue, exists := configVars[key] if !exists { - t.Errorf("Secret key '%s' not found in configVars, available keys: %v", key, getMapKeys(configVars)) + keys := testGetStringMapKeys(configVars) + t.Errorf("Secret key '%s' not found in configVars, available keys: %v", key, keys) continue } if actualValue != expectedValue { @@ -225,20 +245,13 @@ func TestCollectInputsIncludesSecrets(t *testing.T) { // Verify that apContext.configVars is included if _, exists := inputs[APContextConfigVarsPath]; !exists { - t.Errorf("apContext.configVars not found in CollectInputs output, available keys: %v", getMapKeys(inputs)) + keys := testGetStringMapKeys(inputs) + t.Errorf("apContext.configVars not found in CollectInputs output, available keys: %v", keys) } // Verify that test_var.data is included if _, exists := inputs["test_var.data"]; !exists { - t.Errorf("test_var.data not found in CollectInputs output, available keys: %v", getMapKeys(inputs)) + keys := testGetStringMapKeys(inputs) + t.Errorf("test_var.data not found in CollectInputs output, available keys: %v", keys) } } - -// Helper function to get map keys for debugging -func getMapKeys[K comparable, V any](m map[K]V) []K { - keys := make([]K, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - return keys -} diff --git a/operator/operator.go b/operator/operator.go index f0668e8d..42897401 100644 --- a/operator/operator.go +++ b/operator/operator.go @@ -451,6 +451,9 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { operatorId: [32]byte{0}, // this is set below operatorEcdsaPrivateKey: operatorEcdsaPrivateKey, + // Copy apConfigAddr from tempOperator (set by PopulateKnownConfigByChainID) + apConfigAddr: tempOperator.apConfigAddr, + txManager: txMgr, elapsing: elapsing, } @@ -493,19 +496,44 @@ func (o *Operator) Start(ctx context.Context) error { if o.signerAddress.Cmp(o.operatorAddr) != 0 { // Ensure alias key is correctly bind to operator address o.logger.Infof("checking operator alias address. operator: %s alias %s", o.operatorAddr, o.signerAddress) + o.logger.Infof("APConfig contract address: %s", o.apConfigAddr.Hex()) + apConfigContract, err := apconfig.GetContract(o.config.EthRpcUrl, o.apConfigAddr) if err != nil { + o.logger.Errorf("❌ Failed to get APConfig contract at %s: %v", o.apConfigAddr.Hex(), err) return fmt.Errorf("failed to get APConfig contract: %w", err) } - aliasAddress, err := apConfigContract.GetAlias(nil, o.operatorAddr) + + // Create timeout context for the contract call (30 seconds) + timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + // Create proper call options with timeout + callOpts := &bind.CallOpts{ + Pending: false, + Context: timeoutCtx, + } + + o.logger.Infof("Calling GetAlias on APConfig contract for operator %s...", o.operatorAddr.Hex()) + aliasAddress, err := apConfigContract.GetAlias(callOpts, o.operatorAddr) if err != nil { - panic(err) + o.logger.Errorf("❌ Failed to get alias for operator %s from APConfig contract", o.operatorAddr.Hex()) + o.logger.Errorf(" Error: %v", err) + o.logger.Infof("🔧 SOLUTION: You need to declare/update your alias key mapping in the APConfig contract") + o.logger.Infof(" Run: ./out/ap operator declareAlias --config=%s --address=%s", "config/operator-ethereum.yaml", "/path/to/your/alias/key.json") + return fmt.Errorf("failed to get alias for operator %s: %w", o.operatorAddr.Hex(), err) } + o.logger.Infof("Retrieved alias address from contract: %s", aliasAddress.Hex()) if o.signerAddress.Cmp(aliasAddress) == 0 { - o.logger.Infof("Confirm operator %s matches alias %s", o.operatorAddr, o.signerAddress) + o.logger.Infof("✅ Confirmed operator %s matches alias %s", o.operatorAddr, o.signerAddress) } else { - panic(fmt.Errorf("ECDSA private key doesn't match operator address")) + o.logger.Errorf("❌ ALIAS MISMATCH:") + o.logger.Errorf(" Expected alias (from your key): %s", o.signerAddress.Hex()) + o.logger.Errorf(" Actual alias (from contract): %s", aliasAddress.Hex()) + o.logger.Infof("🔧 SOLUTION: Update your alias key mapping in the APConfig contract") + o.logger.Infof(" Run: ./out/ap operator declareAlias --config=%s --address=%s", "config/operator-ethereum.yaml", "/path/to/your/correct/alias/key.json") + return fmt.Errorf("ECDSA private key doesn't match the declared alias address. Expected: %s, Got: %s", o.signerAddress.Hex(), aliasAddress.Hex()) } } @@ -548,12 +576,12 @@ func (o *Operator) retryConnect() error { o.aggregatorConn, err = grpc.NewClient(o.config.AggregatorServerIpPortAddress, opts...) if err != nil { if strings.Contains(err.Error(), "connection refused") { - o.logger.Info("❌ Cannot create gRPC client for aggregator", + o.logger.Error("❌ Cannot create gRPC client for aggregator", "aggregator_address", o.config.AggregatorServerIpPortAddress, "operator", o.config.OperatorAddress, "raw_error", err) } else { - o.logger.Info("❌ Failed to create gRPC client for aggregator", + o.logger.Error("❌ Failed to create gRPC client for aggregator", "aggregator_address", o.config.AggregatorServerIpPortAddress, "operator", o.config.OperatorAddress, "raw_error", err) @@ -576,17 +604,17 @@ func (o *Operator) retryConnect() error { if pingErr != nil { if strings.Contains(pingErr.Error(), "connection refused") { - o.logger.Info("❌ Cannot connect to aggregator - service appears to be down", + o.logger.Error("❌ Cannot connect to aggregator - service appears to be down", "aggregator_address", o.config.AggregatorServerIpPortAddress, "operator", o.config.OperatorAddress, "raw_error", pingErr) } else if strings.Contains(pingErr.Error(), "no such host") || strings.Contains(pingErr.Error(), "name resolution") { - o.logger.Info("❌ Cannot resolve aggregator hostname", + o.logger.Error("❌ Cannot resolve aggregator hostname", "aggregator_address", o.config.AggregatorServerIpPortAddress, "operator", o.config.OperatorAddress, "raw_error", pingErr) } else { - o.logger.Info("❌ Failed to establish connection to aggregator", + o.logger.Error("❌ Failed to establish connection to aggregator", "aggregator_address", o.config.AggregatorServerIpPortAddress, "operator", o.config.OperatorAddress, "raw_error", pingErr) diff --git a/protobuf/avs.pb.go b/protobuf/avs.pb.go index f924a961..fae4af21 100644 --- a/protobuf/avs.pb.go +++ b/protobuf/avs.pb.go @@ -607,7 +607,9 @@ func (x *IdReq) GetId() string { type FixedTimeTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config as field so it is generated in Go - Config *FixedTimeTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *FixedTimeTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -649,11 +651,20 @@ func (x *FixedTimeTrigger) GetConfig() *FixedTimeTrigger_Config { return nil } +func (x *FixedTimeTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + // Simple timebase or cron syntax. type CronTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config as field so it is generated in Go - Config *CronTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *CronTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -695,10 +706,19 @@ func (x *CronTrigger) GetConfig() *CronTrigger_Config { return nil } +func (x *CronTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type BlockTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config as field so it is generated in Go - Config *BlockTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *BlockTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -740,13 +760,22 @@ func (x *BlockTrigger) GetConfig() *BlockTrigger_Config { return nil } +func (x *BlockTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + // EventTrigger monitors blockchain events using direct RPC filter queries. // Clients provide an array of ethereum.FilterQuery structures that map directly to RPC calls. // This approach eliminates parsing overhead and provides maximum flexibility and performance. type EventTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config as field so it is generated in Go - Config *EventTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *EventTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -788,10 +817,19 @@ func (x *EventTrigger) GetConfig() *EventTrigger_Config { return nil } +func (x *EventTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type ManualTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config as field so it is generated in Go - Config *ManualTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *ManualTrigger_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -833,6 +871,13 @@ func (x *ManualTrigger) GetConfig() *ManualTrigger_Config { return nil } +func (x *ManualTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type TaskTrigger struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -845,8 +890,10 @@ type TaskTrigger struct { // *TaskTrigger_Cron // *TaskTrigger_Block // *TaskTrigger_Event - TriggerType isTaskTrigger_TriggerType `protobuf_oneof:"trigger_type"` - Id string `protobuf:"bytes,7,opt,name=id,proto3" json:"id,omitempty"` + TriggerType isTaskTrigger_TriggerType `protobuf_oneof:"trigger_type"` + Id string `protobuf:"bytes,7,opt,name=id,proto3" json:"id,omitempty"` + // Input data provided by user for this trigger + Input *structpb.Value `protobuf:"bytes,9,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -954,6 +1001,13 @@ func (x *TaskTrigger) GetId() string { return "" } +func (x *TaskTrigger) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type isTaskTrigger_TriggerType interface { isTaskTrigger_TriggerType() } @@ -996,7 +1050,9 @@ func (*TaskTrigger_Event) isTaskTrigger_TriggerType() {} type ETHTransferNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *ETHTransferNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *ETHTransferNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1038,10 +1094,19 @@ func (x *ETHTransferNode) GetConfig() *ETHTransferNode_Config { return nil } +func (x *ETHTransferNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type ContractWriteNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *ContractWriteNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *ContractWriteNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1083,10 +1148,19 @@ func (x *ContractWriteNode) GetConfig() *ContractWriteNode_Config { return nil } +func (x *ContractWriteNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type ContractReadNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *ContractReadNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *ContractReadNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1128,10 +1202,19 @@ func (x *ContractReadNode) GetConfig() *ContractReadNode_Config { return nil } +func (x *ContractReadNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type GraphQLQueryNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *GraphQLQueryNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *GraphQLQueryNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1173,10 +1256,19 @@ func (x *GraphQLQueryNode) GetConfig() *GraphQLQueryNode_Config { return nil } +func (x *GraphQLQueryNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type RestAPINode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *RestAPINode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *RestAPINode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1218,10 +1310,19 @@ func (x *RestAPINode) GetConfig() *RestAPINode_Config { return nil } +func (x *RestAPINode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type CustomCodeNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *CustomCodeNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *CustomCodeNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1263,10 +1364,19 @@ func (x *CustomCodeNode) GetConfig() *CustomCodeNode_Config { return nil } +func (x *CustomCodeNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type BranchNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *BranchNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *BranchNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1308,10 +1418,19 @@ func (x *BranchNode) GetConfig() *BranchNode_Config { return nil } +func (x *BranchNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type FilterNode struct { state protoimpl.MessageState `protogen:"open.v1"` // Include Config and Input as fields so they are generated in Go - Config *FilterNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *FilterNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1353,6 +1472,13 @@ func (x *FilterNode) GetConfig() *FilterNode_Config { return nil } +func (x *FilterNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + // LoopNode currently not support, but we pre-defined to reverse the field id type LoopNode struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1368,7 +1494,9 @@ type LoopNode struct { // *LoopNode_CustomCode Runner isLoopNode_Runner `protobuf_oneof:"runner"` // Include Config and Input as fields so they are generated in Go - Config *LoopNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Config *LoopNode_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1471,6 +1599,13 @@ func (x *LoopNode) GetConfig() *LoopNode_Config { return nil } +func (x *LoopNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type isLoopNode_Runner interface { isLoopNode_Runner() } @@ -1597,7 +1732,9 @@ type TaskNode struct { // *TaskNode_Filter // *TaskNode_Loop // *TaskNode_CustomCode - TaskType isTaskNode_TaskType `protobuf_oneof:"task_type"` + TaskType isTaskNode_TaskType `protobuf_oneof:"task_type"` + // Input data provided by user for this node + Input *structpb.Value `protobuf:"bytes,19,opt,name=input,proto3" json:"input,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1741,6 +1878,13 @@ func (x *TaskNode) GetCustomCode() *CustomCodeNode { return nil } +func (x *TaskNode) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + type isTaskNode_TaskType interface { isTaskNode_TaskType() } @@ -4505,6 +4649,7 @@ type RunTriggerReq struct { state protoimpl.MessageState `protogen:"open.v1"` TriggerType TriggerType `protobuf:"varint,1,opt,name=trigger_type,json=triggerType,proto3,enum=aggregator.TriggerType" json:"trigger_type,omitempty"` // Type of trigger to execute using the TriggerType enum TriggerConfig map[string]*structpb.Value `protobuf:"bytes,2,rep,name=trigger_config,json=triggerConfig,proto3" json:"trigger_config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Configuration for the trigger + TriggerInput map[string]*structpb.Value `protobuf:"bytes,3,rep,name=trigger_input,json=triggerInput,proto3" json:"trigger_input,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Input data for the trigger unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4553,6 +4698,13 @@ func (x *RunTriggerReq) GetTriggerConfig() map[string]*structpb.Value { return nil } +func (x *RunTriggerReq) GetTriggerInput() map[string]*structpb.Value { + if x != nil { + return x.TriggerInput + } + return nil +} + // Response message for RunTrigger type RunTriggerResp struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7304,6 +7456,8 @@ type Execution_Step struct { Error string `protobuf:"bytes,13,opt,name=error,proto3" json:"error,omitempty"` Log string `protobuf:"bytes,12,opt,name=log,proto3" json:"log,omitempty"` Inputs []string `protobuf:"bytes,16,rep,name=inputs,proto3" json:"inputs,omitempty"` + // Input data that was set on this trigger/node + Input *structpb.Value `protobuf:"bytes,19,opt,name=input,proto3" json:"input,omitempty"` // Types that are valid to be assigned to OutputData: // // *Execution_Step_BlockTrigger @@ -7408,6 +7562,13 @@ func (x *Execution_Step) GetInputs() []string { return nil } +func (x *Execution_Step) GetInput() *structpb.Value { + if x != nil { + return x.Input + } + return nil +} + func (x *Execution_Step) GetOutputData() isExecution_Step_OutputData { if x != nil { return x.OutputData @@ -7664,7 +7825,7 @@ type Evm_Log struct { func (x *Evm_Log) Reset() { *x = Evm_Log{} - mi := &file_avs_proto_msgTypes[111] + mi := &file_avs_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7676,7 +7837,7 @@ func (x *Evm_Log) String() string { func (*Evm_Log) ProtoMessage() {} func (x *Evm_Log) ProtoReflect() protoreflect.Message { - mi := &file_avs_proto_msgTypes[111] + mi := &file_avs_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7782,7 +7943,7 @@ type Evm_TransactionReceipt struct { func (x *Evm_TransactionReceipt) Reset() { *x = Evm_TransactionReceipt{} - mi := &file_avs_proto_msgTypes[112] + mi := &file_avs_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7794,7 +7955,7 @@ func (x *Evm_TransactionReceipt) String() string { func (*Evm_TransactionReceipt) ProtoMessage() {} func (x *Evm_TransactionReceipt) ProtoReflect() protoreflect.Message { - mi := &file_avs_proto_msgTypes[112] + mi := &file_avs_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7955,7 +8116,7 @@ type Evm_UserOp struct { func (x *Evm_UserOp) Reset() { *x = Evm_UserOp{} - mi := &file_avs_proto_msgTypes[113] + mi := &file_avs_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7967,7 +8128,7 @@ func (x *Evm_UserOp) String() string { func (*Evm_UserOp) ProtoMessage() {} func (x *Evm_UserOp) ProtoReflect() protoreflect.Message { - mi := &file_avs_proto_msgTypes[113] + mi := &file_avs_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8078,23 +8239,26 @@ const file_avs_proto_rawDesc = "" + "\x05found\x18\x02 \x01(\bR\x05found\x12\x16\n" + "\x06source\x18\x03 \x01(\tR\x06source\"\x17\n" + "\x05IdReq\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\"\xbe\x01\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\xec\x01\n" + "\x10FixedTimeTrigger\x12;\n" + - "\x06config\x18\x01 \x01(\v2#.aggregator.FixedTimeTrigger.ConfigR\x06config\x1a \n" + + "\x06config\x18\x01 \x01(\v2#.aggregator.FixedTimeTrigger.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a \n" + "\x06Config\x12\x16\n" + "\x06epochs\x18\x01 \x03(\x03R\x06epochs\x1aK\n" + "\x06Output\x12\x1c\n" + "\ttimestamp\x18\x01 \x01(\x04R\ttimestamp\x12#\n" + - "\rtimestamp_iso\x18\x02 \x01(\tR\ftimestampIso\"\xba\x01\n" + + "\rtimestamp_iso\x18\x02 \x01(\tR\ftimestampIso\"\xe8\x01\n" + "\vCronTrigger\x126\n" + - "\x06config\x18\x01 \x01(\v2\x1e.aggregator.CronTrigger.ConfigR\x06config\x1a&\n" + + "\x06config\x18\x01 \x01(\v2\x1e.aggregator.CronTrigger.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a&\n" + "\x06Config\x12\x1c\n" + "\tschedules\x18\x01 \x03(\tR\tschedules\x1aK\n" + "\x06Output\x12\x1c\n" + "\ttimestamp\x18\x01 \x01(\x04R\ttimestamp\x12#\n" + - "\rtimestamp_iso\x18\x02 \x01(\tR\ftimestampIso\"\xd1\x02\n" + + "\rtimestamp_iso\x18\x02 \x01(\tR\ftimestampIso\"\xff\x02\n" + "\fBlockTrigger\x127\n" + - "\x06config\x18\x01 \x01(\v2\x1f.aggregator.BlockTrigger.ConfigR\x06config\x1a$\n" + + "\x06config\x18\x01 \x01(\v2\x1f.aggregator.BlockTrigger.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a$\n" + "\x06Config\x12\x1a\n" + "\binterval\x18\x01 \x01(\x03R\binterval\x1a\xe1\x01\n" + "\x06Output\x12!\n" + @@ -8108,9 +8272,10 @@ const file_avs_proto_rawDesc = "" + "difficulty\x18\x05 \x01(\tR\n" + "difficulty\x12\x1b\n" + "\tgas_limit\x18\x06 \x01(\x04R\bgasLimit\x12\x19\n" + - "\bgas_used\x18\a \x01(\x04R\agasUsed\"\xd3\a\n" + + "\bgas_used\x18\a \x01(\x04R\agasUsed\"\x81\b\n" + "\fEventTrigger\x127\n" + - "\x06config\x18\x01 \x01(\v2\x1f.aggregator.EventTrigger.ConfigR\x06config\x1a\xad\x01\n" + + "\x06config\x18\x01 \x01(\v2\x1f.aggregator.EventTrigger.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a\xad\x01\n" + "\x05Query\x12\x1c\n" + "\taddresses\x18\x01 \x03(\tR\taddresses\x127\n" + "\x06topics\x18\x02 \x03(\v2\x1f.aggregator.EventTrigger.TopicsR\x06topics\x124\n" + @@ -8140,12 +8305,13 @@ const file_avs_proto_rawDesc = "" + " \x01(\tR\x05value\x12'\n" + "\x0fvalue_formatted\x18\v \x01(\tR\x0evalueFormatted\x12+\n" + "\x11transaction_index\x18\f \x01(\rR\x10transactionIndex\x12\x1b\n" + - "\tlog_index\x18\r \x01(\rR\blogIndex\"t\n" + + "\tlog_index\x18\r \x01(\rR\blogIndex\"\xa2\x01\n" + "\rManualTrigger\x128\n" + - "\x06config\x18\x01 \x01(\v2 .aggregator.ManualTrigger.ConfigR\x06config\x1a\b\n" + + "\x06config\x18\x01 \x01(\v2 .aggregator.ManualTrigger.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a\b\n" + "\x06Config\x1a\x1f\n" + "\x06Output\x12\x15\n" + - "\x06run_at\x18\x01 \x01(\x04R\x05runAt\"\xda\x02\n" + + "\x06run_at\x18\x01 \x01(\x04R\x05runAt\"\x88\x03\n" + "\vTaskTrigger\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12+\n" + "\x04type\x18\b \x01(\x0e2\x17.aggregator.TriggerTypeR\x04type\x12\x18\n" + @@ -8155,17 +8321,20 @@ const file_avs_proto_rawDesc = "" + "\x04cron\x18\x04 \x01(\v2\x17.aggregator.CronTriggerH\x00R\x04cron\x120\n" + "\x05block\x18\x05 \x01(\v2\x18.aggregator.BlockTriggerH\x00R\x05block\x120\n" + "\x05event\x18\x06 \x01(\v2\x18.aggregator.EventTriggerH\x00R\x05event\x12\x0e\n" + - "\x02id\x18\a \x01(\tR\x02idB\x0e\n" + - "\ftrigger_type\"\xc6\x01\n" + + "\x02id\x18\a \x01(\tR\x02id\x12,\n" + + "\x05input\x18\t \x01(\v2\x16.google.protobuf.ValueR\x05inputB\x0e\n" + + "\ftrigger_type\"\xf4\x01\n" + "\x0fETHTransferNode\x12:\n" + - "\x06config\x18\x01 \x01(\v2\".aggregator.ETHTransferNode.ConfigR\x06config\x1aB\n" + + "\x06config\x18\x01 \x01(\v2\".aggregator.ETHTransferNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1aB\n" + "\x06Config\x12 \n" + "\vdestination\x18\x01 \x01(\tR\vdestination\x12\x16\n" + "\x06amount\x18\x02 \x01(\tR\x06amount\x1a3\n" + "\x06Output\x12)\n" + - "\x10transaction_hash\x18\x01 \x01(\tR\x0ftransactionHash\"\xaa\r\n" + + "\x10transaction_hash\x18\x01 \x01(\tR\x0ftransactionHash\"\xd8\r\n" + "\x11ContractWriteNode\x12<\n" + - "\x06config\x18\x01 \x01(\v2$.aggregator.ContractWriteNode.ConfigR\x06config\x1a\xc0\x01\n" + + "\x06config\x18\x01 \x01(\v2$.aggregator.ContractWriteNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a\xc0\x01\n" + "\x06Config\x12)\n" + "\x10contract_address\x18\x01 \x01(\tR\x0fcontractAddress\x12\x1b\n" + "\tcall_data\x18\x02 \x01(\tR\bcallData\x12!\n" + @@ -8225,9 +8394,10 @@ const file_avs_proto_rawDesc = "" + "ReturnData\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x14\n" + - "\x05value\x18\x03 \x01(\tR\x05value\"\x91\x05\n" + + "\x05value\x18\x03 \x01(\tR\x05value\"\xbf\x05\n" + "\x10ContractReadNode\x12;\n" + - "\x06config\x18\x01 \x01(\v2#.aggregator.ContractReadNode.ConfigR\x06config\x1aJ\n" + + "\x06config\x18\x01 \x01(\v2#.aggregator.ContractReadNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1aJ\n" + "\n" + "MethodCall\x12\x1b\n" + "\tcall_data\x18\x01 \x01(\tR\bcallData\x12\x1f\n" + @@ -8248,9 +8418,10 @@ const file_avs_proto_rawDesc = "" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x14\n" + "\x05value\x18\x03 \x01(\tR\x05value\x1aM\n" + "\x06Output\x12C\n" + - "\aresults\x18\x01 \x03(\v2).aggregator.ContractReadNode.MethodResultR\aresults\"\xc6\x02\n" + + "\aresults\x18\x01 \x03(\v2).aggregator.ContractReadNode.MethodResultR\aresults\"\xf4\x02\n" + "\x10GraphQLQueryNode\x12;\n" + - "\x06config\x18\x01 \x01(\v2#.aggregator.GraphQLQueryNode.ConfigR\x06config\x1a\xc0\x01\n" + + "\x06config\x18\x01 \x01(\v2#.aggregator.GraphQLQueryNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a\xc0\x01\n" + "\x06Config\x12\x10\n" + "\x03url\x18\x01 \x01(\tR\x03url\x12\x14\n" + "\x05query\x18\x02 \x01(\tR\x05query\x12P\n" + @@ -8259,9 +8430,10 @@ const file_avs_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a2\n" + "\x06Output\x12(\n" + - "\x04data\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\x04data\"\xc7\x02\n" + + "\x04data\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\x04data\"\xf5\x02\n" + "\vRestAPINode\x126\n" + - "\x06config\x18\x01 \x01(\v2\x1e.aggregator.RestAPINode.ConfigR\x06config\x1a\xc9\x01\n" + + "\x06config\x18\x01 \x01(\v2\x1e.aggregator.RestAPINode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a\xc9\x01\n" + "\x06Config\x12\x10\n" + "\x03url\x18\x01 \x01(\tR\x03url\x12E\n" + "\aheaders\x18\x02 \x03(\v2+.aggregator.RestAPINode.Config.HeadersEntryR\aheaders\x12\x12\n" + @@ -8271,17 +8443,19 @@ const file_avs_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a4\n" + "\x06Output\x12*\n" + - "\x04data\x18\x01 \x01(\v2\x16.google.protobuf.ValueR\x04data\"\xc9\x01\n" + + "\x04data\x18\x01 \x01(\v2\x16.google.protobuf.ValueR\x04data\"\xf7\x01\n" + "\x0eCustomCodeNode\x129\n" + - "\x06config\x18\x01 \x01(\v2!.aggregator.CustomCodeNode.ConfigR\x06config\x1aF\n" + + "\x06config\x18\x01 \x01(\v2!.aggregator.CustomCodeNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1aF\n" + "\x06Config\x12$\n" + "\x04lang\x18\x01 \x01(\x0e2\x10.aggregator.LangR\x04lang\x12\x16\n" + "\x06source\x18\x02 \x01(\tR\x06source\x1a4\n" + "\x06Output\x12*\n" + - "\x04data\x18\x01 \x01(\v2\x16.google.protobuf.ValueR\x04data\"\x8d\x02\n" + + "\x04data\x18\x01 \x01(\v2\x16.google.protobuf.ValueR\x04data\"\xbb\x02\n" + "\n" + "BranchNode\x125\n" + - "\x06config\x18\x01 \x01(\v2\x1d.aggregator.BranchNode.ConfigR\x06config\x1aO\n" + + "\x06config\x18\x01 \x01(\v2\x1d.aggregator.BranchNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1aO\n" + "\tCondition\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1e\n" + @@ -8293,17 +8467,18 @@ const file_avs_proto_rawDesc = "" + "conditions\x18\x01 \x03(\v2 .aggregator.BranchNode.ConditionR\n" + "conditions\x1a+\n" + "\x06Output\x12!\n" + - "\fcondition_id\x18\x01 \x01(\tR\vconditionId\"\xbe\x01\n" + + "\fcondition_id\x18\x01 \x01(\tR\vconditionId\"\xec\x01\n" + "\n" + "FilterNode\x125\n" + - "\x06config\x18\x01 \x01(\v2\x1d.aggregator.FilterNode.ConfigR\x06config\x1aE\n" + + "\x06config\x18\x01 \x01(\v2\x1d.aggregator.FilterNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1aE\n" + "\x06Config\x12\x1e\n" + "\n" + "expression\x18\x01 \x01(\tR\n" + "expression\x12\x1b\n" + "\tsource_id\x18\x02 \x01(\tR\bsourceId\x1a2\n" + "\x06Output\x12(\n" + - "\x04data\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\x04data\"\xd6\x04\n" + + "\x04data\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\x04data\"\x84\x05\n" + "\bLoopNode\x12@\n" + "\feth_transfer\x18\n" + " \x01(\v2\x1b.aggregator.ETHTransferNodeH\x00R\vethTransfer\x12F\n" + @@ -8313,7 +8488,8 @@ const file_avs_proto_rawDesc = "" + "\brest_api\x18\x0e \x01(\v2\x17.aggregator.RestAPINodeH\x00R\arestApi\x12=\n" + "\vcustom_code\x18\x0f \x01(\v2\x1a.aggregator.CustomCodeNodeH\x00R\n" + "customCode\x123\n" + - "\x06config\x18\x01 \x01(\v2\x1b.aggregator.LoopNode.ConfigR\x06config\x1a[\n" + + "\x06config\x18\x01 \x01(\v2\x1b.aggregator.LoopNode.ConfigR\x06config\x12,\n" + + "\x05input\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05input\x1a[\n" + "\x06Config\x12\x1b\n" + "\tsource_id\x18\x01 \x01(\tR\bsourceId\x12\x19\n" + "\biter_val\x18\x02 \x01(\tR\aiterVal\x12\x19\n" + @@ -8324,7 +8500,7 @@ const file_avs_proto_rawDesc = "" + "\bTaskEdge\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + "\x06source\x18\x02 \x01(\tR\x06source\x12\x16\n" + - "\x06target\x18\x03 \x01(\tR\x06target\"\xfe\x04\n" + + "\x06target\x18\x03 \x01(\tR\x06target\"\xac\x05\n" + "\bTaskNode\x12\x0e\n" + "\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" + "\x04name\x18\x03 \x01(\tR\x04name\x12(\n" + @@ -8339,8 +8515,9 @@ const file_avs_proto_rawDesc = "" + "\x06filter\x18\x10 \x01(\v2\x16.aggregator.FilterNodeH\x00R\x06filter\x12*\n" + "\x04loop\x18\x11 \x01(\v2\x14.aggregator.LoopNodeH\x00R\x04loop\x12=\n" + "\vcustom_code\x18\x12 \x01(\v2\x1a.aggregator.CustomCodeNodeH\x00R\n" + - "customCodeB\v\n" + - "\ttask_type\"\xcd\n" + + "customCode\x12,\n" + + "\x05input\x18\x13 \x01(\v2\x16.google.protobuf.ValueR\x05inputB\v\n" + + "\ttask_type\"\xfb\n" + "\n" + "\tExecution\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x19\n" + @@ -8348,7 +8525,7 @@ const file_avs_proto_rawDesc = "" + "\x06end_at\x18\x03 \x01(\x03R\x05endAt\x12\x18\n" + "\asuccess\x18\x04 \x01(\bR\asuccess\x12\x14\n" + "\x05error\x18\x05 \x01(\tR\x05error\x120\n" + - "\x05steps\x18\b \x03(\v2\x1a.aggregator.Execution.StepR\x05steps\x1a\x9b\t\n" + + "\x05steps\x18\b \x03(\v2\x1a.aggregator.Execution.StepR\x05steps\x1a\xc9\t\n" + "\x04Step\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x04type\x18\x11 \x01(\tR\x04type\x12\x12\n" + @@ -8356,7 +8533,8 @@ const file_avs_proto_rawDesc = "" + "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" + "\x05error\x18\r \x01(\tR\x05error\x12\x10\n" + "\x03log\x18\f \x01(\tR\x03log\x12\x16\n" + - "\x06inputs\x18\x10 \x03(\tR\x06inputs\x12F\n" + + "\x06inputs\x18\x10 \x03(\tR\x06inputs\x12,\n" + + "\x05input\x18\x13 \x01(\v2\x16.google.protobuf.ValueR\x05input\x12F\n" + "\rblock_trigger\x18\x14 \x01(\v2\x1f.aggregator.BlockTrigger.OutputH\x00R\fblockTrigger\x12S\n" + "\x12fixed_time_trigger\x18\x15 \x01(\v2#.aggregator.FixedTimeTrigger.OutputH\x00R\x10fixedTimeTrigger\x12C\n" + "\fcron_trigger\x18\x16 \x01(\v2\x1e.aggregator.CronTrigger.OutputH\x00R\vcronTrigger\x12F\n" + @@ -8569,12 +8747,16 @@ const file_avs_proto_rawDesc = "" + "\x06branch\x18\x10 \x01(\v2\x1d.aggregator.BranchNode.OutputH\x00R\x06branch\x127\n" + "\x06filter\x18\x11 \x01(\v2\x1d.aggregator.FilterNode.OutputH\x00R\x06filter\x121\n" + "\x04loop\x18\x12 \x01(\v2\x1b.aggregator.LoopNode.OutputH\x00R\x04loopB\r\n" + - "\voutput_data\"\xfa\x01\n" + + "\voutput_data\"\xa5\x03\n" + "\rRunTriggerReq\x12:\n" + "\ftrigger_type\x18\x01 \x01(\x0e2\x17.aggregator.TriggerTypeR\vtriggerType\x12S\n" + - "\x0etrigger_config\x18\x02 \x03(\v2,.aggregator.RunTriggerReq.TriggerConfigEntryR\rtriggerConfig\x1aX\n" + + "\x0etrigger_config\x18\x02 \x03(\v2,.aggregator.RunTriggerReq.TriggerConfigEntryR\rtriggerConfig\x12P\n" + + "\rtrigger_input\x18\x03 \x03(\v2+.aggregator.RunTriggerReq.TriggerInputEntryR\ftriggerInput\x1aX\n" + "\x12TriggerConfigEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + + "\x05value\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05value:\x028\x01\x1aW\n" + + "\x11TriggerInputEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + "\x05value\x18\x02 \x01(\v2\x16.google.protobuf.ValueR\x05value:\x028\x01\"\xe3\x03\n" + "\x0eRunTriggerResp\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x14\n" + @@ -8735,7 +8917,7 @@ func file_avs_proto_rawDescGZIP() []byte { } var file_avs_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_avs_proto_msgTypes = make([]protoimpl.MessageInfo, 115) +var file_avs_proto_msgTypes = make([]protoimpl.MessageInfo, 116) var file_avs_proto_goTypes = []any{ (TriggerType)(0), // 0: aggregator.TriggerType (NodeType)(0), // 1: aggregator.NodeType @@ -8854,194 +9036,214 @@ var file_avs_proto_goTypes = []any{ nil, // 114: aggregator.RunNodeWithInputsReq.NodeConfigEntry nil, // 115: aggregator.RunNodeWithInputsReq.InputVariablesEntry nil, // 116: aggregator.RunTriggerReq.TriggerConfigEntry - (*Evm_Log)(nil), // 117: aggregator.Evm.Log - (*Evm_TransactionReceipt)(nil), // 118: aggregator.Evm.TransactionReceipt - (*Evm_UserOp)(nil), // 119: aggregator.Evm.UserOp - nil, // 120: aggregator.SimulateTaskReq.InputVariablesEntry - (*anypb.Any)(nil), // 121: google.protobuf.Any + nil, // 117: aggregator.RunTriggerReq.TriggerInputEntry + (*Evm_Log)(nil), // 118: aggregator.Evm.Log + (*Evm_TransactionReceipt)(nil), // 119: aggregator.Evm.TransactionReceipt + (*Evm_UserOp)(nil), // 120: aggregator.Evm.UserOp + nil, // 121: aggregator.SimulateTaskReq.InputVariablesEntry (*structpb.Value)(nil), // 122: google.protobuf.Value - (*wrapperspb.BoolValue)(nil), // 123: google.protobuf.BoolValue + (*anypb.Any)(nil), // 123: google.protobuf.Any + (*wrapperspb.BoolValue)(nil), // 124: google.protobuf.BoolValue } var file_avs_proto_depIdxs = []int32{ 6, // 0: aggregator.GetTokenMetadataResp.token:type_name -> aggregator.TokenMetadata 69, // 1: aggregator.FixedTimeTrigger.config:type_name -> aggregator.FixedTimeTrigger.Config - 71, // 2: aggregator.CronTrigger.config:type_name -> aggregator.CronTrigger.Config - 73, // 3: aggregator.BlockTrigger.config:type_name -> aggregator.BlockTrigger.Config - 77, // 4: aggregator.EventTrigger.config:type_name -> aggregator.EventTrigger.Config - 80, // 5: aggregator.ManualTrigger.config:type_name -> aggregator.ManualTrigger.Config - 0, // 6: aggregator.TaskTrigger.type:type_name -> aggregator.TriggerType - 10, // 7: aggregator.TaskTrigger.fixed_time:type_name -> aggregator.FixedTimeTrigger - 11, // 8: aggregator.TaskTrigger.cron:type_name -> aggregator.CronTrigger - 12, // 9: aggregator.TaskTrigger.block:type_name -> aggregator.BlockTrigger - 13, // 10: aggregator.TaskTrigger.event:type_name -> aggregator.EventTrigger - 82, // 11: aggregator.ETHTransferNode.config:type_name -> aggregator.ETHTransferNode.Config - 84, // 12: aggregator.ContractWriteNode.config:type_name -> aggregator.ContractWriteNode.Config - 94, // 13: aggregator.ContractReadNode.config:type_name -> aggregator.ContractReadNode.Config - 98, // 14: aggregator.GraphQLQueryNode.config:type_name -> aggregator.GraphQLQueryNode.Config - 101, // 15: aggregator.RestAPINode.config:type_name -> aggregator.RestAPINode.Config - 104, // 16: aggregator.CustomCodeNode.config:type_name -> aggregator.CustomCodeNode.Config - 107, // 17: aggregator.BranchNode.config:type_name -> aggregator.BranchNode.Config - 109, // 18: aggregator.FilterNode.config:type_name -> aggregator.FilterNode.Config - 16, // 19: aggregator.LoopNode.eth_transfer:type_name -> aggregator.ETHTransferNode - 17, // 20: aggregator.LoopNode.contract_write:type_name -> aggregator.ContractWriteNode - 18, // 21: aggregator.LoopNode.contract_read:type_name -> aggregator.ContractReadNode - 19, // 22: aggregator.LoopNode.graphql_data_query:type_name -> aggregator.GraphQLQueryNode - 20, // 23: aggregator.LoopNode.rest_api:type_name -> aggregator.RestAPINode - 21, // 24: aggregator.LoopNode.custom_code:type_name -> aggregator.CustomCodeNode - 111, // 25: aggregator.LoopNode.config:type_name -> aggregator.LoopNode.Config - 1, // 26: aggregator.TaskNode.type:type_name -> aggregator.NodeType - 16, // 27: aggregator.TaskNode.eth_transfer:type_name -> aggregator.ETHTransferNode - 17, // 28: aggregator.TaskNode.contract_write:type_name -> aggregator.ContractWriteNode - 18, // 29: aggregator.TaskNode.contract_read:type_name -> aggregator.ContractReadNode - 19, // 30: aggregator.TaskNode.graphql_query:type_name -> aggregator.GraphQLQueryNode - 20, // 31: aggregator.TaskNode.rest_api:type_name -> aggregator.RestAPINode - 22, // 32: aggregator.TaskNode.branch:type_name -> aggregator.BranchNode - 23, // 33: aggregator.TaskNode.filter:type_name -> aggregator.FilterNode - 24, // 34: aggregator.TaskNode.loop:type_name -> aggregator.LoopNode - 21, // 35: aggregator.TaskNode.custom_code:type_name -> aggregator.CustomCodeNode - 113, // 36: aggregator.Execution.steps:type_name -> aggregator.Execution.Step - 4, // 37: aggregator.Task.status:type_name -> aggregator.TaskStatus - 15, // 38: aggregator.Task.trigger:type_name -> aggregator.TaskTrigger - 26, // 39: aggregator.Task.nodes:type_name -> aggregator.TaskNode - 25, // 40: aggregator.Task.edges:type_name -> aggregator.TaskEdge - 15, // 41: aggregator.CreateTaskReq.trigger:type_name -> aggregator.TaskTrigger - 26, // 42: aggregator.CreateTaskReq.nodes:type_name -> aggregator.TaskNode - 25, // 43: aggregator.CreateTaskReq.edges:type_name -> aggregator.TaskEdge - 34, // 44: aggregator.ListWalletResp.items:type_name -> aggregator.SmartWallet - 28, // 45: aggregator.ListTasksResp.items:type_name -> aggregator.Task - 51, // 46: aggregator.ListTasksResp.page_info:type_name -> aggregator.PageInfo - 27, // 47: aggregator.ListExecutionsResp.items:type_name -> aggregator.Execution - 51, // 48: aggregator.ListExecutionsResp.page_info:type_name -> aggregator.PageInfo - 5, // 49: aggregator.ExecutionStatusResp.status:type_name -> aggregator.ExecutionStatus - 0, // 50: aggregator.TriggerTaskReq.trigger_type:type_name -> aggregator.TriggerType - 74, // 51: aggregator.TriggerTaskReq.block_trigger:type_name -> aggregator.BlockTrigger.Output - 70, // 52: aggregator.TriggerTaskReq.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output - 72, // 53: aggregator.TriggerTaskReq.cron_trigger:type_name -> aggregator.CronTrigger.Output - 78, // 54: aggregator.TriggerTaskReq.event_trigger:type_name -> aggregator.EventTrigger.Output - 81, // 55: aggregator.TriggerTaskReq.manual_trigger:type_name -> aggregator.ManualTrigger.Output - 5, // 56: aggregator.TriggerTaskResp.status:type_name -> aggregator.ExecutionStatus - 52, // 57: aggregator.ListSecretsResp.items:type_name -> aggregator.Secret - 51, // 58: aggregator.ListSecretsResp.page_info:type_name -> aggregator.PageInfo - 1, // 59: aggregator.RunNodeWithInputsReq.node_type:type_name -> aggregator.NodeType - 114, // 60: aggregator.RunNodeWithInputsReq.node_config:type_name -> aggregator.RunNodeWithInputsReq.NodeConfigEntry - 115, // 61: aggregator.RunNodeWithInputsReq.input_variables:type_name -> aggregator.RunNodeWithInputsReq.InputVariablesEntry - 83, // 62: aggregator.RunNodeWithInputsResp.eth_transfer:type_name -> aggregator.ETHTransferNode.Output - 99, // 63: aggregator.RunNodeWithInputsResp.graphql:type_name -> aggregator.GraphQLQueryNode.Output - 96, // 64: aggregator.RunNodeWithInputsResp.contract_read:type_name -> aggregator.ContractReadNode.Output - 86, // 65: aggregator.RunNodeWithInputsResp.contract_write:type_name -> aggregator.ContractWriteNode.Output - 105, // 66: aggregator.RunNodeWithInputsResp.custom_code:type_name -> aggregator.CustomCodeNode.Output - 102, // 67: aggregator.RunNodeWithInputsResp.rest_api:type_name -> aggregator.RestAPINode.Output - 108, // 68: aggregator.RunNodeWithInputsResp.branch:type_name -> aggregator.BranchNode.Output - 110, // 69: aggregator.RunNodeWithInputsResp.filter:type_name -> aggregator.FilterNode.Output - 112, // 70: aggregator.RunNodeWithInputsResp.loop:type_name -> aggregator.LoopNode.Output - 0, // 71: aggregator.RunTriggerReq.trigger_type:type_name -> aggregator.TriggerType - 116, // 72: aggregator.RunTriggerReq.trigger_config:type_name -> aggregator.RunTriggerReq.TriggerConfigEntry - 74, // 73: aggregator.RunTriggerResp.block_trigger:type_name -> aggregator.BlockTrigger.Output - 70, // 74: aggregator.RunTriggerResp.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output - 72, // 75: aggregator.RunTriggerResp.cron_trigger:type_name -> aggregator.CronTrigger.Output - 78, // 76: aggregator.RunTriggerResp.event_trigger:type_name -> aggregator.EventTrigger.Output - 81, // 77: aggregator.RunTriggerResp.manual_trigger:type_name -> aggregator.ManualTrigger.Output - 15, // 78: aggregator.SimulateTaskReq.trigger:type_name -> aggregator.TaskTrigger - 26, // 79: aggregator.SimulateTaskReq.nodes:type_name -> aggregator.TaskNode - 25, // 80: aggregator.SimulateTaskReq.edges:type_name -> aggregator.TaskEdge - 120, // 81: aggregator.SimulateTaskReq.input_variables:type_name -> aggregator.SimulateTaskReq.InputVariablesEntry - 76, // 82: aggregator.EventTrigger.Query.topics:type_name -> aggregator.EventTrigger.Topics - 75, // 83: aggregator.EventTrigger.Config.queries:type_name -> aggregator.EventTrigger.Query - 117, // 84: aggregator.EventTrigger.Output.evm_log:type_name -> aggregator.Evm.Log - 79, // 85: aggregator.EventTrigger.Output.transfer_log:type_name -> aggregator.EventTrigger.TransferLogOutput - 85, // 86: aggregator.ContractWriteNode.Config.method_calls:type_name -> aggregator.ContractWriteNode.MethodCall - 87, // 87: aggregator.ContractWriteNode.Output.results:type_name -> aggregator.ContractWriteNode.MethodResult - 88, // 88: aggregator.ContractWriteNode.MethodResult.transaction:type_name -> aggregator.ContractWriteNode.TransactionData - 89, // 89: aggregator.ContractWriteNode.MethodResult.events:type_name -> aggregator.ContractWriteNode.EventData - 90, // 90: aggregator.ContractWriteNode.MethodResult.error:type_name -> aggregator.ContractWriteNode.ErrorData - 91, // 91: aggregator.ContractWriteNode.MethodResult.return_data:type_name -> aggregator.ContractWriteNode.ReturnData - 92, // 92: aggregator.ContractWriteNode.EventData.decoded:type_name -> aggregator.ContractWriteNode.EventData.DecodedEntry - 93, // 93: aggregator.ContractReadNode.Config.method_calls:type_name -> aggregator.ContractReadNode.MethodCall - 97, // 94: aggregator.ContractReadNode.MethodResult.data:type_name -> aggregator.ContractReadNode.MethodResult.StructuredField - 95, // 95: aggregator.ContractReadNode.Output.results:type_name -> aggregator.ContractReadNode.MethodResult - 100, // 96: aggregator.GraphQLQueryNode.Config.variables:type_name -> aggregator.GraphQLQueryNode.Config.VariablesEntry - 121, // 97: aggregator.GraphQLQueryNode.Output.data:type_name -> google.protobuf.Any - 103, // 98: aggregator.RestAPINode.Config.headers:type_name -> aggregator.RestAPINode.Config.HeadersEntry - 122, // 99: aggregator.RestAPINode.Output.data:type_name -> google.protobuf.Value - 2, // 100: aggregator.CustomCodeNode.Config.lang:type_name -> aggregator.Lang - 122, // 101: aggregator.CustomCodeNode.Output.data:type_name -> google.protobuf.Value - 106, // 102: aggregator.BranchNode.Config.conditions:type_name -> aggregator.BranchNode.Condition - 121, // 103: aggregator.FilterNode.Output.data:type_name -> google.protobuf.Any - 74, // 104: aggregator.Execution.Step.block_trigger:type_name -> aggregator.BlockTrigger.Output - 70, // 105: aggregator.Execution.Step.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output - 72, // 106: aggregator.Execution.Step.cron_trigger:type_name -> aggregator.CronTrigger.Output - 78, // 107: aggregator.Execution.Step.event_trigger:type_name -> aggregator.EventTrigger.Output - 81, // 108: aggregator.Execution.Step.manual_trigger:type_name -> aggregator.ManualTrigger.Output - 83, // 109: aggregator.Execution.Step.eth_transfer:type_name -> aggregator.ETHTransferNode.Output - 99, // 110: aggregator.Execution.Step.graphql:type_name -> aggregator.GraphQLQueryNode.Output - 96, // 111: aggregator.Execution.Step.contract_read:type_name -> aggregator.ContractReadNode.Output - 86, // 112: aggregator.Execution.Step.contract_write:type_name -> aggregator.ContractWriteNode.Output - 105, // 113: aggregator.Execution.Step.custom_code:type_name -> aggregator.CustomCodeNode.Output - 102, // 114: aggregator.Execution.Step.rest_api:type_name -> aggregator.RestAPINode.Output - 108, // 115: aggregator.Execution.Step.branch:type_name -> aggregator.BranchNode.Output - 110, // 116: aggregator.Execution.Step.filter:type_name -> aggregator.FilterNode.Output - 112, // 117: aggregator.Execution.Step.loop:type_name -> aggregator.LoopNode.Output - 122, // 118: aggregator.RunNodeWithInputsReq.NodeConfigEntry.value:type_name -> google.protobuf.Value - 122, // 119: aggregator.RunNodeWithInputsReq.InputVariablesEntry.value:type_name -> google.protobuf.Value - 122, // 120: aggregator.RunTriggerReq.TriggerConfigEntry.value:type_name -> google.protobuf.Value - 122, // 121: aggregator.SimulateTaskReq.InputVariablesEntry.value:type_name -> google.protobuf.Value - 42, // 122: aggregator.Aggregator.GetKey:input_type -> aggregator.GetKeyReq - 55, // 123: aggregator.Aggregator.GetSignatureFormat:input_type -> aggregator.GetSignatureFormatReq - 31, // 124: aggregator.Aggregator.GetNonce:input_type -> aggregator.NonceRequest - 44, // 125: aggregator.Aggregator.GetWallet:input_type -> aggregator.GetWalletReq - 46, // 126: aggregator.Aggregator.SetWallet:input_type -> aggregator.SetWalletReq - 33, // 127: aggregator.Aggregator.ListWallets:input_type -> aggregator.ListWalletReq - 29, // 128: aggregator.Aggregator.CreateTask:input_type -> aggregator.CreateTaskReq - 36, // 129: aggregator.Aggregator.ListTasks:input_type -> aggregator.ListTasksReq - 9, // 130: aggregator.Aggregator.GetTask:input_type -> aggregator.IdReq - 38, // 131: aggregator.Aggregator.ListExecutions:input_type -> aggregator.ListExecutionsReq - 40, // 132: aggregator.Aggregator.GetExecution:input_type -> aggregator.ExecutionReq - 40, // 133: aggregator.Aggregator.GetExecutionStatus:input_type -> aggregator.ExecutionReq - 9, // 134: aggregator.Aggregator.CancelTask:input_type -> aggregator.IdReq - 9, // 135: aggregator.Aggregator.DeleteTask:input_type -> aggregator.IdReq - 47, // 136: aggregator.Aggregator.TriggerTask:input_type -> aggregator.TriggerTaskReq - 49, // 137: aggregator.Aggregator.CreateSecret:input_type -> aggregator.CreateOrUpdateSecretReq - 54, // 138: aggregator.Aggregator.DeleteSecret:input_type -> aggregator.DeleteSecretReq - 50, // 139: aggregator.Aggregator.ListSecrets:input_type -> aggregator.ListSecretsReq - 49, // 140: aggregator.Aggregator.UpdateSecret:input_type -> aggregator.CreateOrUpdateSecretReq - 57, // 141: aggregator.Aggregator.GetWorkflowCount:input_type -> aggregator.GetWorkflowCountReq - 59, // 142: aggregator.Aggregator.GetExecutionCount:input_type -> aggregator.GetExecutionCountReq - 61, // 143: aggregator.Aggregator.GetExecutionStats:input_type -> aggregator.GetExecutionStatsReq - 63, // 144: aggregator.Aggregator.RunNodeWithInputs:input_type -> aggregator.RunNodeWithInputsReq - 65, // 145: aggregator.Aggregator.RunTrigger:input_type -> aggregator.RunTriggerReq - 68, // 146: aggregator.Aggregator.SimulateTask:input_type -> aggregator.SimulateTaskReq - 7, // 147: aggregator.Aggregator.GetTokenMetadata:input_type -> aggregator.GetTokenMetadataReq - 43, // 148: aggregator.Aggregator.GetKey:output_type -> aggregator.KeyResp - 56, // 149: aggregator.Aggregator.GetSignatureFormat:output_type -> aggregator.GetSignatureFormatResp - 32, // 150: aggregator.Aggregator.GetNonce:output_type -> aggregator.NonceResp - 45, // 151: aggregator.Aggregator.GetWallet:output_type -> aggregator.GetWalletResp - 45, // 152: aggregator.Aggregator.SetWallet:output_type -> aggregator.GetWalletResp - 35, // 153: aggregator.Aggregator.ListWallets:output_type -> aggregator.ListWalletResp - 30, // 154: aggregator.Aggregator.CreateTask:output_type -> aggregator.CreateTaskResp - 37, // 155: aggregator.Aggregator.ListTasks:output_type -> aggregator.ListTasksResp - 28, // 156: aggregator.Aggregator.GetTask:output_type -> aggregator.Task - 39, // 157: aggregator.Aggregator.ListExecutions:output_type -> aggregator.ListExecutionsResp - 27, // 158: aggregator.Aggregator.GetExecution:output_type -> aggregator.Execution - 41, // 159: aggregator.Aggregator.GetExecutionStatus:output_type -> aggregator.ExecutionStatusResp - 123, // 160: aggregator.Aggregator.CancelTask:output_type -> google.protobuf.BoolValue - 123, // 161: aggregator.Aggregator.DeleteTask:output_type -> google.protobuf.BoolValue - 48, // 162: aggregator.Aggregator.TriggerTask:output_type -> aggregator.TriggerTaskResp - 123, // 163: aggregator.Aggregator.CreateSecret:output_type -> google.protobuf.BoolValue - 123, // 164: aggregator.Aggregator.DeleteSecret:output_type -> google.protobuf.BoolValue - 53, // 165: aggregator.Aggregator.ListSecrets:output_type -> aggregator.ListSecretsResp - 123, // 166: aggregator.Aggregator.UpdateSecret:output_type -> google.protobuf.BoolValue - 58, // 167: aggregator.Aggregator.GetWorkflowCount:output_type -> aggregator.GetWorkflowCountResp - 60, // 168: aggregator.Aggregator.GetExecutionCount:output_type -> aggregator.GetExecutionCountResp - 62, // 169: aggregator.Aggregator.GetExecutionStats:output_type -> aggregator.GetExecutionStatsResp - 64, // 170: aggregator.Aggregator.RunNodeWithInputs:output_type -> aggregator.RunNodeWithInputsResp - 66, // 171: aggregator.Aggregator.RunTrigger:output_type -> aggregator.RunTriggerResp - 27, // 172: aggregator.Aggregator.SimulateTask:output_type -> aggregator.Execution - 8, // 173: aggregator.Aggregator.GetTokenMetadata:output_type -> aggregator.GetTokenMetadataResp - 148, // [148:174] is the sub-list for method output_type - 122, // [122:148] is the sub-list for method input_type - 122, // [122:122] is the sub-list for extension type_name - 122, // [122:122] is the sub-list for extension extendee - 0, // [0:122] is the sub-list for field type_name + 122, // 2: aggregator.FixedTimeTrigger.input:type_name -> google.protobuf.Value + 71, // 3: aggregator.CronTrigger.config:type_name -> aggregator.CronTrigger.Config + 122, // 4: aggregator.CronTrigger.input:type_name -> google.protobuf.Value + 73, // 5: aggregator.BlockTrigger.config:type_name -> aggregator.BlockTrigger.Config + 122, // 6: aggregator.BlockTrigger.input:type_name -> google.protobuf.Value + 77, // 7: aggregator.EventTrigger.config:type_name -> aggregator.EventTrigger.Config + 122, // 8: aggregator.EventTrigger.input:type_name -> google.protobuf.Value + 80, // 9: aggregator.ManualTrigger.config:type_name -> aggregator.ManualTrigger.Config + 122, // 10: aggregator.ManualTrigger.input:type_name -> google.protobuf.Value + 0, // 11: aggregator.TaskTrigger.type:type_name -> aggregator.TriggerType + 10, // 12: aggregator.TaskTrigger.fixed_time:type_name -> aggregator.FixedTimeTrigger + 11, // 13: aggregator.TaskTrigger.cron:type_name -> aggregator.CronTrigger + 12, // 14: aggregator.TaskTrigger.block:type_name -> aggregator.BlockTrigger + 13, // 15: aggregator.TaskTrigger.event:type_name -> aggregator.EventTrigger + 122, // 16: aggregator.TaskTrigger.input:type_name -> google.protobuf.Value + 82, // 17: aggregator.ETHTransferNode.config:type_name -> aggregator.ETHTransferNode.Config + 122, // 18: aggregator.ETHTransferNode.input:type_name -> google.protobuf.Value + 84, // 19: aggregator.ContractWriteNode.config:type_name -> aggregator.ContractWriteNode.Config + 122, // 20: aggregator.ContractWriteNode.input:type_name -> google.protobuf.Value + 94, // 21: aggregator.ContractReadNode.config:type_name -> aggregator.ContractReadNode.Config + 122, // 22: aggregator.ContractReadNode.input:type_name -> google.protobuf.Value + 98, // 23: aggregator.GraphQLQueryNode.config:type_name -> aggregator.GraphQLQueryNode.Config + 122, // 24: aggregator.GraphQLQueryNode.input:type_name -> google.protobuf.Value + 101, // 25: aggregator.RestAPINode.config:type_name -> aggregator.RestAPINode.Config + 122, // 26: aggregator.RestAPINode.input:type_name -> google.protobuf.Value + 104, // 27: aggregator.CustomCodeNode.config:type_name -> aggregator.CustomCodeNode.Config + 122, // 28: aggregator.CustomCodeNode.input:type_name -> google.protobuf.Value + 107, // 29: aggregator.BranchNode.config:type_name -> aggregator.BranchNode.Config + 122, // 30: aggregator.BranchNode.input:type_name -> google.protobuf.Value + 109, // 31: aggregator.FilterNode.config:type_name -> aggregator.FilterNode.Config + 122, // 32: aggregator.FilterNode.input:type_name -> google.protobuf.Value + 16, // 33: aggregator.LoopNode.eth_transfer:type_name -> aggregator.ETHTransferNode + 17, // 34: aggregator.LoopNode.contract_write:type_name -> aggregator.ContractWriteNode + 18, // 35: aggregator.LoopNode.contract_read:type_name -> aggregator.ContractReadNode + 19, // 36: aggregator.LoopNode.graphql_data_query:type_name -> aggregator.GraphQLQueryNode + 20, // 37: aggregator.LoopNode.rest_api:type_name -> aggregator.RestAPINode + 21, // 38: aggregator.LoopNode.custom_code:type_name -> aggregator.CustomCodeNode + 111, // 39: aggregator.LoopNode.config:type_name -> aggregator.LoopNode.Config + 122, // 40: aggregator.LoopNode.input:type_name -> google.protobuf.Value + 1, // 41: aggregator.TaskNode.type:type_name -> aggregator.NodeType + 16, // 42: aggregator.TaskNode.eth_transfer:type_name -> aggregator.ETHTransferNode + 17, // 43: aggregator.TaskNode.contract_write:type_name -> aggregator.ContractWriteNode + 18, // 44: aggregator.TaskNode.contract_read:type_name -> aggregator.ContractReadNode + 19, // 45: aggregator.TaskNode.graphql_query:type_name -> aggregator.GraphQLQueryNode + 20, // 46: aggregator.TaskNode.rest_api:type_name -> aggregator.RestAPINode + 22, // 47: aggregator.TaskNode.branch:type_name -> aggregator.BranchNode + 23, // 48: aggregator.TaskNode.filter:type_name -> aggregator.FilterNode + 24, // 49: aggregator.TaskNode.loop:type_name -> aggregator.LoopNode + 21, // 50: aggregator.TaskNode.custom_code:type_name -> aggregator.CustomCodeNode + 122, // 51: aggregator.TaskNode.input:type_name -> google.protobuf.Value + 113, // 52: aggregator.Execution.steps:type_name -> aggregator.Execution.Step + 4, // 53: aggregator.Task.status:type_name -> aggregator.TaskStatus + 15, // 54: aggregator.Task.trigger:type_name -> aggregator.TaskTrigger + 26, // 55: aggregator.Task.nodes:type_name -> aggregator.TaskNode + 25, // 56: aggregator.Task.edges:type_name -> aggregator.TaskEdge + 15, // 57: aggregator.CreateTaskReq.trigger:type_name -> aggregator.TaskTrigger + 26, // 58: aggregator.CreateTaskReq.nodes:type_name -> aggregator.TaskNode + 25, // 59: aggregator.CreateTaskReq.edges:type_name -> aggregator.TaskEdge + 34, // 60: aggregator.ListWalletResp.items:type_name -> aggregator.SmartWallet + 28, // 61: aggregator.ListTasksResp.items:type_name -> aggregator.Task + 51, // 62: aggregator.ListTasksResp.page_info:type_name -> aggregator.PageInfo + 27, // 63: aggregator.ListExecutionsResp.items:type_name -> aggregator.Execution + 51, // 64: aggregator.ListExecutionsResp.page_info:type_name -> aggregator.PageInfo + 5, // 65: aggregator.ExecutionStatusResp.status:type_name -> aggregator.ExecutionStatus + 0, // 66: aggregator.TriggerTaskReq.trigger_type:type_name -> aggregator.TriggerType + 74, // 67: aggregator.TriggerTaskReq.block_trigger:type_name -> aggregator.BlockTrigger.Output + 70, // 68: aggregator.TriggerTaskReq.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output + 72, // 69: aggregator.TriggerTaskReq.cron_trigger:type_name -> aggregator.CronTrigger.Output + 78, // 70: aggregator.TriggerTaskReq.event_trigger:type_name -> aggregator.EventTrigger.Output + 81, // 71: aggregator.TriggerTaskReq.manual_trigger:type_name -> aggregator.ManualTrigger.Output + 5, // 72: aggregator.TriggerTaskResp.status:type_name -> aggregator.ExecutionStatus + 52, // 73: aggregator.ListSecretsResp.items:type_name -> aggregator.Secret + 51, // 74: aggregator.ListSecretsResp.page_info:type_name -> aggregator.PageInfo + 1, // 75: aggregator.RunNodeWithInputsReq.node_type:type_name -> aggregator.NodeType + 114, // 76: aggregator.RunNodeWithInputsReq.node_config:type_name -> aggregator.RunNodeWithInputsReq.NodeConfigEntry + 115, // 77: aggregator.RunNodeWithInputsReq.input_variables:type_name -> aggregator.RunNodeWithInputsReq.InputVariablesEntry + 83, // 78: aggregator.RunNodeWithInputsResp.eth_transfer:type_name -> aggregator.ETHTransferNode.Output + 99, // 79: aggregator.RunNodeWithInputsResp.graphql:type_name -> aggregator.GraphQLQueryNode.Output + 96, // 80: aggregator.RunNodeWithInputsResp.contract_read:type_name -> aggregator.ContractReadNode.Output + 86, // 81: aggregator.RunNodeWithInputsResp.contract_write:type_name -> aggregator.ContractWriteNode.Output + 105, // 82: aggregator.RunNodeWithInputsResp.custom_code:type_name -> aggregator.CustomCodeNode.Output + 102, // 83: aggregator.RunNodeWithInputsResp.rest_api:type_name -> aggregator.RestAPINode.Output + 108, // 84: aggregator.RunNodeWithInputsResp.branch:type_name -> aggregator.BranchNode.Output + 110, // 85: aggregator.RunNodeWithInputsResp.filter:type_name -> aggregator.FilterNode.Output + 112, // 86: aggregator.RunNodeWithInputsResp.loop:type_name -> aggregator.LoopNode.Output + 0, // 87: aggregator.RunTriggerReq.trigger_type:type_name -> aggregator.TriggerType + 116, // 88: aggregator.RunTriggerReq.trigger_config:type_name -> aggregator.RunTriggerReq.TriggerConfigEntry + 117, // 89: aggregator.RunTriggerReq.trigger_input:type_name -> aggregator.RunTriggerReq.TriggerInputEntry + 74, // 90: aggregator.RunTriggerResp.block_trigger:type_name -> aggregator.BlockTrigger.Output + 70, // 91: aggregator.RunTriggerResp.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output + 72, // 92: aggregator.RunTriggerResp.cron_trigger:type_name -> aggregator.CronTrigger.Output + 78, // 93: aggregator.RunTriggerResp.event_trigger:type_name -> aggregator.EventTrigger.Output + 81, // 94: aggregator.RunTriggerResp.manual_trigger:type_name -> aggregator.ManualTrigger.Output + 15, // 95: aggregator.SimulateTaskReq.trigger:type_name -> aggregator.TaskTrigger + 26, // 96: aggregator.SimulateTaskReq.nodes:type_name -> aggregator.TaskNode + 25, // 97: aggregator.SimulateTaskReq.edges:type_name -> aggregator.TaskEdge + 121, // 98: aggregator.SimulateTaskReq.input_variables:type_name -> aggregator.SimulateTaskReq.InputVariablesEntry + 76, // 99: aggregator.EventTrigger.Query.topics:type_name -> aggregator.EventTrigger.Topics + 75, // 100: aggregator.EventTrigger.Config.queries:type_name -> aggregator.EventTrigger.Query + 118, // 101: aggregator.EventTrigger.Output.evm_log:type_name -> aggregator.Evm.Log + 79, // 102: aggregator.EventTrigger.Output.transfer_log:type_name -> aggregator.EventTrigger.TransferLogOutput + 85, // 103: aggregator.ContractWriteNode.Config.method_calls:type_name -> aggregator.ContractWriteNode.MethodCall + 87, // 104: aggregator.ContractWriteNode.Output.results:type_name -> aggregator.ContractWriteNode.MethodResult + 88, // 105: aggregator.ContractWriteNode.MethodResult.transaction:type_name -> aggregator.ContractWriteNode.TransactionData + 89, // 106: aggregator.ContractWriteNode.MethodResult.events:type_name -> aggregator.ContractWriteNode.EventData + 90, // 107: aggregator.ContractWriteNode.MethodResult.error:type_name -> aggregator.ContractWriteNode.ErrorData + 91, // 108: aggregator.ContractWriteNode.MethodResult.return_data:type_name -> aggregator.ContractWriteNode.ReturnData + 92, // 109: aggregator.ContractWriteNode.EventData.decoded:type_name -> aggregator.ContractWriteNode.EventData.DecodedEntry + 93, // 110: aggregator.ContractReadNode.Config.method_calls:type_name -> aggregator.ContractReadNode.MethodCall + 97, // 111: aggregator.ContractReadNode.MethodResult.data:type_name -> aggregator.ContractReadNode.MethodResult.StructuredField + 95, // 112: aggregator.ContractReadNode.Output.results:type_name -> aggregator.ContractReadNode.MethodResult + 100, // 113: aggregator.GraphQLQueryNode.Config.variables:type_name -> aggregator.GraphQLQueryNode.Config.VariablesEntry + 123, // 114: aggregator.GraphQLQueryNode.Output.data:type_name -> google.protobuf.Any + 103, // 115: aggregator.RestAPINode.Config.headers:type_name -> aggregator.RestAPINode.Config.HeadersEntry + 122, // 116: aggregator.RestAPINode.Output.data:type_name -> google.protobuf.Value + 2, // 117: aggregator.CustomCodeNode.Config.lang:type_name -> aggregator.Lang + 122, // 118: aggregator.CustomCodeNode.Output.data:type_name -> google.protobuf.Value + 106, // 119: aggregator.BranchNode.Config.conditions:type_name -> aggregator.BranchNode.Condition + 123, // 120: aggregator.FilterNode.Output.data:type_name -> google.protobuf.Any + 122, // 121: aggregator.Execution.Step.input:type_name -> google.protobuf.Value + 74, // 122: aggregator.Execution.Step.block_trigger:type_name -> aggregator.BlockTrigger.Output + 70, // 123: aggregator.Execution.Step.fixed_time_trigger:type_name -> aggregator.FixedTimeTrigger.Output + 72, // 124: aggregator.Execution.Step.cron_trigger:type_name -> aggregator.CronTrigger.Output + 78, // 125: aggregator.Execution.Step.event_trigger:type_name -> aggregator.EventTrigger.Output + 81, // 126: aggregator.Execution.Step.manual_trigger:type_name -> aggregator.ManualTrigger.Output + 83, // 127: aggregator.Execution.Step.eth_transfer:type_name -> aggregator.ETHTransferNode.Output + 99, // 128: aggregator.Execution.Step.graphql:type_name -> aggregator.GraphQLQueryNode.Output + 96, // 129: aggregator.Execution.Step.contract_read:type_name -> aggregator.ContractReadNode.Output + 86, // 130: aggregator.Execution.Step.contract_write:type_name -> aggregator.ContractWriteNode.Output + 105, // 131: aggregator.Execution.Step.custom_code:type_name -> aggregator.CustomCodeNode.Output + 102, // 132: aggregator.Execution.Step.rest_api:type_name -> aggregator.RestAPINode.Output + 108, // 133: aggregator.Execution.Step.branch:type_name -> aggregator.BranchNode.Output + 110, // 134: aggregator.Execution.Step.filter:type_name -> aggregator.FilterNode.Output + 112, // 135: aggregator.Execution.Step.loop:type_name -> aggregator.LoopNode.Output + 122, // 136: aggregator.RunNodeWithInputsReq.NodeConfigEntry.value:type_name -> google.protobuf.Value + 122, // 137: aggregator.RunNodeWithInputsReq.InputVariablesEntry.value:type_name -> google.protobuf.Value + 122, // 138: aggregator.RunTriggerReq.TriggerConfigEntry.value:type_name -> google.protobuf.Value + 122, // 139: aggregator.RunTriggerReq.TriggerInputEntry.value:type_name -> google.protobuf.Value + 122, // 140: aggregator.SimulateTaskReq.InputVariablesEntry.value:type_name -> google.protobuf.Value + 42, // 141: aggregator.Aggregator.GetKey:input_type -> aggregator.GetKeyReq + 55, // 142: aggregator.Aggregator.GetSignatureFormat:input_type -> aggregator.GetSignatureFormatReq + 31, // 143: aggregator.Aggregator.GetNonce:input_type -> aggregator.NonceRequest + 44, // 144: aggregator.Aggregator.GetWallet:input_type -> aggregator.GetWalletReq + 46, // 145: aggregator.Aggregator.SetWallet:input_type -> aggregator.SetWalletReq + 33, // 146: aggregator.Aggregator.ListWallets:input_type -> aggregator.ListWalletReq + 29, // 147: aggregator.Aggregator.CreateTask:input_type -> aggregator.CreateTaskReq + 36, // 148: aggregator.Aggregator.ListTasks:input_type -> aggregator.ListTasksReq + 9, // 149: aggregator.Aggregator.GetTask:input_type -> aggregator.IdReq + 38, // 150: aggregator.Aggregator.ListExecutions:input_type -> aggregator.ListExecutionsReq + 40, // 151: aggregator.Aggregator.GetExecution:input_type -> aggregator.ExecutionReq + 40, // 152: aggregator.Aggregator.GetExecutionStatus:input_type -> aggregator.ExecutionReq + 9, // 153: aggregator.Aggregator.CancelTask:input_type -> aggregator.IdReq + 9, // 154: aggregator.Aggregator.DeleteTask:input_type -> aggregator.IdReq + 47, // 155: aggregator.Aggregator.TriggerTask:input_type -> aggregator.TriggerTaskReq + 49, // 156: aggregator.Aggregator.CreateSecret:input_type -> aggregator.CreateOrUpdateSecretReq + 54, // 157: aggregator.Aggregator.DeleteSecret:input_type -> aggregator.DeleteSecretReq + 50, // 158: aggregator.Aggregator.ListSecrets:input_type -> aggregator.ListSecretsReq + 49, // 159: aggregator.Aggregator.UpdateSecret:input_type -> aggregator.CreateOrUpdateSecretReq + 57, // 160: aggregator.Aggregator.GetWorkflowCount:input_type -> aggregator.GetWorkflowCountReq + 59, // 161: aggregator.Aggregator.GetExecutionCount:input_type -> aggregator.GetExecutionCountReq + 61, // 162: aggregator.Aggregator.GetExecutionStats:input_type -> aggregator.GetExecutionStatsReq + 63, // 163: aggregator.Aggregator.RunNodeWithInputs:input_type -> aggregator.RunNodeWithInputsReq + 65, // 164: aggregator.Aggregator.RunTrigger:input_type -> aggregator.RunTriggerReq + 68, // 165: aggregator.Aggregator.SimulateTask:input_type -> aggregator.SimulateTaskReq + 7, // 166: aggregator.Aggregator.GetTokenMetadata:input_type -> aggregator.GetTokenMetadataReq + 43, // 167: aggregator.Aggregator.GetKey:output_type -> aggregator.KeyResp + 56, // 168: aggregator.Aggregator.GetSignatureFormat:output_type -> aggregator.GetSignatureFormatResp + 32, // 169: aggregator.Aggregator.GetNonce:output_type -> aggregator.NonceResp + 45, // 170: aggregator.Aggregator.GetWallet:output_type -> aggregator.GetWalletResp + 45, // 171: aggregator.Aggregator.SetWallet:output_type -> aggregator.GetWalletResp + 35, // 172: aggregator.Aggregator.ListWallets:output_type -> aggregator.ListWalletResp + 30, // 173: aggregator.Aggregator.CreateTask:output_type -> aggregator.CreateTaskResp + 37, // 174: aggregator.Aggregator.ListTasks:output_type -> aggregator.ListTasksResp + 28, // 175: aggregator.Aggregator.GetTask:output_type -> aggregator.Task + 39, // 176: aggregator.Aggregator.ListExecutions:output_type -> aggregator.ListExecutionsResp + 27, // 177: aggregator.Aggregator.GetExecution:output_type -> aggregator.Execution + 41, // 178: aggregator.Aggregator.GetExecutionStatus:output_type -> aggregator.ExecutionStatusResp + 124, // 179: aggregator.Aggregator.CancelTask:output_type -> google.protobuf.BoolValue + 124, // 180: aggregator.Aggregator.DeleteTask:output_type -> google.protobuf.BoolValue + 48, // 181: aggregator.Aggregator.TriggerTask:output_type -> aggregator.TriggerTaskResp + 124, // 182: aggregator.Aggregator.CreateSecret:output_type -> google.protobuf.BoolValue + 124, // 183: aggregator.Aggregator.DeleteSecret:output_type -> google.protobuf.BoolValue + 53, // 184: aggregator.Aggregator.ListSecrets:output_type -> aggregator.ListSecretsResp + 124, // 185: aggregator.Aggregator.UpdateSecret:output_type -> google.protobuf.BoolValue + 58, // 186: aggregator.Aggregator.GetWorkflowCount:output_type -> aggregator.GetWorkflowCountResp + 60, // 187: aggregator.Aggregator.GetExecutionCount:output_type -> aggregator.GetExecutionCountResp + 62, // 188: aggregator.Aggregator.GetExecutionStats:output_type -> aggregator.GetExecutionStatsResp + 64, // 189: aggregator.Aggregator.RunNodeWithInputs:output_type -> aggregator.RunNodeWithInputsResp + 66, // 190: aggregator.Aggregator.RunTrigger:output_type -> aggregator.RunTriggerResp + 27, // 191: aggregator.Aggregator.SimulateTask:output_type -> aggregator.Execution + 8, // 192: aggregator.Aggregator.GetTokenMetadata:output_type -> aggregator.GetTokenMetadataResp + 167, // [167:193] is the sub-list for method output_type + 141, // [141:167] is the sub-list for method input_type + 141, // [141:141] is the sub-list for extension type_name + 141, // [141:141] is the sub-list for extension extendee + 0, // [0:141] is the sub-list for field type_name } func init() { file_avs_proto_init() } @@ -9127,7 +9329,7 @@ func file_avs_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_avs_proto_rawDesc), len(file_avs_proto_rawDesc)), NumEnums: 6, - NumMessages: 115, + NumMessages: 116, NumExtensions: 0, NumServices: 1, }, diff --git a/protobuf/avs.proto b/protobuf/avs.proto index 84647d68..5bd7eefb 100644 --- a/protobuf/avs.proto +++ b/protobuf/avs.proto @@ -65,6 +65,26 @@ message GetTokenMetadataResp { // CLIENT-FACING MESSAGES // ============================================================================ +// TRIGGER AND NODE INPUT SYSTEM: +// +// All triggers and nodes now support an `input` field of type google.protobuf.Value, +// which stores user-provided input data as JavaScript objects. This data is available +// for reference by subsequent nodes during task execution. +// +// During task execution, both the output data and input data of each step are accessible: +// - `node_name.data` or `node_name.output` - The computed output from the node execution +// - `node_name.input` - The user-provided input data defined when the task/node was created +// +// This allows for flexible data flow where: +// 1. Static configuration is stored in the Config field +// 2. User-provided runtime data is stored in the input field +// 3. Computed results are available in the Output field +// +// Example usage in SimulateTask or CreateTask: +// - Define input data for each node/trigger when creating the task +// - Reference both computed outputs and input data in subsequent nodes +// - Use expressions like `${previous_node.data.result}` and `${previous_node.input.user_value}` + message IdReq { string id = 1; } @@ -88,6 +108,8 @@ message FixedTimeTrigger { // Include Config as field so it is generated in Go Config config = 1; + // Input data provided by user for this trigger + google.protobuf.Value input = 2; } // Simple timebase or cron syntax. @@ -103,6 +125,8 @@ message CronTrigger { // Include Config as field so it is generated in Go Config config = 1; + // Input data provided by user for this trigger + google.protobuf.Value input = 2; } message BlockTrigger { @@ -122,6 +146,8 @@ message BlockTrigger { // Include Config as field so it is generated in Go Config config = 1; + // Input data provided by user for this trigger + google.protobuf.Value input = 2; } // EventTrigger monitors blockchain events using direct RPC filter queries. @@ -193,6 +219,8 @@ message EventTrigger { // Include Config as field so it is generated in Go Config config = 1; + // Input data provided by user for this trigger + google.protobuf.Value input = 2; } message ManualTrigger { @@ -209,6 +237,8 @@ message ManualTrigger { // Include Config as field so it is generated in Go Config config = 1; + // Input data provided by user for this trigger + google.protobuf.Value input = 2; } message TaskTrigger { @@ -233,6 +263,8 @@ message TaskTrigger { EventTrigger event = 6; } string id = 7; + // Input data provided by user for this trigger + google.protobuf.Value input = 9; } // gRPC internal error code use up to 17, we extend and start from 1000 to avoid any conflict @@ -292,6 +324,8 @@ message ETHTransferNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message ContractWriteNode { @@ -364,6 +398,8 @@ message ContractWriteNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message ContractReadNode { @@ -402,6 +438,8 @@ message ContractReadNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } @@ -421,6 +459,8 @@ message GraphQLQueryNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message RestAPINode { @@ -438,6 +478,8 @@ message RestAPINode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message CustomCodeNode { @@ -453,6 +495,8 @@ message CustomCodeNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message BranchNode { @@ -475,6 +519,8 @@ message BranchNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } message FilterNode { @@ -494,6 +540,8 @@ message FilterNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } // LoopNode currently not support, but we pre-defined to reverse the field id @@ -531,6 +579,8 @@ message LoopNode { // Include Config and Input as fields so they are generated in Go Config config = 1; + // Input data provided by user for this node + google.protobuf.Value input = 2; } // The edge is relationship or direct between node @@ -567,6 +617,8 @@ message TaskNode { LoopNode loop = 17; CustomCodeNode custom_code = 18; } + // Input data provided by user for this node + google.protobuf.Value input = 19; } message Execution { @@ -587,6 +639,8 @@ message Execution { string error = 13; string log = 12; repeated string inputs = 16; + // Input data that was set on this trigger/node + google.protobuf.Value input = 19; oneof output_data { // Trigger outputs BlockTrigger.Output block_trigger = 20; @@ -953,7 +1007,7 @@ service Aggregator { // RunNodeWithInputs allows executing a single node with provided inputs for testing purposes rpc RunNodeWithInputs(RunNodeWithInputsReq) returns (RunNodeWithInputsResp); - // RunTrigger allows executing a single trigger for testing purposes (triggers don't accept inputs) + // RunTrigger allows executing a single trigger for testing purposes rpc RunTrigger(RunTriggerReq) returns (RunTriggerResp); // SimulateTask allows executing a complete task simulation including trigger and all workflow nodes @@ -1032,6 +1086,7 @@ message RunNodeWithInputsResp { message RunTriggerReq { TriggerType trigger_type = 1; // Type of trigger to execute using the TriggerType enum map trigger_config = 2; // Configuration for the trigger + map trigger_input = 3; // Input data for the trigger } // Response message for RunTrigger diff --git a/protobuf/avs_grpc.pb.go b/protobuf/avs_grpc.pb.go index 795167ba..e7e6df4a 100644 --- a/protobuf/avs_grpc.pb.go +++ b/protobuf/avs_grpc.pb.go @@ -100,7 +100,7 @@ type AggregatorClient interface { GetExecutionStats(ctx context.Context, in *GetExecutionStatsReq, opts ...grpc.CallOption) (*GetExecutionStatsResp, error) // RunNodeWithInputs allows executing a single node with provided inputs for testing purposes RunNodeWithInputs(ctx context.Context, in *RunNodeWithInputsReq, opts ...grpc.CallOption) (*RunNodeWithInputsResp, error) - // RunTrigger allows executing a single trigger for testing purposes (triggers don't accept inputs) + // RunTrigger allows executing a single trigger for testing purposes RunTrigger(ctx context.Context, in *RunTriggerReq, opts ...grpc.CallOption) (*RunTriggerResp, error) // SimulateTask allows executing a complete task simulation including trigger and all workflow nodes SimulateTask(ctx context.Context, in *SimulateTaskReq, opts ...grpc.CallOption) (*Execution, error) @@ -428,7 +428,7 @@ type AggregatorServer interface { GetExecutionStats(context.Context, *GetExecutionStatsReq) (*GetExecutionStatsResp, error) // RunNodeWithInputs allows executing a single node with provided inputs for testing purposes RunNodeWithInputs(context.Context, *RunNodeWithInputsReq) (*RunNodeWithInputsResp, error) - // RunTrigger allows executing a single trigger for testing purposes (triggers don't accept inputs) + // RunTrigger allows executing a single trigger for testing purposes RunTrigger(context.Context, *RunTriggerReq) (*RunTriggerResp, error) // SimulateTask allows executing a complete task simulation including trigger and all workflow nodes SimulateTask(context.Context, *SimulateTaskReq) (*Execution, error)