From cf2b54464e518ccd59d49427b289f39bc389fb005a67ebc2cd9394576da90162 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 23 Jun 2026 11:42:44 +0400 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20update=20user=5Fpda=20=D0=B4=D0=BB=D1=8F=20homes?= =?UTF-8?q?erver=20=D0=BD=D0=B0=20ESP32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...23_1245_esp32_homeserver_pda_update_fix.md | 26 ++++++++ ESP32/AGENTS.md | 11 ++++ .../shine_homeserver_main.ino | 66 +++++++++++++++++-- VERSION.properties | 4 +- 4 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-06-23_1245_esp32_homeserver_pda_update_fix.md diff --git a/Dev_Docs/Pending_Features/2026-06-23_1245_esp32_homeserver_pda_update_fix.md b/Dev_Docs/Pending_Features/2026-06-23_1245_esp32_homeserver_pda_update_fix.md new file mode 100644 index 0000000..6da227a --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-23_1245_esp32_homeserver_pda_update_fix.md @@ -0,0 +1,26 @@ +## Кратко + +Исправлена ESP32-ветка обновления `user_pda` для добавления `homeserver`-сессии после миграции формата PDA на `RecoveryKeyBlock`. + +## Что сделано + +- В `shine_homeserver_main.ino` синхронизирован `create/update` payload с новым форматом `shine_users`. +- В сериализацию и парсинг PDA добавлен `RecoveryKeyBlock`. +- Для ветки `Add Homeserver` добавлены промежуточные checkpoint-записи в NVS, чтобы после crash или reset было видно, на каком шаге оборвалась операция. +- В `ESP32/AGENTS.md` добавлена памятка по чтению `last_error`. + +## Что проверять + +- Зарегистрировать или использовать уже существующий аккаунт на ESP32. +- Дойти до состояния `homeserver not in PDA`. +- Нажать `Add Homeserver`. +- Если операция не успешна, считать `last_error` по USB serial и убедиться, что видна свежая запись именно по шагам `Homeserver PDA update ...`, а не старый diag. + +## Ожидаемый результат + +- `Add Homeserver` добавляет `homeserver1` в `sessions` блока `SessionsBlock`. +- Если операция падает, в NVS сохраняется свежая диагностическая запись с текущим этапом, а не устаревший лог регистрации. + +## Статус + +`pending` diff --git a/ESP32/AGENTS.md b/ESP32/AGENTS.md index 44c8aea..0af2112 100644 --- a/ESP32/AGENTS.md +++ b/ESP32/AGENTS.md @@ -15,3 +15,14 @@ - Основной способ проверки и прошивки скетчей для `ESP32-S3-Touch-AMOLED-2.16` - `main-device/burn.sh`. - Не собирать эти скетчи напрямую через `arduino-cli compile` без `burn.sh`, потому что скрипт добавляет нужные локальные библиотеки и конфиги из `official-demo/examples/Arduino-v3.3.5/libraries`. - Если сборка падает по `lv_conf.h` или `TouchDrvCSTXXX.hpp`, сначала проверять именно `burn.sh` и его `--library` пути, а не считать, что файл пропал из репозитория. + +## Диагностика ESP32 + +- Последнюю сохранённую ошибку или диагностическую запись читать с устройства через USB serial monitor на `115200`. +- Базовая команда: + `arduino-cli monitor -p /dev/ttyACM0 --config baudrate=115200` +- После подключения отправлять одну из команд: + `last_error`, `last_diag` или `reg_diag` +- Для очистки сохранённой диагностики использовать: + `clear_error` или `clear_diag` +- При падениях в ветках регистрации и обновления PDA сначала читать именно `last_error`: запись хранится в NVS и может пережить перезагрузку устройства. 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 2232aaa..62dbe61 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 @@ -80,6 +80,7 @@ static const char *kUsersEconomyConfigSeed = "shine_users_economy_config"; static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault"; static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK"; static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress"; +static const uint8_t kBlockTypeRecoveryKey = 0; static const uint8_t kBlockTypeRootKey = 1; static const uint8_t kBlockTypeClientKey = 2; static const uint8_t kBlockTypeBlockchainRegistry = 3; @@ -229,6 +230,7 @@ struct ShinePdaUserState { std::vector accessServers; uint8_t sessionsMode = 1; uint8_t trustedCount = 0; + uint8_t recoveryKey32[32] = {}; uint8_t rootKey32[32] = {}; uint8_t clientKey32[32] = {}; uint8_t blockchainKey32[32] = {}; @@ -546,6 +548,7 @@ static std::vector buildUnsignedCreateRecord( const String &login, const String &blockchainName, const String &serverAddress, + const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], const uint8_t blockchainPub[32], @@ -556,6 +559,7 @@ static std::vector buildCreateInstructionData( const String &login, const String &blockchainName, const String &serverAddress, + const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], const uint8_t blockchainPub[32], @@ -615,6 +619,7 @@ static void loadRegisterDiagDetailsFromPrefs(); static void saveRegisterDiagDetailsToPrefs(const String &details); static void clearRegisterDiagDetailsFromPrefs(); static void saveRegisterDiag(const String &status, const String &summary, const String &details); +static void saveRegisterDiagCheckpoint(const String &summary, const String &details); static void printRegisterDiagToSerial(); static void clearRegisterDiag(); static void handleUsbSerialCommands(); @@ -1805,7 +1810,11 @@ static std::vector serializeUnsignedRecordState(const ShinePdaUserState pushU32LE(out, state.recordNumber); pushFixed(out, state.prevRecordHash32, 32); pushStrU8(out, state.login); - out.push_back(state.isServer ? 7 : 6); + out.push_back(state.isServer ? 8 : 7); + + out.push_back(kBlockTypeRecoveryKey); + out.push_back(0); + pushFixed(out, state.recoveryKey32, 32); out.push_back(kBlockTypeRootKey); out.push_back(0); @@ -1883,6 +1892,7 @@ static std::vector buildUpdateInstructionData(const ShinePdaUserState & out.reserve(640); out.push_back(4); pushStrU8(out, state.login); + pushFixed(out, state.recoveryKey32, 32); pushFixed(out, state.rootKey32, 32); pushU64LE(out, state.createdAtMs); pushU64LE(out, updatedAtMs); @@ -1933,6 +1943,7 @@ static std::vector buildUnsignedCreateRecord( const String &login, const String &blockchainName, const String &serverAddress, + const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], const uint8_t blockchainPub[32], @@ -1951,7 +1962,11 @@ static std::vector buildUnsignedCreateRecord( pushU32LE(out, 0); out.insert(out.end(), 32, 0); pushStrU8(out, login); - out.push_back(7); + out.push_back(8); + + out.push_back(kBlockTypeRecoveryKey); + out.push_back(0); + pushFixed(out, recoveryPub, 32); out.push_back(kBlockTypeRootKey); out.push_back(0); @@ -2005,6 +2020,7 @@ static std::vector buildCreateInstructionData( const String &login, const String &blockchainName, const String &serverAddress, + const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], const uint8_t blockchainPub[32], @@ -2015,6 +2031,7 @@ static std::vector buildCreateInstructionData( out.reserve(512); out.push_back(3); pushStrU8(out, login); + pushFixed(out, recoveryPub, 32); pushFixed(out, rootPub, 32); pushU64LE(out, createdAtMs); pushU64LE(out, 0); @@ -2757,17 +2774,22 @@ static bool registerHomeserverOnSolana(String &messageOut) { uint8_t rootSeed[32] = {}; uint8_t rootPub[32] = {}; uint8_t rootSec[64] = {}; + uint8_t recoverySeed[32] = {}; + uint8_t recoveryPub[32] = {}; + uint8_t recoverySec[64] = {}; uint8_t blockchainSeed[32] = {}; uint8_t blockchainPub[32] = {}; uint8_t blockchainSec[64] = {}; uint8_t clientSeed[32] = {}; uint8_t clientPub[32] = {}; uint8_t deviceSec[64] = {}; - if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) || + if (!deriveSeedKeypairFromBase58(gRecoveryPrivB58, recoverySeed, recoveryPub, recoverySec) || + !deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) || !deriveSeedKeypairFromBase58(gBlockchainPrivB58, blockchainSeed, blockchainPub, blockchainSec) || !deriveSeedKeypairFromBase58(gDevicePrivB58, clientSeed, clientPub, deviceSec)) { return failWithDiag("Failed to restore keys"); } + diagDetails += String("recovery_pub=") + bytesToBase58(recoveryPub, 32) + "\n"; diagDetails += String("root_pub=") + bytesToBase58(rootPub, 32) + "\n"; diagDetails += String("blockchain_pub=") + bytesToBase58(blockchainPub, 32) + "\n"; diagDetails += String("device_pub=") + bytesToBase58(clientPub, 32) + "\n"; @@ -2788,6 +2810,7 @@ static bool registerHomeserverOnSolana(String &messageOut) { diagDetails += String("created_at_ms=") + String((unsigned long long)createdAtMs) + "\n"; std::vector unsignedRecord = buildUnsignedCreateRecord( cleanLogin, blockchainName, gShineServerUrl, + recoveryPub, rootPub, clientPub, blockchainPub, lastBlockSignature, startBonusLimit, createdAtMs); uint8_t unsignedHash[32]; @@ -2802,6 +2825,7 @@ static bool registerHomeserverOnSolana(String &messageOut) { std::vector createData = buildCreateInstructionData( cleanLogin, blockchainName, gShineServerUrl, + recoveryPub, rootPub, clientPub, blockchainPub, lastBlockSignature, rootSignature, createdAtMs); std::vector edRootData = buildEd25519InstructionData(rootSignature, rootPub, unsignedHash); @@ -3035,6 +3059,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag diagDetails += String("login=") + cleanLogin + "\n"; diagDetails += String("homeserver=") + gHomeserverValue + "\n"; diagDetails += String("rpc=") + gSolanaRpcUrl + "\n"; + saveRegisterDiagCheckpoint("Homeserver PDA update started", diagDetails); if (cleanLogin.isEmpty()) { return failWithDiag("Login is not set"); @@ -3060,7 +3085,11 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag if (!currentState.found) { return failWithDiag("User PDA does not exist yet"); } + saveRegisterDiagCheckpoint("Homeserver PDA read", diagDetails); + uint8_t recoverySeed[32] = {}; + uint8_t recoveryPub[32] = {}; + uint8_t recoverySec[64] = {}; uint8_t rootSeed[32] = {}; uint8_t rootPub[32] = {}; uint8_t rootSec[64] = {}; @@ -3068,14 +3097,19 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag uint8_t clientPub[32] = {}; uint8_t deviceSec[64] = {}; uint8_t homeserverPub[32] = {}; - if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) + if (!deriveSeedKeypairFromBase58(gRecoveryPrivB58, recoverySeed, recoveryPub, recoverySec) + || !deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) || !deriveSeedKeypairFromBase58(gDevicePrivB58, clientSeed, clientPub, deviceSec) || !base58ToFixed32(gHomeserverPubB58, homeserverPub)) { return failWithDiag("Failed to restore local keys"); } + if (memcmp(recoveryPub, currentState.recoveryKey32, 32) != 0) { + return failWithDiag("Recovery key does not match PDA"); + } if (memcmp(clientPub, currentState.clientKey32, 32) != 0) { return failWithDiag("Client key does not match PDA"); } + saveRegisterDiagCheckpoint("Local keys restored", diagDetails); uint8_t userPda[32] = {}; uint8_t economyConfig[32] = {}; @@ -3099,6 +3133,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag diagDetails += String("inflow_vault_pda=") + bytesToBase58(inflowVault, 32) + "\n"; ShinePdaUserState nextState = currentState; + memcpy(nextState.recoveryKey32, recoveryPub, 32); memcpy(nextState.rootKey32, rootPub, 32); memcpy(nextState.clientKey32, clientPub, 32); nextState.updatedAtMs = shineNowMs(); @@ -3127,6 +3162,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag memcpy(rec.sessionPubKey32, homeserverPub, 32); nextState.sessions.push_back(rec); } + saveRegisterDiagCheckpoint("Homeserver session merged", diagDetails); std::vector oldUnsigned = serializeUnsignedRecordState(currentState); uint8_t prevHash32[32] = {}; @@ -3143,6 +3179,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag } diagDetails += String("unsigned_record_len=") + String((unsigned long)newUnsigned.size()) + "\n"; diagDetails += String("unsigned_record_hash=") + bytesToHexString(unsignedHash, 32) + "\n"; + saveRegisterDiagCheckpoint("Unsigned update built", diagDetails); std::vector lastBlockState = buildLastBlockStateBytes( cleanLogin, @@ -3170,6 +3207,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag return failWithDiag(messageOut); } diagDetails += String("recent_blockhash=") + recentBlockhash58 + "\n"; + saveRegisterDiagCheckpoint("Recent blockhash loaded", diagDetails); std::vector message = buildUpdateLegacyMessage( recentBlockhash, @@ -3187,6 +3225,7 @@ static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messag String txBase64 = encodeTransactionBase64(txSignature, message); String signatureB58 = bytesToBase58(txSignature, 64); diagDetails += String("tx_signature=") + signatureB58 + "\n"; + saveRegisterDiagCheckpoint("Signed update transaction", diagDetails); String payload; if (!rpcCallSolana("sendTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"preflightCommitment\":\"confirmed\"}]", payload)) { @@ -3334,6 +3373,13 @@ static bool parseShineUserPdaBytes(const std::vector &bytes, ShinePdaUs errorOut = "Bad PDA block header"; return false; } + if (blockType == kBlockTypeRecoveryKey) { + if (!readBytes(outState.recoveryKey32, 32)) { + errorOut = "Bad recovery key block"; + return false; + } + continue; + } if (blockType == kBlockTypeRootKey) { if (!readBytes(outState.rootKey32, 32)) { errorOut = "Bad root key block"; @@ -3571,10 +3617,12 @@ static void refreshAccountPdaStatus() { return; } + uint8_t recoveryPub[32] = {}; uint8_t rootPub[32] = {}; uint8_t clientPub[32] = {}; uint8_t blockchainPub[32] = {}; - if (!base58ToFixed32(gRootPubB58, rootPub) + if (!base58ToFixed32(gRecoveryPubB58, recoveryPub) + || !base58ToFixed32(gRootPubB58, rootPub) || !base58ToFixed32(gDevicePubB58, clientPub) || !base58ToFixed32(gBlockchainPubB58, blockchainPub)) { gAccountPdaStatus = ACCOUNT_PDA_MISMATCH; @@ -3585,7 +3633,9 @@ static void refreshAccountPdaStatus() { } String mismatch; - if (memcmp(rootPub, pdaState.rootKey32, 32) != 0) { + if (memcmp(recoveryPub, pdaState.recoveryKey32, 32) != 0) { + mismatch = "recovery key mismatch"; + } else if (memcmp(rootPub, pdaState.rootKey32, 32) != 0) { mismatch = "root key mismatch"; } else if (memcmp(blockchainPub, pdaState.blockchainKey32, 32) != 0) { mismatch = "blockchain key mismatch"; @@ -4782,6 +4832,10 @@ static void saveRegisterDiag(const String &status, const String &summary, const saveRegisterDiagDetailsToPrefs(details); } +static void saveRegisterDiagCheckpoint(const String &summary, const String &details) { + saveRegisterDiag("progress", summary, details); +} + static void clearRegisterDiag() { gLastRegisterDiagStatus = "none"; gLastRegisterDiagSummary = ""; diff --git a/VERSION.properties b/VERSION.properties index f6016a1..54d8bf7 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.236 -server.version=1.2.222 +client.version=1.2.237 +server.version=1.2.223