Исправить update user_pda для homeserver на ESP32

This commit is contained in:
AidarKC 2026-06-23 11:42:44 +04:00
parent 4e60c1274a
commit cf2b54464e
4 changed files with 99 additions and 8 deletions

View File

@ -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`

View File

@ -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 и может пережить перезагрузку устройства.

View File

@ -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<String> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> createData = buildCreateInstructionData(
cleanLogin, blockchainName, gShineServerUrl,
recoveryPub,
rootPub, clientPub, blockchainPub,
lastBlockSignature, rootSignature, createdAtMs);
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> &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 = "";

View File

@ -1,2 +1,2 @@
client.version=1.2.236
server.version=1.2.222
client.version=1.2.237
server.version=1.2.223