diff --git a/scripts/tee-mgmt-cli/cmd/cert.go b/scripts/tee-mgmt-cli/cmd/cert.go index f3fee36c..ebf2662c 100644 --- a/scripts/tee-mgmt-cli/cmd/cert.go +++ b/scripts/tee-mgmt-cli/cmd/cert.go @@ -40,7 +40,8 @@ var certSetAWSCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "AWS cert set") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "AWS cert set") return nil }, } diff --git a/scripts/tee-mgmt-cli/cmd/heartbeat.go b/scripts/tee-mgmt-cli/cmd/heartbeat.go index db05295b..56ba1890 100644 --- a/scripts/tee-mgmt-cli/cmd/heartbeat.go +++ b/scripts/tee-mgmt-cli/cmd/heartbeat.go @@ -38,7 +38,8 @@ var heartbeatSetMaxAgeCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), fmt.Sprintf("Heartbeat max age set to %s seconds", maxAge.String())) + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, fmt.Sprintf("Heartbeat max age set to %s seconds", maxAge.String())) return nil }, } diff --git a/scripts/tee-mgmt-cli/cmd/pcr.go b/scripts/tee-mgmt-cli/cmd/pcr.go index 6005ba52..03061698 100644 --- a/scripts/tee-mgmt-cli/cmd/pcr.go +++ b/scripts/tee-mgmt-cli/cmd/pcr.go @@ -57,7 +57,8 @@ var pcrApproveCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "PCR approved") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "PCR approved") return nil }, } @@ -81,7 +82,8 @@ var pcrRevokeCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "PCR revoked") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "PCR revoked") return nil }, } diff --git a/scripts/tee-mgmt-cli/cmd/role.go b/scripts/tee-mgmt-cli/cmd/role.go index a20b3362..e8e64dc5 100644 --- a/scripts/tee-mgmt-cli/cmd/role.go +++ b/scripts/tee-mgmt-cli/cmd/role.go @@ -26,7 +26,8 @@ var roleGrantAdminCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "Admin added") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "Admin added") return nil }, } @@ -43,7 +44,8 @@ var roleGrantOperatorCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "Operator added") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "Operator added") return nil }, } @@ -60,7 +62,8 @@ var roleRevokeAdminCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "Admin revoked") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "Admin revoked") return nil }, } @@ -77,7 +80,8 @@ var roleRevokeOperatorCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "Operator revoked") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "Operator revoked") return nil }, } diff --git a/scripts/tee-mgmt-cli/cmd/tee.go b/scripts/tee-mgmt-cli/cmd/tee.go index 93a73a67..7c66518b 100644 --- a/scripts/tee-mgmt-cli/cmd/tee.go +++ b/scripts/tee-mgmt-cli/cmd/tee.go @@ -152,10 +152,15 @@ var teeRegisterCmd = &cobra.Command{ } fmt.Printf(" TX: %s\n", txHash) - if client.WaitForTx(txHash) { + success, revertReason := client.WaitForTx(txHash) + if success { fmt.Printf("\nTEE registered! ID: 0x%s\n", hex.EncodeToString(expectedId[:])) } else { - fmt.Println("\nRegistration failed") + if revertReason != "" { + fmt.Printf("\nRegistration failed: %s\n", revertReason) + } else { + fmt.Println("\nRegistration failed") + } os.Exit(1) } return nil @@ -179,7 +184,8 @@ var teeDisableCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "TEE disabled") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "TEE disabled") return nil }, } @@ -201,7 +207,8 @@ var teeEnableCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "TEE enabled") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "TEE enabled") return nil }, } diff --git a/scripts/tee-mgmt-cli/cmd/types.go b/scripts/tee-mgmt-cli/cmd/types.go index 862d459d..95c69c9e 100644 --- a/scripts/tee-mgmt-cli/cmd/types.go +++ b/scripts/tee-mgmt-cli/cmd/types.go @@ -43,7 +43,8 @@ var typeAddCmd = &cobra.Command{ return fmt.Errorf("failed: %w", err) } fmt.Printf("TX: %s\n", txHash) - registry.PrintTxResult(client.WaitForTx(txHash), "Type added") + success, reason := client.WaitForTx(txHash) + registry.PrintTxResult(success, reason, "Type added") return nil }, } diff --git a/scripts/tee-mgmt-cli/registry/client.go b/scripts/tee-mgmt-cli/registry/client.go index 0aa65a1f..94c6a376 100644 --- a/scripts/tee-mgmt-cli/registry/client.go +++ b/scripts/tee-mgmt-cli/registry/client.go @@ -453,18 +453,131 @@ func (c *Client) sendTxSigned(data []byte) (string, error) { return signed.Hash().Hex(), nil } -func (c *Client) WaitForTx(txHash string) bool { +func (c *Client) WaitForTx(txHash string) (bool, string) { Log("Waiting for confirmation...") + var lastErr error for i := 0; i < 30; i++ { - resp, _ := c.rpcCall("eth_getTransactionReceipt", []string{txHash}) - var result struct{ Result *struct{ Status string } } - json.Unmarshal(resp, &result) + resp, err := c.rpcCall("eth_getTransactionReceipt", []string{txHash}) + if err != nil { + lastErr = err + time.Sleep(time.Second) + continue + } + var result struct { + Result *struct { + Status string `json:"status"` + BlockNumber string `json:"blockNumber"` + } `json:"result"` + } + if err := json.Unmarshal(resp, &result); err != nil { + lastErr = fmt.Errorf("failed to parse receipt response: %w", err) + time.Sleep(time.Second) + continue + } if result.Result != nil { - return result.Result.Status == "0x1" + if result.Result.Status == "0x1" { + return true, "" + } + reason := c.getRevertReason(txHash, result.Result.BlockNumber) + return false, reason } time.Sleep(time.Second) } - return false + if lastErr != nil { + return false, fmt.Sprintf("RPC error: %v", lastErr) + } + return false, "timed out waiting for receipt" +} + +func (c *Client) getRevertReason(txHash, blockNumber string) string { + // Fetch the original transaction to replay it + resp, err := c.rpcCall("eth_getTransactionByHash", []string{txHash}) + if err != nil { + return "" + } + var txResult struct { + Result *struct { + From string `json:"from"` + To string `json:"to"` + Data string `json:"input"` + Value string `json:"value"` + Gas string `json:"gas"` + } `json:"result"` + } + json.Unmarshal(resp, &txResult) + if txResult.Result == nil { + return "" + } + + // Replay the call at the block it was mined to get the revert data + callParams := map[string]string{ + "from": txResult.Result.From, + "to": txResult.Result.To, + "data": txResult.Result.Data, + } + if txResult.Result.Value != "" { + callParams["value"] = txResult.Result.Value + } + if txResult.Result.Gas != "" { + callParams["gas"] = txResult.Result.Gas + } + + resp, err = c.rpcCall("eth_call", []interface{}{callParams, blockNumber}) + if err != nil { + return "" + } + + var callResult struct { + Error *struct { + Message string `json:"message"` + Data string `json:"data"` + } `json:"error"` + Result string `json:"result"` + } + json.Unmarshal(resp, &callResult) + + if callResult.Error != nil { + // Try to decode revert reason from error data + if callResult.Error.Data != "" { + if reason := decodeRevertReason(callResult.Error.Data); reason != "" { + return reason + } + } + return callResult.Error.Message + } + + // Some nodes return revert data in the result field + if len(callResult.Result) > 2 { + if reason := decodeRevertReason(callResult.Result); reason != "" { + return reason + } + } + + return "transaction reverted" +} + +func decodeRevertReason(hexData string) string { + hexData = strings.TrimPrefix(hexData, "0x") + if len(hexData) < 8 { + return "" + } + + data, err := hex.DecodeString(hexData) + if err != nil { + return "" + } + + // Check for Error(string) selector: 0x08c379a0 + if len(data) >= 68 && hex.EncodeToString(data[:4]) == "08c379a0" { + strT, _ := abi.NewType("string", "", nil) + args := abi.Arguments{{Type: strT}} + values, err := args.UnpackValues(data[4:]) + if err == nil && len(values) > 0 { + return fmt.Sprintf("%v", values[0]) + } + } + + return "" } func (c *Client) rpcCall(method string, params interface{}) ([]byte, error) { @@ -648,11 +761,15 @@ func Log(format string, args ...interface{}) { fmt.Printf("[%s] %s\n", time.Now().Format("15:04:05"), fmt.Sprintf(format, args...)) } -func PrintTxResult(success bool, msg string) { +func PrintTxResult(success bool, revertReason string, msg string) { if success { fmt.Printf("%s\n", msg) } else { - fmt.Println("Transaction failed") + if revertReason != "" { + fmt.Printf("Transaction failed: %s\n", revertReason) + } else { + fmt.Println("Transaction failed") + } } }