ESP32: добавить USB-диагностику регистрации Solana
This commit is contained in:
parent
21030b1d51
commit
436e1f0c53
@ -0,0 +1,45 @@
|
|||||||
|
# ESP32 USB-диагностика регистрации Solana
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
- В основной скетч `shine_homeserver_main` добавено сохранение последней диагностики регистрации Solana в `Preferences`.
|
||||||
|
- Добавлены USB-команды через `Serial`:
|
||||||
|
- `last_error`
|
||||||
|
- `last_diag`
|
||||||
|
- `reg_diag`
|
||||||
|
- `clear_error`
|
||||||
|
- `clear_diag`
|
||||||
|
- `help`
|
||||||
|
- Перед отправкой `create_user_pda` добавлена RPC-проверка `users_economy_config_pda`.
|
||||||
|
- Стартовый `paid_limit_bytes` в подписываемой записи теперь берётся из on-chain `users_economy_config`, а не из хардкода.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
1. Подключить устройство по USB.
|
||||||
|
2. Открыть последовательный порт `115200`.
|
||||||
|
3. Отправить команду `last_error`.
|
||||||
|
4. Убедиться, что устройство печатает сохранённую диагностику между маркерами:
|
||||||
|
- `LAST_REGISTER_DIAG_BEGIN`
|
||||||
|
- `LAST_REGISTER_DIAG_END`
|
||||||
|
5. Запустить регистрацию с устройства и дождаться ошибки или успеха.
|
||||||
|
6. Снова отправить `last_error`.
|
||||||
|
7. Проверить, что в диагностике есть:
|
||||||
|
- `status`
|
||||||
|
- `summary`
|
||||||
|
- `rpc`
|
||||||
|
- `user_pda`
|
||||||
|
- `users_economy_config_pda`
|
||||||
|
- `inflow_vault_pda`
|
||||||
|
- `root_pub`
|
||||||
|
- `blockchain_pub`
|
||||||
|
- `device_pub`
|
||||||
|
8. При ошибке `0x3` проверить, что текст стал конкретнее и помогает понять, какая PDA или RPC-конфигурация не совпала.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- Последняя ошибка регистрации читается по USB без просмотра экрана.
|
||||||
|
- После неудачной регистрации на устройстве остаётся подробная диагностическая запись.
|
||||||
|
- Если `users_economy_config_pda` отсутствует или принадлежит не той программе, это явно видно до отправки транзакции.
|
||||||
|
|
||||||
@ -256,6 +256,11 @@ static bool gRegisterConfirmCanSubmit = false;
|
|||||||
static String gRegisterResultMessage;
|
static String gRegisterResultMessage;
|
||||||
static String gRegisterResultDetails;
|
static String gRegisterResultDetails;
|
||||||
static bool gRegisterResultSuccess = false;
|
static bool gRegisterResultSuccess = false;
|
||||||
|
static String gLastRegisterDiagStatus = "none";
|
||||||
|
static String gLastRegisterDiagSummary;
|
||||||
|
static String gLastRegisterDiagDetails;
|
||||||
|
static String gLastRegisterDiagTime;
|
||||||
|
static String gSerialCommandBuffer;
|
||||||
static String gShineSessionId;
|
static String gShineSessionId;
|
||||||
static String gShineSessionKey;
|
static String gShineSessionKey;
|
||||||
static String gShineStoragePwd;
|
static String gShineStoragePwd;
|
||||||
@ -362,6 +367,12 @@ static bool rpcCallSolana(const char *method, const String ¶msJson, String &
|
|||||||
static bool rpcResponseHasError(const String &payload);
|
static bool rpcResponseHasError(const String &payload);
|
||||||
static bool getLatestBlockhashBytes(uint8_t out[32], String &blockhashB58, String &messageOut);
|
static bool getLatestBlockhashBytes(uint8_t out[32], String &blockhashB58, String &messageOut);
|
||||||
static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &messageOut);
|
static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &messageOut);
|
||||||
|
static bool loadUsersEconomyConfigState(const String &economyConfigAddress,
|
||||||
|
uint64_t ®istrationFeeLamportsOut,
|
||||||
|
uint64_t &lamportsPerLimitStepOut,
|
||||||
|
uint64_t &startBonusLimitOut,
|
||||||
|
String &messageOut);
|
||||||
|
static bool loadAccountOwner(const String &address, String &ownerOut, bool &existsOut, String &messageOut);
|
||||||
static bool extractRpcErrorSummary(const String &payload, String &messageOut);
|
static bool extractRpcErrorSummary(const String &payload, String &messageOut);
|
||||||
static String compactRpcLogs(const String &payload, int maxLines = 3);
|
static String compactRpcLogs(const String &payload, int maxLines = 3);
|
||||||
static bool simulateTransactionForError(const String &txBase64, String &messageOut);
|
static bool simulateTransactionForError(const String &txBase64, String &messageOut);
|
||||||
@ -374,6 +385,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
const uint8_t devicePub[32],
|
const uint8_t devicePub[32],
|
||||||
const uint8_t blockchainPub[32],
|
const uint8_t blockchainPub[32],
|
||||||
const uint8_t lastBlockSignature[64],
|
const uint8_t lastBlockSignature[64],
|
||||||
|
uint64_t paidLimitBytes,
|
||||||
uint64_t createdAtMs);
|
uint64_t createdAtMs);
|
||||||
static std::vector<uint8_t> buildCreateInstructionData(
|
static std::vector<uint8_t> buildCreateInstructionData(
|
||||||
const String &login,
|
const String &login,
|
||||||
@ -399,6 +411,10 @@ static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_
|
|||||||
static String encodeTransactionBase64(const uint8_t signature[64], const std::vector<uint8_t> &message);
|
static String encodeTransactionBase64(const uint8_t signature[64], const std::vector<uint8_t> &message);
|
||||||
static bool awaitTransactionConfirmation(const String &signatureB58, String &messageOut);
|
static bool awaitTransactionConfirmation(const String &signatureB58, String &messageOut);
|
||||||
static bool registerHomeserverOnSolana(String &messageOut);
|
static bool registerHomeserverOnSolana(String &messageOut);
|
||||||
|
static void saveRegisterDiag(const String &status, const String &summary, const String &details);
|
||||||
|
static void printRegisterDiagToSerial();
|
||||||
|
static void clearRegisterDiag();
|
||||||
|
static void handleUsbSerialCommands();
|
||||||
static void prepareRegisterAccountScreen();
|
static void prepareRegisterAccountScreen();
|
||||||
static String buildSessionKeyStringFromPublicBase64(const String &pubB64);
|
static String buildSessionKeyStringFromPublicBase64(const String &pubB64);
|
||||||
static bool deriveKeypairFromSeed32(const uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
static bool deriveKeypairFromSeed32(const uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
||||||
@ -1140,6 +1156,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
const uint8_t devicePub[32],
|
const uint8_t devicePub[32],
|
||||||
const uint8_t blockchainPub[32],
|
const uint8_t blockchainPub[32],
|
||||||
const uint8_t lastBlockSignature[64],
|
const uint8_t lastBlockSignature[64],
|
||||||
|
uint64_t paidLimitBytes,
|
||||||
uint64_t createdAtMs) {
|
uint64_t createdAtMs) {
|
||||||
std::vector<uint8_t> out;
|
std::vector<uint8_t> out;
|
||||||
out.reserve(512);
|
out.reserve(512);
|
||||||
@ -1169,7 +1186,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
out.push_back(1);
|
out.push_back(1);
|
||||||
pushStrU8(out, blockchainName);
|
pushStrU8(out, blockchainName);
|
||||||
pushFixed(out, blockchainPub, 32);
|
pushFixed(out, blockchainPub, 32);
|
||||||
pushU64LE(out, 100000);
|
pushU64LE(out, paidLimitBytes);
|
||||||
pushU64LE(out, 0);
|
pushU64LE(out, 0);
|
||||||
pushU32LE(out, 0);
|
pushU32LE(out, 0);
|
||||||
out.insert(out.end(), 32, 0);
|
out.insert(out.end(), 32, 0);
|
||||||
@ -1306,6 +1323,82 @@ static bool pdaAlreadyExists(const String &login, String &pdaAddress, String &me
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool loadAccountOwner(const String &address, String &ownerOut, bool &existsOut, String &messageOut) {
|
||||||
|
ownerOut = "";
|
||||||
|
existsOut = false;
|
||||||
|
String payload;
|
||||||
|
if (!rpcCallSolana("getAccountInfo", "[\"" + address + "\",{\"encoding\":\"base64\",\"commitment\":\"confirmed\"}]", payload)) {
|
||||||
|
messageOut = "Failed to read account info";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (payload.indexOf("\"value\":null") >= 0) {
|
||||||
|
existsOut = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
existsOut = true;
|
||||||
|
if (!jsonStringField(payload, "owner", ownerOut) || ownerOut.isEmpty()) {
|
||||||
|
messageOut = "Account owner missing in RPC response";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool loadUsersEconomyConfigState(const String &economyConfigAddress,
|
||||||
|
uint64_t ®istrationFeeLamportsOut,
|
||||||
|
uint64_t &lamportsPerLimitStepOut,
|
||||||
|
uint64_t &startBonusLimitOut,
|
||||||
|
String &messageOut) {
|
||||||
|
registrationFeeLamportsOut = 0;
|
||||||
|
lamportsPerLimitStepOut = 0;
|
||||||
|
startBonusLimitOut = 0;
|
||||||
|
|
||||||
|
String payload;
|
||||||
|
if (!rpcCallSolana("getAccountInfo", "[\"" + economyConfigAddress + "\",{\"encoding\":\"base64\",\"commitment\":\"confirmed\"}]", payload)) {
|
||||||
|
messageOut = "Failed to read users economy config";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (payload.indexOf("\"value\":null") >= 0) {
|
||||||
|
messageOut = "Users economy config is missing on this Solana RPC";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String owner;
|
||||||
|
if (!jsonStringField(payload, "owner", owner) || owner.isEmpty()) {
|
||||||
|
messageOut = "Users economy config owner is missing";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (owner != String(kShineUsersProgramId)) {
|
||||||
|
messageOut = String("Users economy config owner mismatch: ") + owner;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dataB64;
|
||||||
|
if (!jsonStringField(payload, "data", dataB64) || dataB64.isEmpty()) {
|
||||||
|
messageOut = "Users economy config base64 is missing";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> raw;
|
||||||
|
if (!base64DecodeStd(dataB64, raw)) {
|
||||||
|
messageOut = "Users economy config base64 decode failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (raw.size() < 25) {
|
||||||
|
messageOut = "Users economy config is too short";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
registrationFeeLamportsOut = 0;
|
||||||
|
lamportsPerLimitStepOut = 0;
|
||||||
|
startBonusLimitOut = 0;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
registrationFeeLamportsOut |= ((uint64_t)raw[1 + i]) << (8 * i);
|
||||||
|
lamportsPerLimitStepOut |= ((uint64_t)raw[9 + i]) << (8 * i);
|
||||||
|
startBonusLimitOut |= ((uint64_t)raw[17 + i]) << (8 * i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static String compactRpcLogs(const String &payload, int maxLines) {
|
static String compactRpcLogs(const String &payload, int maxLines) {
|
||||||
String out;
|
String out;
|
||||||
int pos = payload.indexOf("\"logs\"");
|
int pos = payload.indexOf("\"logs\"");
|
||||||
@ -1490,22 +1583,33 @@ static bool awaitTransactionConfirmation(const String &signatureB58, String &mes
|
|||||||
|
|
||||||
static bool registerHomeserverOnSolana(String &messageOut) {
|
static bool registerHomeserverOnSolana(String &messageOut) {
|
||||||
messageOut = "";
|
messageOut = "";
|
||||||
String cleanLogin = normalizeLoginValue(gLoginValue);
|
String diagDetails;
|
||||||
if (cleanLogin.isEmpty()) {
|
auto failWithDiag = [&](const String &summary) -> bool {
|
||||||
messageOut = "Login is not set";
|
messageOut = summary;
|
||||||
|
saveRegisterDiag("error", summary, diagDetails);
|
||||||
|
printRegisterDiagToSerial();
|
||||||
return false;
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
String cleanLogin = normalizeLoginValue(gLoginValue);
|
||||||
|
diagDetails += String("login=") + cleanLogin + "\n";
|
||||||
|
diagDetails += String("rpc=") + gSolanaRpcUrl + "\n";
|
||||||
|
diagDetails += String("shine_server=") + gShineServerUrl + "\n";
|
||||||
|
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
||||||
|
|
||||||
|
if (cleanLogin.isEmpty()) {
|
||||||
|
return failWithDiag("Login is not set");
|
||||||
}
|
}
|
||||||
if (!gSecretConfigured || gRootPrivB58.isEmpty() || gBlockchainPrivB58.isEmpty() || gDevicePrivB58.isEmpty()) {
|
if (!gSecretConfigured || gRootPrivB58.isEmpty() || gBlockchainPrivB58.isEmpty() || gDevicePrivB58.isEmpty()) {
|
||||||
messageOut = "Secret is not ready";
|
return failWithDiag("Secret is not ready");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
messageOut = "Connect Wi-Fi first";
|
diagDetails += "wifi=disconnected\n";
|
||||||
return false;
|
return failWithDiag("Connect Wi-Fi first");
|
||||||
}
|
}
|
||||||
|
diagDetails += String("wifi=connected:") + WiFi.localIP().toString() + "\n";
|
||||||
if (gSolanaRpcUrl.isEmpty()) {
|
if (gSolanaRpcUrl.isEmpty()) {
|
||||||
messageOut = "Set Solana RPC first";
|
return failWithDiag("Set Solana RPC first");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String existingPda;
|
String existingPda;
|
||||||
@ -1520,11 +1624,13 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
gAccountStatusMessage = "User is already registered";
|
gAccountStatusMessage = "User is already registered";
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
||||||
refreshAccountPdaStatus();
|
refreshAccountPdaStatus();
|
||||||
|
diagDetails += String("user_pda=") + existingPda + "\n";
|
||||||
|
saveRegisterDiag("ok", "User is already registered", diagDetails);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (pdaCheckMessage == "Failed to derive user PDA" || pdaCheckMessage == "Failed to check PDA" || pdaCheckMessage == "Unexpected getAccountInfo response") {
|
if (pdaCheckMessage == "Failed to derive user PDA" || pdaCheckMessage == "Failed to check PDA" || pdaCheckMessage == "Unexpected getAccountInfo response") {
|
||||||
messageOut = pdaCheckMessage;
|
diagDetails += String("user_pda_check=") + pdaCheckMessage + "\n";
|
||||||
return false;
|
return failWithDiag(pdaCheckMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t userPda[32];
|
uint8_t userPda[32];
|
||||||
@ -1540,10 +1646,44 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
!findProgramAddress({
|
!findProgramAddress({
|
||||||
std::vector<uint8_t>((const uint8_t *)kPaymentsInflowSeed, (const uint8_t *)kPaymentsInflowSeed + strlen(kPaymentsInflowSeed))
|
std::vector<uint8_t>((const uint8_t *)kPaymentsInflowSeed, (const uint8_t *)kPaymentsInflowSeed + strlen(kPaymentsInflowSeed))
|
||||||
}, kShinePaymentsProgramId, inflowVault)) {
|
}, kShinePaymentsProgramId, inflowVault)) {
|
||||||
messageOut = "Failed to derive required PDAs";
|
return failWithDiag("Failed to derive required PDAs");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String userPdaB58 = bytesToBase58(userPda, 32);
|
||||||
|
String economyConfigB58 = bytesToBase58(economyConfig, 32);
|
||||||
|
String inflowVaultB58 = bytesToBase58(inflowVault, 32);
|
||||||
|
diagDetails += String("user_pda=") + userPdaB58 + "\n";
|
||||||
|
diagDetails += String("users_economy_config_pda=") + economyConfigB58 + "\n";
|
||||||
|
diagDetails += String("inflow_vault_pda=") + inflowVaultB58 + "\n";
|
||||||
|
|
||||||
|
String accountInfoMessage;
|
||||||
|
String ownerValue;
|
||||||
|
bool ownerExists = false;
|
||||||
|
if (!loadAccountOwner(userPdaB58, ownerValue, ownerExists, accountInfoMessage)) {
|
||||||
|
diagDetails += String("user_pda_rpc_error=") + accountInfoMessage + "\n";
|
||||||
|
return failWithDiag(accountInfoMessage);
|
||||||
|
}
|
||||||
|
diagDetails += String("user_pda_exists=") + (ownerExists ? "true" : "false") + "\n";
|
||||||
|
if (ownerExists) {
|
||||||
|
diagDetails += String("user_pda_owner=") + ownerValue + "\n";
|
||||||
|
return failWithDiag("User PDA already exists on RPC");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t registrationFeeLamports = 0;
|
||||||
|
uint64_t lamportsPerLimitStep = 0;
|
||||||
|
uint64_t startBonusLimit = 0;
|
||||||
|
if (!loadUsersEconomyConfigState(economyConfigB58,
|
||||||
|
registrationFeeLamports,
|
||||||
|
lamportsPerLimitStep,
|
||||||
|
startBonusLimit,
|
||||||
|
accountInfoMessage)) {
|
||||||
|
diagDetails += String("users_economy_config_error=") + accountInfoMessage + "\n";
|
||||||
|
return failWithDiag(accountInfoMessage);
|
||||||
|
}
|
||||||
|
diagDetails += String("registration_fee_lamports=") + String((unsigned long long)registrationFeeLamports) + "\n";
|
||||||
|
diagDetails += String("lamports_per_limit_step=") + String((unsigned long long)lamportsPerLimitStep) + "\n";
|
||||||
|
diagDetails += String("start_bonus_limit=") + String((unsigned long long)startBonusLimit) + "\n";
|
||||||
|
|
||||||
uint8_t rootSeed[32] = {};
|
uint8_t rootSeed[32] = {};
|
||||||
uint8_t rootPub[32] = {};
|
uint8_t rootPub[32] = {};
|
||||||
uint8_t rootSec[64] = {};
|
uint8_t rootSec[64] = {};
|
||||||
@ -1556,9 +1696,11 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) ||
|
if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec) ||
|
||||||
!deriveSeedKeypairFromBase58(gBlockchainPrivB58, blockchainSeed, blockchainPub, blockchainSec) ||
|
!deriveSeedKeypairFromBase58(gBlockchainPrivB58, blockchainSeed, blockchainPub, blockchainSec) ||
|
||||||
!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)) {
|
!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)) {
|
||||||
messageOut = "Failed to restore keys";
|
return failWithDiag("Failed to restore keys");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
diagDetails += String("root_pub=") + bytesToBase58(rootPub, 32) + "\n";
|
||||||
|
diagDetails += String("blockchain_pub=") + bytesToBase58(blockchainPub, 32) + "\n";
|
||||||
|
diagDetails += String("device_pub=") + bytesToBase58(devicePub, 32) + "\n";
|
||||||
|
|
||||||
String blockchainName = cleanLogin + "-001";
|
String blockchainName = cleanLogin + "-001";
|
||||||
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(cleanLogin, blockchainName);
|
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(cleanLogin, blockchainName);
|
||||||
@ -1566,21 +1708,19 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
uint8_t lastBlockSignature[64];
|
uint8_t lastBlockSignature[64];
|
||||||
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
||||||
if (!signMessageEd25519(std::vector<uint8_t>(lastBlockHash, lastBlockHash + 32), blockchainSec, lastBlockSignature)) {
|
if (!signMessageEd25519(std::vector<uint8_t>(lastBlockHash, lastBlockHash + 32), blockchainSec, lastBlockSignature)) {
|
||||||
messageOut = "Failed to sign LastBlockState";
|
return failWithDiag("Failed to sign LastBlockState");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t createdAtMs = shineNowMs();
|
uint64_t createdAtMs = shineNowMs();
|
||||||
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
|
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
|
||||||
cleanLogin, blockchainName, gShineServerUrl,
|
cleanLogin, blockchainName, gShineServerUrl,
|
||||||
rootPub, devicePub, blockchainPub,
|
rootPub, devicePub, blockchainPub,
|
||||||
lastBlockSignature, createdAtMs);
|
lastBlockSignature, startBonusLimit, createdAtMs);
|
||||||
uint8_t unsignedHash[32];
|
uint8_t unsignedHash[32];
|
||||||
uint8_t rootSignature[64];
|
uint8_t rootSignature[64];
|
||||||
sha256calc(unsignedRecord.data(), unsignedRecord.size(), unsignedHash);
|
sha256calc(unsignedRecord.data(), unsignedRecord.size(), unsignedHash);
|
||||||
if (!signMessageEd25519(std::vector<uint8_t>(unsignedHash, unsignedHash + 32), rootSec, rootSignature)) {
|
if (!signMessageEd25519(std::vector<uint8_t>(unsignedHash, unsignedHash + 32), rootSec, rootSignature)) {
|
||||||
messageOut = "Failed to sign PDA record";
|
return failWithDiag("Failed to sign PDA record");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> createData = buildCreateInstructionData(
|
std::vector<uint8_t> createData = buildCreateInstructionData(
|
||||||
@ -1593,8 +1733,10 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
uint8_t recentBlockhash[32];
|
uint8_t recentBlockhash[32];
|
||||||
String recentBlockhash58;
|
String recentBlockhash58;
|
||||||
if (!getLatestBlockhashBytes(recentBlockhash, recentBlockhash58, messageOut)) {
|
if (!getLatestBlockhashBytes(recentBlockhash, recentBlockhash58, messageOut)) {
|
||||||
return false;
|
diagDetails += String("blockhash_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
}
|
}
|
||||||
|
diagDetails += String("recent_blockhash=") + recentBlockhash58 + "\n";
|
||||||
|
|
||||||
std::vector<uint8_t> message = buildLegacyMessage(
|
std::vector<uint8_t> message = buildLegacyMessage(
|
||||||
recentBlockhash,
|
recentBlockhash,
|
||||||
@ -1607,16 +1749,16 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
createData);
|
createData);
|
||||||
uint8_t txSignature[64];
|
uint8_t txSignature[64];
|
||||||
if (!signMessageEd25519(message, deviceSec, txSignature)) {
|
if (!signMessageEd25519(message, deviceSec, txSignature)) {
|
||||||
messageOut = "Failed to sign Solana transaction";
|
return failWithDiag("Failed to sign Solana transaction");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
String txBase64 = encodeTransactionBase64(txSignature, message);
|
String txBase64 = encodeTransactionBase64(txSignature, message);
|
||||||
String signatureB58 = bytesToBase58(txSignature, 64);
|
String signatureB58 = bytesToBase58(txSignature, 64);
|
||||||
|
diagDetails += String("tx_signature=") + signatureB58 + "\n";
|
||||||
|
|
||||||
String payload;
|
String payload;
|
||||||
if (!rpcCallSolana("sendTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"preflightCommitment\":\"confirmed\"}]", payload)) {
|
if (!rpcCallSolana("sendTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"preflightCommitment\":\"confirmed\"}]", payload)) {
|
||||||
messageOut = "RPC did not accept transaction";
|
diagDetails += "send_transaction_rpc_error=true\n";
|
||||||
return false;
|
return failWithDiag("RPC did not accept transaction");
|
||||||
}
|
}
|
||||||
if (rpcResponseHasError(payload)) {
|
if (rpcResponseHasError(payload)) {
|
||||||
if (!extractRpcErrorSummary(payload, messageOut)) {
|
if (!extractRpcErrorSummary(payload, messageOut)) {
|
||||||
@ -1630,13 +1772,15 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
messageOut += " | simulate: " + simulated;
|
messageOut += " | simulate: " + simulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
diagDetails += String("send_transaction_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
}
|
}
|
||||||
if (!awaitTransactionConfirmation(signatureB58, messageOut)) {
|
if (!awaitTransactionConfirmation(signatureB58, messageOut)) {
|
||||||
return false;
|
diagDetails += String("confirmation_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
gUserPdaAddress = bytesToBase58(userPda, 32);
|
gUserPdaAddress = userPdaB58;
|
||||||
gRegistrationSignature = signatureB58;
|
gRegistrationSignature = signatureB58;
|
||||||
gAccountStatusMessage = "Solana registration complete";
|
gAccountStatusMessage = "Solana registration complete";
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
||||||
@ -1646,6 +1790,8 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
refreshAccountPdaStatus();
|
refreshAccountPdaStatus();
|
||||||
messageOut = "Solana registration confirmed";
|
messageOut = "Solana registration confirmed";
|
||||||
|
saveRegisterDiag("ok", messageOut, diagDetails);
|
||||||
|
printRegisterDiagToSerial();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2573,6 +2719,10 @@ static void loadPrefs() {
|
|||||||
gShineStoragePwd = gPrefs.getString("shine_store_pwd", "");
|
gShineStoragePwd = gPrefs.getString("shine_store_pwd", "");
|
||||||
gUserPdaAddress = gPrefs.getString("user_pda", "");
|
gUserPdaAddress = gPrefs.getString("user_pda", "");
|
||||||
gRegistrationSignature = gPrefs.getString("registration_sig", "");
|
gRegistrationSignature = gPrefs.getString("registration_sig", "");
|
||||||
|
gLastRegisterDiagStatus = gPrefs.getString("reg_diag_status", "none");
|
||||||
|
gLastRegisterDiagSummary = gPrefs.getString("reg_diag_summary", "");
|
||||||
|
gLastRegisterDiagDetails = gPrefs.getString("reg_diag_details", "");
|
||||||
|
gLastRegisterDiagTime = gPrefs.getString("reg_diag_time", "");
|
||||||
gBalanceStatusMessage = gDevicePubB58.isEmpty() ? "Balance: secret not set" : "Balance: tap to load";
|
gBalanceStatusMessage = gDevicePubB58.isEmpty() ? "Balance: secret not set" : "Balance: tap to load";
|
||||||
gAccountCheckPending = true;
|
gAccountCheckPending = true;
|
||||||
gLastAccountCheckMs = 0;
|
gLastAccountCheckMs = 0;
|
||||||
@ -2687,6 +2837,71 @@ static String wifiHomeSummary() {
|
|||||||
return String("Wi-Fi (") + gWifiSavedSsid + ") disconnected";
|
return String("Wi-Fi (") + gWifiSavedSsid + ") disconnected";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void saveRegisterDiag(const String &status, const String &summary, const String &details) {
|
||||||
|
gLastRegisterDiagStatus = status;
|
||||||
|
gLastRegisterDiagSummary = summary.length() > 240 ? summary.substring(0, 240) : summary;
|
||||||
|
gLastRegisterDiagDetails = details.length() > 1800 ? details.substring(0, 1800) : details;
|
||||||
|
gLastRegisterDiagTime = String(shineNowMs());
|
||||||
|
gPrefs.putString("reg_diag_status", gLastRegisterDiagStatus);
|
||||||
|
gPrefs.putString("reg_diag_summary", gLastRegisterDiagSummary);
|
||||||
|
gPrefs.putString("reg_diag_details", gLastRegisterDiagDetails);
|
||||||
|
gPrefs.putString("reg_diag_time", gLastRegisterDiagTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearRegisterDiag() {
|
||||||
|
gLastRegisterDiagStatus = "none";
|
||||||
|
gLastRegisterDiagSummary = "";
|
||||||
|
gLastRegisterDiagDetails = "";
|
||||||
|
gLastRegisterDiagTime = "";
|
||||||
|
gPrefs.remove("reg_diag_status");
|
||||||
|
gPrefs.remove("reg_diag_summary");
|
||||||
|
gPrefs.remove("reg_diag_details");
|
||||||
|
gPrefs.remove("reg_diag_time");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printRegisterDiagToSerial() {
|
||||||
|
Serial.println("LAST_REGISTER_DIAG_BEGIN");
|
||||||
|
Serial.println(String("status=") + gLastRegisterDiagStatus);
|
||||||
|
Serial.println(String("time_ms=") + gLastRegisterDiagTime);
|
||||||
|
Serial.println(String("summary=") + gLastRegisterDiagSummary);
|
||||||
|
Serial.println("details<<");
|
||||||
|
Serial.println(gLastRegisterDiagDetails);
|
||||||
|
Serial.println(">>details");
|
||||||
|
Serial.println("LAST_REGISTER_DIAG_END");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleUsbSerialCommands() {
|
||||||
|
while (Serial.available() > 0) {
|
||||||
|
char ch = (char)Serial.read();
|
||||||
|
if (ch == '\r') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch == '\n') {
|
||||||
|
String cmd = gSerialCommandBuffer;
|
||||||
|
gSerialCommandBuffer = "";
|
||||||
|
cmd.trim();
|
||||||
|
cmd.toLowerCase();
|
||||||
|
if (cmd.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cmd == "last_error" || cmd == "last_diag" || cmd == "reg_diag") {
|
||||||
|
printRegisterDiagToSerial();
|
||||||
|
} else if (cmd == "clear_error" || cmd == "clear_diag") {
|
||||||
|
clearRegisterDiag();
|
||||||
|
Serial.println("register diag cleared");
|
||||||
|
} else if (cmd == "help") {
|
||||||
|
Serial.println("commands: last_error, last_diag, reg_diag, clear_error, clear_diag, help");
|
||||||
|
} else {
|
||||||
|
Serial.println(String("unknown command: ") + cmd);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gSerialCommandBuffer.length() < 120) {
|
||||||
|
gSerialCommandBuffer += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String loginDisplayValue() {
|
static String loginDisplayValue() {
|
||||||
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
||||||
}
|
}
|
||||||
@ -4128,9 +4343,11 @@ void setup() {
|
|||||||
|
|
||||||
rebuildScreen();
|
rebuildScreen();
|
||||||
Serial.println("Minimal nav test ready");
|
Serial.println("Minimal nav test ready");
|
||||||
|
Serial.println("USB diag commands: last_error, last_diag, reg_diag, clear_error, help");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
handleUsbSerialCommands();
|
||||||
lv_timer_handler();
|
lv_timer_handler();
|
||||||
manageWifiReconnect();
|
manageWifiReconnect();
|
||||||
manageAccountPdaRefresh();
|
manageAccountPdaRefresh();
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.168
|
client.version=1.2.169
|
||||||
server.version=1.2.157
|
server.version=1.2.158
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user