diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino index e764e2a..708b358 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino @@ -362,6 +362,9 @@ static bool rpcCallSolana(const char *method, const String ¶msJson, String & static bool rpcResponseHasError(const String &payload); static bool getLatestBlockhashBytes(uint8_t out[32], String &blockhashB58, String &messageOut); static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &messageOut); +static bool extractRpcErrorSummary(const String &payload, String &messageOut); +static String compactRpcLogs(const String &payload, int maxLines = 3); +static bool simulateTransactionForError(const String &txBase64, String &messageOut); static std::vector buildLastBlockStateBytes(const String &login, const String &blockchainName); static std::vector buildUnsignedCreateRecord( const String &login, @@ -1223,6 +1226,7 @@ static std::vector buildCreateInstructionData( out.push_back(0); out.push_back(1); out.push_back(0); + out.push_back(0); pushStrU8(out, serverAddress); out.push_back(0); out.push_back(0); @@ -1298,6 +1302,86 @@ static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &me return false; } +static String compactRpcLogs(const String &payload, int maxLines) { + String out; + int pos = payload.indexOf("\"logs\""); + if (pos < 0) { + return ""; + } + int bracket = payload.indexOf('[', pos); + if (bracket < 0) { + return ""; + } + int i = bracket + 1; + int lines = 0; + while (i < (int)payload.length() && lines < maxLines) { + while (i < (int)payload.length() && payload[i] != '"' && payload[i] != ']') { + i++; + } + if (i >= (int)payload.length() || payload[i] == ']') { + break; + } + String line; + bool escape = false; + i++; + for (; i < (int)payload.length(); ++i) { + char ch = payload[i]; + if (escape) { + switch (ch) { + case 'n': line += ' '; break; + case 'r': break; + case 't': line += ' '; break; + default: line += ch; break; + } + escape = false; + continue; + } + if (ch == '\\') { + escape = true; + continue; + } + if (ch == '"') { + i++; + break; + } + line += ch; + } + line.trim(); + if (!line.isEmpty()) { + if (!out.isEmpty()) { + out += " | "; + } + out += line; + lines++; + } + } + return out; +} + +static bool extractRpcErrorSummary(const String &payload, String &messageOut) { + messageOut = ""; + String errorMessage; + if (jsonStringField(payload, "message", errorMessage) && !errorMessage.isEmpty()) { + messageOut = errorMessage; + } + String logs = compactRpcLogs(payload, 3); + if (!logs.isEmpty()) { + if (!messageOut.isEmpty()) { + messageOut += " | "; + } + messageOut += logs; + } + return !messageOut.isEmpty(); +} + +static bool simulateTransactionForError(const String &txBase64, String &messageOut) { + String payload; + if (!rpcCallSolana("simulateTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"sigVerify\":true,\"commitment\":\"processed\"}]", payload)) { + return false; + } + return extractRpcErrorSummary(payload, messageOut); +} + static std::vector buildLegacyMessage( const uint8_t recentBlockhash[32], const uint8_t devicePub[32], @@ -1531,7 +1615,17 @@ static bool registerHomeserverOnSolana(String &messageOut) { return false; } if (rpcResponseHasError(payload)) { - messageOut = "RPC returned sendTransaction error"; + if (!extractRpcErrorSummary(payload, messageOut)) { + messageOut = "RPC returned sendTransaction error"; + } + String simulated; + if (simulateTransactionForError(txBase64, simulated)) { + if (messageOut.isEmpty()) { + messageOut = simulated; + } else if (messageOut.indexOf(simulated) < 0) { + messageOut += " | simulate: " + simulated; + } + } return false; } if (!awaitTransactionConfirmation(signatureB58, messageOut)) { @@ -3085,13 +3179,13 @@ static void actionButtonCb(lv_event_t *event) { String registerMessage; if (registerHomeserverOnSolana(registerMessage)) { gRegisterResultSuccess = true; - gRegisterResultMessage = "Registration in SHiNE completed"; - gRegisterResultDetails = registerMessage; + gRegisterResultMessage = registerMessage; + gRegisterResultDetails = String("user_pda and tx signature were saved"); gAccountStatusMessage = "Registration completed"; } else { gRegisterResultSuccess = false; - gRegisterResultMessage = "Registration failed"; - gRegisterResultDetails = registerMessage; + gRegisterResultMessage = registerMessage; + gRegisterResultDetails = ""; gAccountStatusMessage = registerMessage; } gRegisterConfirmMessage = ""; diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md index 90e96f9..58c1a72 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md @@ -212,7 +212,8 @@ Поведение: - после успешной регистрации данные `user_pda` и `tx signature` сохраняются в `NVS`; -- при ошибке на экране показывается причина отказа. +- при ошибке на экране показывается причина отказа; +- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов. ## Экран STATUS diff --git a/VERSION.properties b/VERSION.properties index c790f2a..8539140 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.166 -server.version=1.2.155 +client.version=1.2.167 +server.version=1.2.156