ESP32: добавить flow обновления homeserver в user PDA
This commit is contained in:
parent
19fd5611b2
commit
a1da814030
@ -14,14 +14,19 @@
|
|||||||
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
||||||
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной; также убедиться, что между двумя нижними кнопками есть небольшой зазор.
|
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной; также убедиться, что между двумя нижними кнопками есть небольшой зазор.
|
||||||
10. Нажать кнопку регистрации и убедиться, что открывается отдельный экран проверки, где ещё раз видно `login`, статус свободного `PDA`, баланс, `homeserver1` с пометкой о стандартном значении и сообщение, если `Wi-Fi` не подключён.
|
10. Нажать кнопку регистрации и убедиться, что открывается отдельный экран проверки, где ещё раз видно `login`, статус свободного `PDA`, баланс, `homeserver1` с пометкой о стандартном значении и сообщение, если `Wi-Fi` не подключён.
|
||||||
11. На экране проверки нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ` и убедиться, что после этого появляется отдельный экран результата с успехом либо подробной ошибкой.
|
11. На экране проверки нажать `REGISTER IN SHINE` и убедиться, что после этого появляется отдельный экран результата с успехом либо подробной ошибкой.
|
||||||
12. После успешной регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана, сохранена в `NVS` и соответствует `device`-адресу устройства, а `tx signature` тоже сохранён.
|
12. После успешной регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана, сохранена в `NVS` и соответствует `device`-адресу устройства, а `tx signature` тоже сохранён.
|
||||||
13. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
13. После успешной регистрации вернуться на `HOME` и проверить новую жёлтую кнопку:
|
||||||
14. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
- если в `PDA` ещё нет текущего homeserver, должна появиться `ADD HOMESERVER`;
|
||||||
15. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
- если ключ homeserver в `PDA` не совпадает с локальным секретом, должна появиться `FIX HOMESERVER PASSWORD`.
|
||||||
|
14. Нажать жёлтую кнопку и убедиться, что открывается отдельный экран пояснения, а затем экран результата обновления `PDA`.
|
||||||
|
15. После успешного `ADD/FIX HOMESERVER` проверить, что основной экран больше не показывает `homeserver not in PDA` или `homeserver key mismatch`, а `SHiNE` может перейти к авторизации.
|
||||||
|
16. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
||||||
|
17. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
||||||
|
18. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
||||||
|
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации homeserver.
|
новый `ESP32`-скетч стабильно запускается, показывает читаемый англоязычный интерфейс, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI, позволяет вручную пройти on-chain регистрацию пользователя и затем отдельным действием записать/исправить homeserver-сессию в `shine_users`.
|
||||||
|
|
||||||
- статус:
|
- статус:
|
||||||
pending
|
pending
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
# ESP32: добавление и исправление homeserver в user PDA
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
в основном ESP32-скетче добавлен отдельный flow после регистрации пользователя: если homeserver-сессия отсутствует в `shine_users` PDA или её ключ не совпадает с локальным секретом, на главном экране показывается жёлтая кнопка `ADD HOMESERVER` или `FIX HOMESERVER PASSWORD`. Действие открывает отдельный экран объяснения, затем отправляет `update_user_pda` и показывает экран результата.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. Зарегистрировать пользователя или взять уже существующего пользователя без корректной homeserver-сессии.
|
||||||
|
2. На `HOME` убедиться, что вместо кнопки регистрации появилась жёлтая кнопка:
|
||||||
|
`ADD HOMESERVER` либо `FIX HOMESERVER PASSWORD`.
|
||||||
|
3. Нажать её и проверить, что открывается отдельный экран с пояснением причины и кнопкой действия.
|
||||||
|
4. Выполнить действие и дождаться экрана результата.
|
||||||
|
5. После успеха проверить, что:
|
||||||
|
- `homeserver not in PDA` или `homeserver key mismatch` исчезли;
|
||||||
|
- в USB-диагностике есть успешный `tx_signature`;
|
||||||
|
- `SHiNE` может перейти к авторизации на сервере.
|
||||||
|
6. Если действие завершается ошибкой, проверить, что:
|
||||||
|
- текст ошибки показан на экране результата;
|
||||||
|
- команда `last_error` по USB возвращает полный сохранённый payload.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
устройство после обычной регистрации пользователя способно отдельной транзакцией добавить или исправить homeserver-сессию в `shine_users` PDA, а ошибки этого шага сохраняются в ту же USB/NVS-диагностику.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@ -97,6 +97,8 @@ enum Screen {
|
|||||||
SCREEN_TEXT_EDIT,
|
SCREEN_TEXT_EDIT,
|
||||||
SCREEN_REGISTER_ACCOUNT_CONFIRM,
|
SCREEN_REGISTER_ACCOUNT_CONFIRM,
|
||||||
SCREEN_REGISTER_ACCOUNT_RESULT,
|
SCREEN_REGISTER_ACCOUNT_RESULT,
|
||||||
|
SCREEN_HOMESERVER_PDA_CONFIRM,
|
||||||
|
SCREEN_HOMESERVER_PDA_RESULT,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SwipeDirection {
|
enum SwipeDirection {
|
||||||
@ -136,6 +138,9 @@ enum ActionId {
|
|||||||
ACTION_REFRESH_BALANCE,
|
ACTION_REFRESH_BALANCE,
|
||||||
ACTION_REGISTER_ACCOUNT,
|
ACTION_REGISTER_ACCOUNT,
|
||||||
ACTION_REGISTER_ACCOUNT_EXECUTE,
|
ACTION_REGISTER_ACCOUNT_EXECUTE,
|
||||||
|
ACTION_HOMESERVER_PDA_ACTION,
|
||||||
|
ACTION_HOMESERVER_PDA_ADD_EXECUTE,
|
||||||
|
ACTION_HOMESERVER_PDA_FIX_EXECUTE,
|
||||||
ACTION_EDITOR_SAVE,
|
ACTION_EDITOR_SAVE,
|
||||||
ACTION_EDITOR_CANCEL,
|
ACTION_EDITOR_CANCEL,
|
||||||
};
|
};
|
||||||
@ -178,11 +183,29 @@ struct ShinePdaSessionRecord {
|
|||||||
struct ShinePdaUserState {
|
struct ShinePdaUserState {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
String login;
|
String login;
|
||||||
|
uint64_t createdAtMs = 0;
|
||||||
|
uint64_t updatedAtMs = 0;
|
||||||
|
uint32_t recordNumber = 0;
|
||||||
|
uint8_t prevRecordHash32[32] = {};
|
||||||
|
uint8_t blockchainType = 1;
|
||||||
bool isServer = false;
|
bool isServer = false;
|
||||||
|
uint8_t addressFormatType = 0;
|
||||||
|
uint8_t addressFormatVersion = 0;
|
||||||
String serverAddress;
|
String serverAddress;
|
||||||
|
std::vector<String> syncServers;
|
||||||
|
std::vector<String> accessServers;
|
||||||
|
uint8_t sessionsMode = 1;
|
||||||
|
uint8_t trustedCount = 0;
|
||||||
uint8_t rootKey32[32] = {};
|
uint8_t rootKey32[32] = {};
|
||||||
uint8_t deviceKey32[32] = {};
|
uint8_t deviceKey32[32] = {};
|
||||||
uint8_t blockchainKey32[32] = {};
|
uint8_t blockchainKey32[32] = {};
|
||||||
|
String blockchainName;
|
||||||
|
uint64_t paidLimitBytes = 0;
|
||||||
|
uint64_t usedBytes = 0;
|
||||||
|
uint32_t lastBlockNumber = 0;
|
||||||
|
uint8_t lastBlockHash32[32] = {};
|
||||||
|
uint8_t lastBlockSignature64[64] = {};
|
||||||
|
String arweaveTxId;
|
||||||
std::vector<ShinePdaSessionRecord> sessions;
|
std::vector<ShinePdaSessionRecord> sessions;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -256,6 +279,8 @@ static String gAccountPdaStatusMessage = "Account not checked";
|
|||||||
static bool gAccountCheckPending = true;
|
static bool gAccountCheckPending = true;
|
||||||
static unsigned long gLastAccountCheckMs = 0;
|
static unsigned long gLastAccountCheckMs = 0;
|
||||||
static bool gShowRegisterAccountButton = false;
|
static bool gShowRegisterAccountButton = false;
|
||||||
|
static bool gShowHomeserverPdaActionButton = false;
|
||||||
|
static String gHomeserverPdaActionReason;
|
||||||
static String gUserPdaAddress;
|
static String gUserPdaAddress;
|
||||||
static String gRegistrationSignature;
|
static String gRegistrationSignature;
|
||||||
static String gShineStatusLine = "SHiNE: account not configured";
|
static String gShineStatusLine = "SHiNE: account not configured";
|
||||||
@ -267,15 +292,18 @@ 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 gHomeserverPdaActionMessage;
|
||||||
|
static String gHomeserverPdaActionDetail;
|
||||||
|
static bool gHomeserverPdaCanAdd = false;
|
||||||
|
static bool gHomeserverPdaCanFix = false;
|
||||||
|
static String gHomeserverPdaResultMessage;
|
||||||
|
static String gHomeserverPdaResultDetails;
|
||||||
|
static bool gHomeserverPdaResultSuccess = false;
|
||||||
static String gLastRegisterDiagStatus = "none";
|
static String gLastRegisterDiagStatus = "none";
|
||||||
static String gLastRegisterDiagSummary;
|
static String gLastRegisterDiagSummary;
|
||||||
static String gLastRegisterDiagDetails;
|
static String gLastRegisterDiagDetails;
|
||||||
static String gLastRegisterDiagTime;
|
static String gLastRegisterDiagTime;
|
||||||
static String gRegisterTriggerSource = "manual";
|
static String gRegisterTriggerSource = "manual";
|
||||||
static unsigned long gBootMillis = 0;
|
|
||||||
static bool gAutoRegisterTestArmed = true;
|
|
||||||
static bool gAutoRegisterTestStarted = false;
|
|
||||||
static bool gAutoRegisterTestFinished = false;
|
|
||||||
static String gSerialCommandBuffer;
|
static String gSerialCommandBuffer;
|
||||||
static String gShineSessionId;
|
static String gShineSessionId;
|
||||||
static String gShineSessionKey;
|
static String gShineSessionKey;
|
||||||
@ -393,7 +421,11 @@ 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 simulateTransactionPayload(const String &txBase64, String &payloadOut);
|
static bool simulateTransactionPayload(const String &txBase64, String &payloadOut);
|
||||||
static bool simulateTransactionForError(const String &txBase64, String &messageOut);
|
static bool simulateTransactionForError(const String &txBase64, String &messageOut);
|
||||||
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login, const String &blockchainName);
|
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login,
|
||||||
|
const String &blockchainName,
|
||||||
|
uint32_t lastBlockNumber = 0,
|
||||||
|
const uint8_t *lastBlockHash32 = nullptr,
|
||||||
|
uint64_t usedBytes = 0);
|
||||||
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
||||||
const String &login,
|
const String &login,
|
||||||
const String &blockchainName,
|
const String &blockchainName,
|
||||||
@ -415,6 +447,12 @@ static std::vector<uint8_t> buildCreateInstructionData(
|
|||||||
const uint8_t rootSignature[64],
|
const uint8_t rootSignature[64],
|
||||||
uint64_t createdAtMs);
|
uint64_t createdAtMs);
|
||||||
static std::vector<uint8_t> buildEd25519InstructionData(const uint8_t signature[64], const uint8_t publicKey[32], const uint8_t messageHash[32]);
|
static std::vector<uint8_t> buildEd25519InstructionData(const uint8_t signature[64], const uint8_t publicKey[32], const uint8_t messageHash[32]);
|
||||||
|
static std::vector<uint8_t> serializeUnsignedRecordState(const ShinePdaUserState &state);
|
||||||
|
static std::vector<uint8_t> buildUpdateInstructionData(const ShinePdaUserState &state,
|
||||||
|
uint32_t nextVersion,
|
||||||
|
uint64_t updatedAtMs,
|
||||||
|
const uint8_t prevHash32[32],
|
||||||
|
const uint8_t rootSignature64[64]);
|
||||||
static std::vector<uint8_t> buildLegacyMessage(
|
static std::vector<uint8_t> buildLegacyMessage(
|
||||||
const uint8_t recentBlockhash[32],
|
const uint8_t recentBlockhash[32],
|
||||||
const uint8_t devicePub[32],
|
const uint8_t devicePub[32],
|
||||||
@ -424,11 +462,21 @@ static std::vector<uint8_t> buildLegacyMessage(
|
|||||||
const std::vector<uint8_t> &edRootData,
|
const std::vector<uint8_t> &edRootData,
|
||||||
const std::vector<uint8_t> &edBchData,
|
const std::vector<uint8_t> &edBchData,
|
||||||
const std::vector<uint8_t> &createData);
|
const std::vector<uint8_t> &createData);
|
||||||
|
static std::vector<uint8_t> buildUpdateLegacyMessage(
|
||||||
|
const uint8_t recentBlockhash[32],
|
||||||
|
const uint8_t devicePub[32],
|
||||||
|
const uint8_t userPda[32],
|
||||||
|
const uint8_t inflowVault[32],
|
||||||
|
const uint8_t economyConfig[32],
|
||||||
|
const std::vector<uint8_t> &edRootData,
|
||||||
|
const std::vector<uint8_t> &edBchData,
|
||||||
|
const std::vector<uint8_t> &updateData);
|
||||||
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]);
|
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]);
|
||||||
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 executeRegisterAccountFlow(const char *triggerSource, bool showResultScreen);
|
static void executeRegisterAccountFlow(const char *triggerSource, bool showResultScreen);
|
||||||
|
static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messageOut);
|
||||||
static void loadRegisterDiagDetailsFromPrefs();
|
static void loadRegisterDiagDetailsFromPrefs();
|
||||||
static void saveRegisterDiagDetailsToPrefs(const String &details);
|
static void saveRegisterDiagDetailsToPrefs(const String &details);
|
||||||
static void clearRegisterDiagDetailsFromPrefs();
|
static void clearRegisterDiagDetailsFromPrefs();
|
||||||
@ -437,6 +485,7 @@ static void printRegisterDiagToSerial();
|
|||||||
static void clearRegisterDiag();
|
static void clearRegisterDiag();
|
||||||
static void handleUsbSerialCommands();
|
static void handleUsbSerialCommands();
|
||||||
static void prepareRegisterAccountScreen();
|
static void prepareRegisterAccountScreen();
|
||||||
|
static void prepareHomeserverPdaActionScreen();
|
||||||
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]);
|
||||||
static bool deriveSeedKeypairFromBase58(const String &seedB58, uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
static bool deriveSeedKeypairFromBase58(const String &seedB58, uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]);
|
||||||
@ -912,6 +961,15 @@ static void markAccountStateDirty() {
|
|||||||
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
||||||
gAccountPdaStatusMessage = "Account not checked";
|
gAccountPdaStatusMessage = "Account not checked";
|
||||||
gShowRegisterAccountButton = false;
|
gShowRegisterAccountButton = false;
|
||||||
|
gShowHomeserverPdaActionButton = false;
|
||||||
|
gHomeserverPdaActionReason = "";
|
||||||
|
gHomeserverPdaActionMessage = "";
|
||||||
|
gHomeserverPdaActionDetail = "";
|
||||||
|
gHomeserverPdaCanAdd = false;
|
||||||
|
gHomeserverPdaCanFix = false;
|
||||||
|
gHomeserverPdaResultMessage = "";
|
||||||
|
gHomeserverPdaResultDetails = "";
|
||||||
|
gHomeserverPdaResultSuccess = false;
|
||||||
gUserPdaAddress = "";
|
gUserPdaAddress = "";
|
||||||
gRegistrationSignature = "";
|
gRegistrationSignature = "";
|
||||||
gRegisterConfirmMessage = "";
|
gRegisterConfirmMessage = "";
|
||||||
@ -1170,15 +1228,160 @@ static bool rpcResponseHasError(const String &payload) {
|
|||||||
return payload.indexOf("\"error\"") >= 0;
|
return payload.indexOf("\"error\"") >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login, const String &blockchainName) {
|
static std::vector<uint8_t> buildLastBlockStateBytes(const String &login,
|
||||||
|
const String &blockchainName,
|
||||||
|
uint32_t lastBlockNumber,
|
||||||
|
const uint8_t *lastBlockHash32,
|
||||||
|
uint64_t usedBytes) {
|
||||||
std::vector<uint8_t> out;
|
std::vector<uint8_t> out;
|
||||||
out.reserve(80);
|
out.reserve(80);
|
||||||
pushFixed(out, (const uint8_t *)kLastBlockPrefix, strlen(kLastBlockPrefix));
|
pushFixed(out, (const uint8_t *)kLastBlockPrefix, strlen(kLastBlockPrefix));
|
||||||
pushStrU8(out, login);
|
pushStrU8(out, login);
|
||||||
pushStrU8(out, blockchainName);
|
pushStrU8(out, blockchainName);
|
||||||
pushU32LE(out, 0);
|
pushU32LE(out, lastBlockNumber);
|
||||||
|
if (lastBlockHash32) {
|
||||||
|
pushFixed(out, lastBlockHash32, 32);
|
||||||
|
} else {
|
||||||
out.insert(out.end(), 32, 0);
|
out.insert(out.end(), 32, 0);
|
||||||
|
}
|
||||||
|
pushU64LE(out, usedBytes);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> serializeUnsignedRecordState(const ShinePdaUserState &state) {
|
||||||
|
std::vector<uint8_t> out;
|
||||||
|
out.reserve(640);
|
||||||
|
pushFixed(out, (const uint8_t *)"SHiNE", 5);
|
||||||
|
out.push_back(1);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(0);
|
||||||
|
pushU64LE(out, state.createdAtMs);
|
||||||
|
pushU64LE(out, state.updatedAtMs);
|
||||||
|
pushU32LE(out, state.recordNumber);
|
||||||
|
pushFixed(out, state.prevRecordHash32, 32);
|
||||||
|
pushStrU8(out, state.login);
|
||||||
|
out.push_back(state.isServer ? 7 : 6);
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeRootKey);
|
||||||
|
out.push_back(0);
|
||||||
|
pushFixed(out, state.rootKey32, 32);
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeDeviceKey);
|
||||||
|
out.push_back(0);
|
||||||
|
pushFixed(out, state.deviceKey32, 32);
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeBlockchainRegistry);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(1);
|
||||||
|
out.push_back(state.blockchainType);
|
||||||
|
pushStrU8(out, state.blockchainName);
|
||||||
|
pushFixed(out, state.blockchainKey32, 32);
|
||||||
|
pushU64LE(out, state.paidLimitBytes);
|
||||||
|
pushU64LE(out, state.usedBytes);
|
||||||
|
pushU32LE(out, state.lastBlockNumber);
|
||||||
|
pushFixed(out, state.lastBlockHash32, 32);
|
||||||
|
pushFixed(out, state.lastBlockSignature64, 64);
|
||||||
|
if (state.arweaveTxId.isEmpty()) {
|
||||||
|
out.push_back(0);
|
||||||
|
} else {
|
||||||
|
out.push_back(1);
|
||||||
|
pushStrU8(out, state.arweaveTxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.isServer) {
|
||||||
|
out.push_back(kBlockTypeServerProfile);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(1);
|
||||||
|
out.push_back(state.addressFormatType);
|
||||||
|
out.push_back(state.addressFormatVersion);
|
||||||
|
pushStrU8(out, state.serverAddress);
|
||||||
|
out.push_back((uint8_t)state.syncServers.size());
|
||||||
|
for (const auto &value : state.syncServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeAccessServers);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back((uint8_t)state.accessServers.size());
|
||||||
|
for (const auto &value : state.accessServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeSessions);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(state.sessionsMode);
|
||||||
|
out.push_back((uint8_t)state.sessions.size());
|
||||||
|
for (const auto &session : state.sessions) {
|
||||||
|
out.push_back(session.sessionType);
|
||||||
|
out.push_back(session.sessionVersion);
|
||||||
|
pushStrU8(out, session.sessionName);
|
||||||
|
pushFixed(out, session.sessionPubKey32, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_back(kBlockTypeTrustedState);
|
||||||
|
out.push_back(0);
|
||||||
|
out.push_back(state.trustedCount);
|
||||||
|
|
||||||
|
uint16_t recordLen = (uint16_t)(out.size() + 64);
|
||||||
|
out[7] = (uint8_t)(recordLen & 0xFF);
|
||||||
|
out[8] = (uint8_t)((recordLen >> 8) & 0xFF);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> buildUpdateInstructionData(const ShinePdaUserState &state,
|
||||||
|
uint32_t nextVersion,
|
||||||
|
uint64_t updatedAtMs,
|
||||||
|
const uint8_t prevHash32[32],
|
||||||
|
const uint8_t rootSignature64[64]) {
|
||||||
|
std::vector<uint8_t> out;
|
||||||
|
out.reserve(640);
|
||||||
|
out.push_back(4);
|
||||||
|
pushStrU8(out, state.login);
|
||||||
|
pushFixed(out, state.rootKey32, 32);
|
||||||
|
pushU64LE(out, state.createdAtMs);
|
||||||
|
pushU64LE(out, updatedAtMs);
|
||||||
|
pushU32LE(out, nextVersion);
|
||||||
|
pushFixed(out, prevHash32, 32);
|
||||||
pushU64LE(out, 0);
|
pushU64LE(out, 0);
|
||||||
|
pushFixed(out, state.deviceKey32, 32);
|
||||||
|
pushFixed(out, state.blockchainKey32, 32);
|
||||||
|
pushStrU8(out, state.blockchainName);
|
||||||
|
pushU64LE(out, state.usedBytes);
|
||||||
|
pushU32LE(out, state.lastBlockNumber);
|
||||||
|
pushFixed(out, state.lastBlockHash32, 32);
|
||||||
|
pushFixed(out, state.lastBlockSignature64, 64);
|
||||||
|
if (state.arweaveTxId.isEmpty()) {
|
||||||
|
out.push_back(0);
|
||||||
|
} else {
|
||||||
|
out.push_back(1);
|
||||||
|
pushStrU8(out, state.arweaveTxId);
|
||||||
|
}
|
||||||
|
out.push_back(state.isServer ? 1 : 0);
|
||||||
|
if (state.isServer) {
|
||||||
|
out.push_back(state.addressFormatType);
|
||||||
|
out.push_back(state.addressFormatVersion);
|
||||||
|
pushStrU8(out, state.serverAddress);
|
||||||
|
out.push_back((uint8_t)state.syncServers.size());
|
||||||
|
for (const auto &value : state.syncServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push_back((uint8_t)state.accessServers.size());
|
||||||
|
for (const auto &value : state.accessServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
|
out.push_back(state.sessionsMode);
|
||||||
|
out.push_back((uint8_t)state.sessions.size());
|
||||||
|
for (const auto &session : state.sessions) {
|
||||||
|
out.push_back(session.sessionType);
|
||||||
|
out.push_back(session.sessionVersion);
|
||||||
|
pushStrU8(out, session.sessionName);
|
||||||
|
pushFixed(out, session.sessionPubKey32, 32);
|
||||||
|
}
|
||||||
|
out.push_back(state.trustedCount);
|
||||||
|
pushFixed(out, rootSignature64, 64);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1585,6 +1788,69 @@ static std::vector<uint8_t> buildLegacyMessage(
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> buildUpdateLegacyMessage(
|
||||||
|
const uint8_t recentBlockhash[32],
|
||||||
|
const uint8_t devicePub[32],
|
||||||
|
const uint8_t userPda[32],
|
||||||
|
const uint8_t inflowVault[32],
|
||||||
|
const uint8_t economyConfig[32],
|
||||||
|
const std::vector<uint8_t> &edRootData,
|
||||||
|
const std::vector<uint8_t> &edBchData,
|
||||||
|
const std::vector<uint8_t> &updateData) {
|
||||||
|
uint8_t systemProgram[32];
|
||||||
|
uint8_t ed25519Program[32];
|
||||||
|
uint8_t usersProgram[32];
|
||||||
|
uint8_t sysvarInstructions[32];
|
||||||
|
base58ToFixed32(kSystemProgramId, systemProgram);
|
||||||
|
base58ToFixed32(kEd25519ProgramId, ed25519Program);
|
||||||
|
base58ToFixed32(kShineUsersProgramId, usersProgram);
|
||||||
|
base58ToFixed32(kSysvarInstructionsId, sysvarInstructions);
|
||||||
|
|
||||||
|
std::vector<std::vector<uint8_t>> accountKeys;
|
||||||
|
accountKeys.emplace_back(devicePub, devicePub + 32);
|
||||||
|
accountKeys.emplace_back(userPda, userPda + 32);
|
||||||
|
accountKeys.emplace_back(inflowVault, inflowVault + 32);
|
||||||
|
accountKeys.emplace_back(systemProgram, systemProgram + 32);
|
||||||
|
accountKeys.emplace_back(economyConfig, economyConfig + 32);
|
||||||
|
accountKeys.emplace_back(ed25519Program, ed25519Program + 32);
|
||||||
|
accountKeys.emplace_back(usersProgram, usersProgram + 32);
|
||||||
|
accountKeys.emplace_back(sysvarInstructions, sysvarInstructions + 32);
|
||||||
|
|
||||||
|
std::vector<uint8_t> msg;
|
||||||
|
msg.reserve(512);
|
||||||
|
msg.push_back(1);
|
||||||
|
msg.push_back(0);
|
||||||
|
msg.push_back(5);
|
||||||
|
shortVecEncode(accountKeys.size(), msg);
|
||||||
|
for (const auto &key : accountKeys) {
|
||||||
|
msg.insert(msg.end(), key.begin(), key.end());
|
||||||
|
}
|
||||||
|
msg.insert(msg.end(), recentBlockhash, recentBlockhash + 32);
|
||||||
|
shortVecEncode(3, msg);
|
||||||
|
|
||||||
|
msg.push_back(5);
|
||||||
|
msg.push_back(0);
|
||||||
|
shortVecEncode(edRootData.size(), msg);
|
||||||
|
msg.insert(msg.end(), edRootData.begin(), edRootData.end());
|
||||||
|
|
||||||
|
msg.push_back(5);
|
||||||
|
msg.push_back(0);
|
||||||
|
shortVecEncode(edBchData.size(), msg);
|
||||||
|
msg.insert(msg.end(), edBchData.begin(), edBchData.end());
|
||||||
|
|
||||||
|
msg.push_back(6);
|
||||||
|
msg.push_back(6);
|
||||||
|
msg.push_back(0);
|
||||||
|
msg.push_back(1);
|
||||||
|
msg.push_back(3);
|
||||||
|
msg.push_back(2);
|
||||||
|
msg.push_back(7);
|
||||||
|
msg.push_back(4);
|
||||||
|
shortVecEncode(updateData.size(), msg);
|
||||||
|
msg.insert(msg.end(), updateData.begin(), updateData.end());
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]) {
|
static bool signMessageEd25519(const std::vector<uint8_t> &message, const uint8_t secretKey[64], uint8_t signature[64]) {
|
||||||
return crypto_sign_ed25519_detached(signature, nullptr, message.data(), (unsigned long long)message.size(), secretKey) == 0;
|
return crypto_sign_ed25519_detached(signature, nullptr, message.data(), (unsigned long long)message.size(), secretKey) == 0;
|
||||||
}
|
}
|
||||||
@ -1632,7 +1898,6 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
|
|
||||||
String cleanLogin = normalizeLoginValue(gLoginValue);
|
String cleanLogin = normalizeLoginValue(gLoginValue);
|
||||||
diagDetails += String("trigger=") + gRegisterTriggerSource + "\n";
|
diagDetails += String("trigger=") + gRegisterTriggerSource + "\n";
|
||||||
diagDetails += String("boot_millis=") + String(gBootMillis) + "\n";
|
|
||||||
diagDetails += String("test_uptime_ms=") + String(millis()) + "\n";
|
diagDetails += String("test_uptime_ms=") + String(millis()) + "\n";
|
||||||
diagDetails += String("login=") + cleanLogin + "\n";
|
diagDetails += String("login=") + cleanLogin + "\n";
|
||||||
diagDetails += String("rpc=") + gSolanaRpcUrl + "\n";
|
diagDetails += String("rpc=") + gSolanaRpcUrl + "\n";
|
||||||
@ -1746,7 +2011,7 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
|
|
||||||
String blockchainName = cleanLogin + "-001";
|
String blockchainName = cleanLogin + "-001";
|
||||||
diagDetails += String("blockchain_name=") + blockchainName + "\n";
|
diagDetails += String("blockchain_name=") + blockchainName + "\n";
|
||||||
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(cleanLogin, blockchainName);
|
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(cleanLogin, blockchainName, 0, nullptr, 0);
|
||||||
uint8_t lastBlockHash[32];
|
uint8_t lastBlockHash[32];
|
||||||
uint8_t lastBlockSignature[64];
|
uint8_t lastBlockSignature[64];
|
||||||
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
||||||
@ -1947,6 +2212,264 @@ static void prepareRegisterAccountScreen() {
|
|||||||
gRegisterConfirmCanSubmit = true;
|
gRegisterConfirmCanSubmit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void prepareHomeserverPdaActionScreen() {
|
||||||
|
gHomeserverPdaActionMessage = "";
|
||||||
|
gHomeserverPdaActionDetail = "";
|
||||||
|
gHomeserverPdaCanAdd = false;
|
||||||
|
gHomeserverPdaCanFix = false;
|
||||||
|
gHomeserverPdaResultMessage = "";
|
||||||
|
gHomeserverPdaResultDetails = "";
|
||||||
|
gHomeserverPdaResultSuccess = false;
|
||||||
|
|
||||||
|
if (gLoginValue.isEmpty()) {
|
||||||
|
gHomeserverPdaActionMessage = "Login is not set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!gSecretConfigured) {
|
||||||
|
gHomeserverPdaActionMessage = "Secret is not set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gHomeserverValue.isEmpty()) {
|
||||||
|
gHomeserverPdaActionMessage = "Homeserver is not set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
gHomeserverPdaActionMessage = "Wi-Fi is not connected";
|
||||||
|
gHomeserverPdaActionDetail = "Connect Wi-Fi, then try again.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gHomeserverPdaActionReason == "homeserver key mismatch") {
|
||||||
|
gHomeserverPdaActionMessage = "Homeserver key in PDA does not match local secret";
|
||||||
|
gHomeserverPdaActionDetail = String("Homeserver: ") + gHomeserverValue;
|
||||||
|
gHomeserverPdaCanFix = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gHomeserverPdaActionReason == "homeserver not in PDA") {
|
||||||
|
gHomeserverPdaActionMessage = "Homeserver session is missing from user PDA";
|
||||||
|
gHomeserverPdaActionDetail = String("Homeserver: ") + gHomeserverValue;
|
||||||
|
gHomeserverPdaCanAdd = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gHomeserverPdaActionMessage = "Homeserver PDA action is not available";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool updateHomeserverSessionOnSolana(bool requireExisting, String &messageOut) {
|
||||||
|
messageOut = "";
|
||||||
|
String diagDetails;
|
||||||
|
auto failWithDiag = [&](const String &summary) -> bool {
|
||||||
|
messageOut = summary;
|
||||||
|
saveRegisterDiag("error", summary, diagDetails);
|
||||||
|
printRegisterDiagToSerial();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
String cleanLogin = normalizeLoginValue(gLoginValue);
|
||||||
|
diagDetails += String("trigger=") + gRegisterTriggerSource + "\n";
|
||||||
|
diagDetails += String("action=") + (requireExisting ? "fix_homeserver_key" : "add_homeserver") + "\n";
|
||||||
|
diagDetails += String("uptime_ms=") + String(millis()) + "\n";
|
||||||
|
diagDetails += String("login=") + cleanLogin + "\n";
|
||||||
|
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
||||||
|
diagDetails += String("rpc=") + gSolanaRpcUrl + "\n";
|
||||||
|
|
||||||
|
if (cleanLogin.isEmpty()) {
|
||||||
|
return failWithDiag("Login is not set");
|
||||||
|
}
|
||||||
|
if (!gSecretConfigured) {
|
||||||
|
return failWithDiag("Secret is not ready");
|
||||||
|
}
|
||||||
|
if (gHomeserverValue.isEmpty()) {
|
||||||
|
return failWithDiag("Homeserver is not set");
|
||||||
|
}
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
diagDetails += "wifi=disconnected\n";
|
||||||
|
return failWithDiag("Connect Wi-Fi first");
|
||||||
|
}
|
||||||
|
diagDetails += String("wifi=connected:") + WiFi.localIP().toString() + "\n";
|
||||||
|
|
||||||
|
ShinePdaUserState currentState;
|
||||||
|
String stateError;
|
||||||
|
if (!readShineUserPda(cleanLogin, currentState, stateError)) {
|
||||||
|
diagDetails += String("read_pda_error=") + stateError + "\n";
|
||||||
|
return failWithDiag(stateError.isEmpty() ? "Failed to read user PDA" : stateError);
|
||||||
|
}
|
||||||
|
if (!currentState.found) {
|
||||||
|
return failWithDiag("User PDA does not exist yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rootSeed[32] = {};
|
||||||
|
uint8_t rootPub[32] = {};
|
||||||
|
uint8_t rootSec[64] = {};
|
||||||
|
uint8_t deviceSeed[32] = {};
|
||||||
|
uint8_t devicePub[32] = {};
|
||||||
|
uint8_t deviceSec[64] = {};
|
||||||
|
uint8_t homeserverPub[32] = {};
|
||||||
|
if (!deriveSeedKeypairFromBase58(gRootPrivB58, rootSeed, rootPub, rootSec)
|
||||||
|
|| !deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|
||||||
|
|| !base58ToFixed32(gHomeserverPubB58, homeserverPub)) {
|
||||||
|
return failWithDiag("Failed to restore local keys");
|
||||||
|
}
|
||||||
|
if (memcmp(devicePub, currentState.deviceKey32, 32) != 0) {
|
||||||
|
return failWithDiag("Device key does not match PDA");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t userPda[32] = {};
|
||||||
|
uint8_t economyConfig[32] = {};
|
||||||
|
uint8_t inflowVault[32] = {};
|
||||||
|
if (!findProgramAddress({
|
||||||
|
std::vector<uint8_t>((const uint8_t *)kUsersSeedPrefix, (const uint8_t *)kUsersSeedPrefix + strlen(kUsersSeedPrefix)),
|
||||||
|
std::vector<uint8_t>((const uint8_t *)cleanLogin.c_str(), (const uint8_t *)cleanLogin.c_str() + cleanLogin.length())
|
||||||
|
}, kShineUsersProgramId, userPda)
|
||||||
|
|| !findProgramAddress({
|
||||||
|
std::vector<uint8_t>((const uint8_t *)kUsersEconomyConfigSeed, (const uint8_t *)kUsersEconomyConfigSeed + strlen(kUsersEconomyConfigSeed))
|
||||||
|
}, kShineUsersProgramId, economyConfig)
|
||||||
|
|| !findProgramAddress({
|
||||||
|
std::vector<uint8_t>((const uint8_t *)kPaymentsInflowSeed, (const uint8_t *)kPaymentsInflowSeed + strlen(kPaymentsInflowSeed))
|
||||||
|
}, kShinePaymentsProgramId, inflowVault)) {
|
||||||
|
return failWithDiag("Failed to derive required PDAs");
|
||||||
|
}
|
||||||
|
|
||||||
|
String userPdaB58 = bytesToBase58(userPda, 32);
|
||||||
|
diagDetails += String("user_pda=") + userPdaB58 + "\n";
|
||||||
|
diagDetails += String("users_economy_config_pda=") + bytesToBase58(economyConfig, 32) + "\n";
|
||||||
|
diagDetails += String("inflow_vault_pda=") + bytesToBase58(inflowVault, 32) + "\n";
|
||||||
|
|
||||||
|
ShinePdaUserState nextState = currentState;
|
||||||
|
memcpy(nextState.rootKey32, rootPub, 32);
|
||||||
|
memcpy(nextState.deviceKey32, devicePub, 32);
|
||||||
|
nextState.updatedAtMs = shineNowMs();
|
||||||
|
nextState.recordNumber = currentState.recordNumber + 1;
|
||||||
|
if (nextState.sessionsMode == 0) {
|
||||||
|
nextState.sessionsMode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool foundSession = false;
|
||||||
|
for (auto &session : nextState.sessions) {
|
||||||
|
if (session.sessionType == kSessionTypeHomeserver && session.sessionName == gHomeserverValue) {
|
||||||
|
foundSession = true;
|
||||||
|
memcpy(session.sessionPubKey32, homeserverPub, 32);
|
||||||
|
session.sessionVersion = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundSession) {
|
||||||
|
if (requireExisting) {
|
||||||
|
return failWithDiag("Homeserver session is missing in PDA");
|
||||||
|
}
|
||||||
|
ShinePdaSessionRecord rec;
|
||||||
|
rec.sessionType = kSessionTypeHomeserver;
|
||||||
|
rec.sessionVersion = 0;
|
||||||
|
rec.sessionName = gHomeserverValue;
|
||||||
|
memcpy(rec.sessionPubKey32, homeserverPub, 32);
|
||||||
|
nextState.sessions.push_back(rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> oldUnsigned = serializeUnsignedRecordState(currentState);
|
||||||
|
uint8_t prevHash32[32] = {};
|
||||||
|
sha256calc(oldUnsigned.data(), oldUnsigned.size(), prevHash32);
|
||||||
|
memcpy(nextState.prevRecordHash32, prevHash32, 32);
|
||||||
|
diagDetails += String("prev_hash=") + bytesToHexString(prevHash32, 32) + "\n";
|
||||||
|
|
||||||
|
std::vector<uint8_t> newUnsigned = serializeUnsignedRecordState(nextState);
|
||||||
|
uint8_t unsignedHash[32] = {};
|
||||||
|
uint8_t rootSignature[64] = {};
|
||||||
|
sha256calc(newUnsigned.data(), newUnsigned.size(), unsignedHash);
|
||||||
|
if (!signMessageEd25519(std::vector<uint8_t>(unsignedHash, unsignedHash + 32), rootSec, rootSignature)) {
|
||||||
|
return failWithDiag("Failed to sign updated PDA record");
|
||||||
|
}
|
||||||
|
diagDetails += String("unsigned_record_len=") + String((unsigned long)newUnsigned.size()) + "\n";
|
||||||
|
diagDetails += String("unsigned_record_hash=") + bytesToHexString(unsignedHash, 32) + "\n";
|
||||||
|
|
||||||
|
std::vector<uint8_t> lastBlockState = buildLastBlockStateBytes(
|
||||||
|
cleanLogin,
|
||||||
|
currentState.blockchainName,
|
||||||
|
currentState.lastBlockNumber,
|
||||||
|
currentState.lastBlockHash32,
|
||||||
|
currentState.usedBytes);
|
||||||
|
uint8_t lastBlockHash[32] = {};
|
||||||
|
sha256calc(lastBlockState.data(), lastBlockState.size(), lastBlockHash);
|
||||||
|
diagDetails += String("last_block_hash=") + bytesToHexString(lastBlockHash, 32) + "\n";
|
||||||
|
|
||||||
|
std::vector<uint8_t> updateData = buildUpdateInstructionData(
|
||||||
|
nextState,
|
||||||
|
nextState.recordNumber,
|
||||||
|
nextState.updatedAtMs,
|
||||||
|
prevHash32,
|
||||||
|
rootSignature);
|
||||||
|
std::vector<uint8_t> edRootData = buildEd25519InstructionData(rootSignature, rootPub, unsignedHash);
|
||||||
|
std::vector<uint8_t> edBchData = buildEd25519InstructionData(currentState.lastBlockSignature64, currentState.blockchainKey32, lastBlockHash);
|
||||||
|
|
||||||
|
uint8_t recentBlockhash[32] = {};
|
||||||
|
String recentBlockhash58;
|
||||||
|
if (!getLatestBlockhashBytes(recentBlockhash, recentBlockhash58, messageOut)) {
|
||||||
|
diagDetails += String("blockhash_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
|
}
|
||||||
|
diagDetails += String("recent_blockhash=") + recentBlockhash58 + "\n";
|
||||||
|
|
||||||
|
std::vector<uint8_t> message = buildUpdateLegacyMessage(
|
||||||
|
recentBlockhash,
|
||||||
|
devicePub,
|
||||||
|
userPda,
|
||||||
|
inflowVault,
|
||||||
|
economyConfig,
|
||||||
|
edRootData,
|
||||||
|
edBchData,
|
||||||
|
updateData);
|
||||||
|
uint8_t txSignature[64] = {};
|
||||||
|
if (!signMessageEd25519(message, deviceSec, txSignature)) {
|
||||||
|
return failWithDiag("Failed to sign Solana transaction");
|
||||||
|
}
|
||||||
|
String txBase64 = encodeTransactionBase64(txSignature, message);
|
||||||
|
String signatureB58 = bytesToBase58(txSignature, 64);
|
||||||
|
diagDetails += String("tx_signature=") + signatureB58 + "\n";
|
||||||
|
|
||||||
|
String payload;
|
||||||
|
if (!rpcCallSolana("sendTransaction", "[\"" + txBase64 + "\",{\"encoding\":\"base64\",\"preflightCommitment\":\"confirmed\"}]", payload)) {
|
||||||
|
diagDetails += "send_transaction_rpc_error=true\n";
|
||||||
|
return failWithDiag("RPC did not accept transaction");
|
||||||
|
}
|
||||||
|
if (rpcResponseHasError(payload)) {
|
||||||
|
if (!extractRpcErrorSummary(payload, messageOut)) {
|
||||||
|
messageOut = "RPC returned sendTransaction error";
|
||||||
|
}
|
||||||
|
diagDetails += "send_transaction_payload<<\n";
|
||||||
|
diagDetails += payload;
|
||||||
|
diagDetails += "\n>>send_transaction_payload\n";
|
||||||
|
String simulated;
|
||||||
|
String simulatedPayload;
|
||||||
|
if (simulateTransactionPayload(txBase64, simulatedPayload)) {
|
||||||
|
extractRpcErrorSummary(simulatedPayload, simulated);
|
||||||
|
if (messageOut.isEmpty()) {
|
||||||
|
messageOut = simulated;
|
||||||
|
} else if (messageOut.indexOf(simulated) < 0) {
|
||||||
|
messageOut += " | simulate: " + simulated;
|
||||||
|
}
|
||||||
|
diagDetails += "simulate_payload<<\n";
|
||||||
|
diagDetails += simulatedPayload;
|
||||||
|
diagDetails += "\n>>simulate_payload\n";
|
||||||
|
}
|
||||||
|
return failWithDiag(messageOut);
|
||||||
|
}
|
||||||
|
if (!awaitTransactionConfirmation(signatureB58, messageOut)) {
|
||||||
|
diagDetails += String("confirmation_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
gUserPdaAddress = userPdaB58;
|
||||||
|
gRegistrationSignature = signatureB58;
|
||||||
|
saveAccountPrefs();
|
||||||
|
refreshAccountPdaStatus();
|
||||||
|
gHomeserverPdaResultSuccess = true;
|
||||||
|
gHomeserverPdaResultMessage = requireExisting ? "Homeserver key updated in PDA" : "Homeserver added to user PDA";
|
||||||
|
gHomeserverPdaResultDetails = String("tx: ") + abbreviateValue(signatureB58, 12, 8);
|
||||||
|
messageOut = gHomeserverPdaResultMessage;
|
||||||
|
saveRegisterDiag("ok", messageOut, diagDetails);
|
||||||
|
printRegisterDiagToSerial();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUserState &outState, String &errorOut) {
|
static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUserState &outState, String &errorOut) {
|
||||||
outState = ShinePdaUserState{};
|
outState = ShinePdaUserState{};
|
||||||
errorOut = "";
|
errorOut = "";
|
||||||
@ -2014,8 +2537,6 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
uint8_t version = 0;
|
uint8_t version = 0;
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
uint16_t recordLen = 0;
|
uint16_t recordLen = 0;
|
||||||
uint64_t ignoreU64 = 0;
|
|
||||||
uint32_t ignoreU32 = 0;
|
|
||||||
if (!readU8(version) || !readU8(flags) || !readU16(recordLen)) {
|
if (!readU8(version) || !readU8(flags) || !readU16(recordLen)) {
|
||||||
errorOut = "Bad PDA header";
|
errorOut = "Bad PDA header";
|
||||||
return false;
|
return false;
|
||||||
@ -2025,11 +2546,14 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
offset = 9;
|
offset = 9;
|
||||||
if (!readU64(ignoreU64) || !readU64(ignoreU64) || !readU32(ignoreU32)) {
|
if (!readU64(outState.createdAtMs) || !readU64(outState.updatedAtMs) || !readU32(outState.recordNumber)) {
|
||||||
errorOut = "Bad PDA fixed fields";
|
errorOut = "Bad PDA fixed fields";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
offset += 32; // prev hash
|
if (!readBytes(outState.prevRecordHash32, 32)) {
|
||||||
|
errorOut = "Bad PDA prev hash";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!readStringU8(outState.login)) {
|
if (!readStringU8(outState.login)) {
|
||||||
errorOut = "Bad PDA login";
|
errorOut = "Bad PDA login";
|
||||||
return false;
|
return false;
|
||||||
@ -2095,7 +2619,15 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 0) {
|
if (j == 0) {
|
||||||
|
outState.blockchainType = blockchainType;
|
||||||
|
outState.blockchainName = blockchainName;
|
||||||
memcpy(outState.blockchainKey32, blockchainKey, 32);
|
memcpy(outState.blockchainKey32, blockchainKey, 32);
|
||||||
|
outState.paidLimitBytes = paidLimit;
|
||||||
|
outState.usedBytes = usedBytes;
|
||||||
|
outState.lastBlockNumber = lastBlockNumber;
|
||||||
|
memcpy(outState.lastBlockHash32, lastBlockHash, 32);
|
||||||
|
memcpy(outState.lastBlockSignature64, lastBlockSig, 64);
|
||||||
|
outState.arweaveTxId = arweaveTxId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -2108,22 +2640,24 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
}
|
}
|
||||||
outState.isServer = isServer == 1;
|
outState.isServer = isServer == 1;
|
||||||
if (outState.isServer) {
|
if (outState.isServer) {
|
||||||
uint8_t addressFormatType = 0;
|
outState.addressFormatType = 0;
|
||||||
uint8_t addressFormatVersion = 0;
|
outState.addressFormatVersion = 0;
|
||||||
uint8_t syncCount = 0;
|
uint8_t syncCount = 0;
|
||||||
if (!readU8(addressFormatType)
|
if (!readU8(outState.addressFormatType)
|
||||||
|| !readU8(addressFormatVersion)
|
|| !readU8(outState.addressFormatVersion)
|
||||||
|| !readStringU8(outState.serverAddress)
|
|| !readStringU8(outState.serverAddress)
|
||||||
|| !readU8(syncCount)) {
|
|| !readU8(syncCount)) {
|
||||||
errorOut = "Bad server address";
|
errorOut = "Bad server address";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
outState.syncServers.clear();
|
||||||
for (uint8_t j = 0; j < syncCount; ++j) {
|
for (uint8_t j = 0; j < syncCount; ++j) {
|
||||||
String ignoreSync;
|
String syncServer;
|
||||||
if (!readStringU8(ignoreSync)) {
|
if (!readStringU8(syncServer)) {
|
||||||
errorOut = "Bad sync_servers";
|
errorOut = "Bad sync_servers";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
outState.syncServers.push_back(syncServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -2134,12 +2668,14 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
errorOut = "Bad access servers";
|
errorOut = "Bad access servers";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
outState.accessServers.clear();
|
||||||
for (uint8_t j = 0; j < accessCount; ++j) {
|
for (uint8_t j = 0; j < accessCount; ++j) {
|
||||||
String ignoreAccess;
|
String accessServer;
|
||||||
if (!readStringU8(ignoreAccess)) {
|
if (!readStringU8(accessServer)) {
|
||||||
errorOut = "Bad access server item";
|
errorOut = "Bad access server item";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
outState.accessServers.push_back(accessServer);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -2150,6 +2686,7 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
errorOut = "Bad sessions block";
|
errorOut = "Bad sessions block";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
outState.sessionsMode = sessionsMode;
|
||||||
outState.sessions.clear();
|
outState.sessions.clear();
|
||||||
for (uint8_t j = 0; j < sessionsCount; ++j) {
|
for (uint8_t j = 0; j < sessionsCount; ++j) {
|
||||||
ShinePdaSessionRecord rec;
|
ShinePdaSessionRecord rec;
|
||||||
@ -2165,8 +2702,7 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (blockType == kBlockTypeTrustedState) {
|
if (blockType == kBlockTypeTrustedState) {
|
||||||
uint8_t trustedCount = 0;
|
if (!readU8(outState.trustedCount)) {
|
||||||
if (!readU8(trustedCount)) {
|
|
||||||
errorOut = "Bad trusted block";
|
errorOut = "Bad trusted block";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2176,6 +2712,10 @@ static bool parseShineUserPdaBytes(const std::vector<uint8_t> &bytes, ShinePdaUs
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (offset + 64 > recordLen) {
|
||||||
|
errorOut = "Missing PDA signature";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
outState.found = true;
|
outState.found = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2233,6 +2773,10 @@ static bool readShineUserPda(const String &login, ShinePdaUserState &outState, S
|
|||||||
static void refreshAccountPdaStatus() {
|
static void refreshAccountPdaStatus() {
|
||||||
gAccountCheckPending = false;
|
gAccountCheckPending = false;
|
||||||
gShowRegisterAccountButton = false;
|
gShowRegisterAccountButton = false;
|
||||||
|
gShowHomeserverPdaActionButton = false;
|
||||||
|
gHomeserverPdaActionReason = "";
|
||||||
|
gHomeserverPdaCanAdd = false;
|
||||||
|
gHomeserverPdaCanFix = false;
|
||||||
|
|
||||||
if (gLoginValue.isEmpty() || !gSecretConfigured) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured) {
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
||||||
@ -2314,6 +2858,15 @@ static void refreshAccountPdaStatus() {
|
|||||||
if (!mismatch.isEmpty()) {
|
if (!mismatch.isEmpty()) {
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
||||||
gAccountPdaStatusMessage = mismatch;
|
gAccountPdaStatusMessage = mismatch;
|
||||||
|
if (mismatch == "homeserver not in PDA") {
|
||||||
|
gShowHomeserverPdaActionButton = true;
|
||||||
|
gHomeserverPdaActionReason = mismatch;
|
||||||
|
gHomeserverPdaCanAdd = true;
|
||||||
|
} else if (mismatch == "homeserver key mismatch") {
|
||||||
|
gShowHomeserverPdaActionButton = true;
|
||||||
|
gHomeserverPdaActionReason = mismatch;
|
||||||
|
gHomeserverPdaCanFix = true;
|
||||||
|
}
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
@ -2811,6 +3364,15 @@ static void loadPrefs() {
|
|||||||
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
||||||
gAccountPdaStatusMessage = "Account not checked";
|
gAccountPdaStatusMessage = "Account not checked";
|
||||||
gShowRegisterAccountButton = false;
|
gShowRegisterAccountButton = false;
|
||||||
|
gShowHomeserverPdaActionButton = false;
|
||||||
|
gHomeserverPdaActionReason = "";
|
||||||
|
gHomeserverPdaActionMessage = "";
|
||||||
|
gHomeserverPdaActionDetail = "";
|
||||||
|
gHomeserverPdaCanAdd = false;
|
||||||
|
gHomeserverPdaCanFix = false;
|
||||||
|
gHomeserverPdaResultMessage = "";
|
||||||
|
gHomeserverPdaResultDetails = "";
|
||||||
|
gHomeserverPdaResultSuccess = false;
|
||||||
gShineStatusLine = "SHiNE: account not configured";
|
gShineStatusLine = "SHiNE: account not configured";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3534,6 +4096,32 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
case ACTION_REGISTER_ACCOUNT_EXECUTE:
|
case ACTION_REGISTER_ACCOUNT_EXECUTE:
|
||||||
executeRegisterAccountFlow("manual", true);
|
executeRegisterAccountFlow("manual", true);
|
||||||
break;
|
break;
|
||||||
|
case ACTION_HOMESERVER_PDA_ACTION:
|
||||||
|
prepareHomeserverPdaActionScreen();
|
||||||
|
showScreen(SCREEN_HOMESERVER_PDA_CONFIRM);
|
||||||
|
break;
|
||||||
|
case ACTION_HOMESERVER_PDA_ADD_EXECUTE: {
|
||||||
|
gRegisterTriggerSource = "manual-homeserver-add";
|
||||||
|
String updateMessage;
|
||||||
|
if (!updateHomeserverSessionOnSolana(false, updateMessage)) {
|
||||||
|
gHomeserverPdaResultSuccess = false;
|
||||||
|
gHomeserverPdaResultMessage = updateMessage;
|
||||||
|
gHomeserverPdaResultDetails = "";
|
||||||
|
}
|
||||||
|
showScreen(SCREEN_HOMESERVER_PDA_RESULT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_HOMESERVER_PDA_FIX_EXECUTE: {
|
||||||
|
gRegisterTriggerSource = "manual-homeserver-fix";
|
||||||
|
String updateMessage;
|
||||||
|
if (!updateHomeserverSessionOnSolana(true, updateMessage)) {
|
||||||
|
gHomeserverPdaResultSuccess = false;
|
||||||
|
gHomeserverPdaResultMessage = updateMessage;
|
||||||
|
gHomeserverPdaResultDetails = "";
|
||||||
|
}
|
||||||
|
showScreen(SCREEN_HOMESERVER_PDA_RESULT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ACTION_OPEN_WIFI:
|
case ACTION_OPEN_WIFI:
|
||||||
gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
||||||
showScreen(SCREEN_WIFI);
|
showScreen(SCREEN_WIFI);
|
||||||
@ -3800,6 +4388,11 @@ static void drawHome() {
|
|||||||
showMessageAt(shineHomeLine(), 322);
|
showMessageAt(shineHomeLine(), 322);
|
||||||
if (gShowRegisterAccountButton) {
|
if (gShowRegisterAccountButton) {
|
||||||
makeButton("REGISTER ACCOUNT", 20, 360, 210, 78, 0x2A9D8F, ACTION_REGISTER_ACCOUNT, &lv_font_montserrat_20);
|
makeButton("REGISTER ACCOUNT", 20, 360, 210, 78, 0x2A9D8F, ACTION_REGISTER_ACCOUNT, &lv_font_montserrat_20);
|
||||||
|
} else if (gShowHomeserverPdaActionButton) {
|
||||||
|
const char *label = gHomeserverPdaActionReason == "homeserver key mismatch"
|
||||||
|
? "FIX HOMESERVER PASSWORD"
|
||||||
|
: "ADD HOMESERVER";
|
||||||
|
makeButton(label, 20, 360, 210, 78, 0xC59B2A, ACTION_HOMESERVER_PDA_ACTION, &lv_font_montserrat_18);
|
||||||
}
|
}
|
||||||
makeButton("SETTINGS", 250, 360, 210, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
makeButton("SETTINGS", 250, 360, 210, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
@ -4111,6 +4704,43 @@ static void drawRegisterAccountResultScreen() {
|
|||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void drawHomeserverPdaConfirmScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("HOMESERVER PDA", 22, &lv_font_montserrat_24);
|
||||||
|
String topLine = gHomeserverPdaActionMessage.isEmpty() ? String("Homeserver action") : gHomeserverPdaActionMessage;
|
||||||
|
makeBody(topLine.c_str(), 96, 420);
|
||||||
|
if (!gHomeserverPdaActionDetail.isEmpty()) {
|
||||||
|
makeBody(gHomeserverPdaActionDetail.c_str(), 146, 420);
|
||||||
|
}
|
||||||
|
String loginLine = String("Login: ") + loginDisplayValue();
|
||||||
|
String homeserverLine = String("Homeserver: ") + homeserverDisplayValue();
|
||||||
|
makeBody(loginLine.c_str(), 196, 420);
|
||||||
|
makeBody(homeserverLine.c_str(), 236, 420);
|
||||||
|
if (gHomeserverPdaCanAdd) {
|
||||||
|
makeButton("ADD HOMESERVER", 22, 300, 436, 70, 0xC59B2A, ACTION_HOMESERVER_PDA_ADD_EXECUTE, &lv_font_montserrat_20);
|
||||||
|
} else if (gHomeserverPdaCanFix) {
|
||||||
|
makeButton("FIX HOMESERVER PASSWORD", 22, 300, 436, 70, 0xC59B2A, ACTION_HOMESERVER_PDA_FIX_EXECUTE, &lv_font_montserrat_16);
|
||||||
|
} else {
|
||||||
|
makeButton("UNAVAILABLE", 22, 300, 436, 70, 0x4A5560, ACTION_NONE, &lv_font_montserrat_20);
|
||||||
|
}
|
||||||
|
makeButton("BACK", 140, 386, 200, 54, 0x5A6570, ACTION_BACK_HOME, &lv_font_montserrat_20);
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawHomeserverPdaResultScreen() {
|
||||||
|
setRootStyle();
|
||||||
|
makeTitle("HOMESERVER RESULT", 22, &lv_font_montserrat_24);
|
||||||
|
String resultTopLine = gHomeserverPdaResultSuccess ? String("Homeserver PDA updated") : String("Homeserver update failed");
|
||||||
|
makeBody(resultTopLine.c_str(), 96, 420);
|
||||||
|
makeBody(gHomeserverPdaResultMessage.c_str(), 146, 420);
|
||||||
|
if (!gHomeserverPdaResultDetails.isEmpty()) {
|
||||||
|
makeBody(gHomeserverPdaResultDetails.c_str(), 196, 420);
|
||||||
|
}
|
||||||
|
makeButton("BACK HOME", 22, 372, 200, 58, 0x5A6570, ACTION_BACK_HOME, &lv_font_montserrat_20);
|
||||||
|
makeButton("ACCOUNT", 258, 372, 200, 58, 0x2A6F97, ACTION_OPEN_ACCOUNT, &lv_font_montserrat_20);
|
||||||
|
makeVersionTag();
|
||||||
|
}
|
||||||
|
|
||||||
static void drawKeyRow(const char *const *tokens,
|
static void drawKeyRow(const char *const *tokens,
|
||||||
int count,
|
int count,
|
||||||
lv_coord_t x,
|
lv_coord_t x,
|
||||||
@ -4277,6 +4907,12 @@ static void rebuildScreen() {
|
|||||||
case SCREEN_REGISTER_ACCOUNT_RESULT:
|
case SCREEN_REGISTER_ACCOUNT_RESULT:
|
||||||
drawRegisterAccountResultScreen();
|
drawRegisterAccountResultScreen();
|
||||||
break;
|
break;
|
||||||
|
case SCREEN_HOMESERVER_PDA_CONFIRM:
|
||||||
|
drawHomeserverPdaConfirmScreen();
|
||||||
|
break;
|
||||||
|
case SCREEN_HOMESERVER_PDA_RESULT:
|
||||||
|
drawHomeserverPdaResultScreen();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4403,6 +5039,8 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
break;
|
break;
|
||||||
case SCREEN_REGISTER_ACCOUNT_CONFIRM:
|
case SCREEN_REGISTER_ACCOUNT_CONFIRM:
|
||||||
case SCREEN_REGISTER_ACCOUNT_RESULT:
|
case SCREEN_REGISTER_ACCOUNT_RESULT:
|
||||||
|
case SCREEN_HOMESERVER_PDA_CONFIRM:
|
||||||
|
case SCREEN_HOMESERVER_PDA_RESULT:
|
||||||
handleHomeSwipe(swipe);
|
handleHomeSwipe(swipe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4411,7 +5049,6 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
sodium_init();
|
sodium_init();
|
||||||
gBootMillis = millis();
|
|
||||||
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
|
||||||
initPowerManagement();
|
initPowerManagement();
|
||||||
|
|
||||||
@ -4473,15 +5110,6 @@ void loop() {
|
|||||||
manageAccountPdaRefresh();
|
manageAccountPdaRefresh();
|
||||||
manageShineConnection();
|
manageShineConnection();
|
||||||
|
|
||||||
if (gAutoRegisterTestArmed && !gAutoRegisterTestStarted && millis() - gBootMillis >= 15000UL) {
|
|
||||||
gAutoRegisterTestStarted = true;
|
|
||||||
gAutoRegisterTestArmed = false;
|
|
||||||
Serial.println("AUTO_REGISTER_TEST_BEGIN");
|
|
||||||
executeRegisterAccountFlow("auto-boot-15s", false);
|
|
||||||
gAutoRegisterTestFinished = true;
|
|
||||||
Serial.println("AUTO_REGISTER_TEST_END");
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long lastHomeRefreshMs = 0;
|
static unsigned long lastHomeRefreshMs = 0;
|
||||||
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) {
|
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) {
|
||||||
lastHomeRefreshMs = millis();
|
lastHomeRefreshMs = millis();
|
||||||
|
|||||||
@ -162,10 +162,14 @@
|
|||||||
Дополнительная большая кнопка:
|
Дополнительная большая кнопка:
|
||||||
|
|
||||||
- `REGISTER ACCOUNT`
|
- `REGISTER ACCOUNT`
|
||||||
|
- либо жёлтая `ADD HOMESERVER`
|
||||||
|
- либо жёлтая `FIX HOMESERVER PASSWORD`
|
||||||
|
|
||||||
Если регистрация уже сделана:
|
Если регистрация уже сделана:
|
||||||
|
|
||||||
- вместо призыва к регистрации показывается статус `Homeserver активен`.
|
- если пользователь создан, но в `PDA` ещё нет сессии текущего homeserver, показывается жёлтая кнопка `ADD HOMESERVER`;
|
||||||
|
- если в `PDA` есть homeserver с тем же именем, но с другим ключом, показывается жёлтая кнопка `FIX HOMESERVER PASSWORD`;
|
||||||
|
- если и пользователь, и homeserver-сессия уже корректны, вместо призыва к регистрации показывается статус `Homeserver активен`.
|
||||||
- две нижние кнопки внизу экрана не прилегают вплотную друг к другу, между ними есть небольшой зазор.
|
- две нижние кнопки внизу экрана не прилегают вплотную друг к другу, между ними есть небольшой зазор.
|
||||||
|
|
||||||
## Экран REGISTER_ACCOUNT_CONFIRM
|
## Экран REGISTER_ACCOUNT_CONFIRM
|
||||||
@ -215,6 +219,49 @@
|
|||||||
- при ошибке на экране показывается причина отказа;
|
- при ошибке на экране показывается причина отказа;
|
||||||
- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов.
|
- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов.
|
||||||
|
|
||||||
|
## Экран HOMESERVER_PDA_CONFIRM
|
||||||
|
|
||||||
|
Показывает, что именно не так с homeserver-секцией уже существующей пользовательской `PDA`.
|
||||||
|
|
||||||
|
Отображается:
|
||||||
|
|
||||||
|
- причина (`homeserver` отсутствует в `PDA` или ключ не совпадает с локальным секретом);
|
||||||
|
- `login`;
|
||||||
|
- имя `homeserver`;
|
||||||
|
- короткое пояснение, что именно будет сделано.
|
||||||
|
|
||||||
|
Кнопки:
|
||||||
|
|
||||||
|
- `ADD HOMESERVER` или `FIX HOMESERVER PASSWORD`
|
||||||
|
- `BACK`
|
||||||
|
|
||||||
|
Поведение:
|
||||||
|
|
||||||
|
- если `Wi-Fi` не подключён, действие недоступно;
|
||||||
|
- экран не создаёт нового пользователя, а запускает `update_user_pda`;
|
||||||
|
- при `ADD HOMESERVER` в блок `sessions` добавляется запись `session_type=100`;
|
||||||
|
- при `FIX HOMESERVER PASSWORD` обновляется публичный ключ уже существующей записи `homeserver`.
|
||||||
|
|
||||||
|
## Экран HOMESERVER_PDA_RESULT
|
||||||
|
|
||||||
|
Показывает результат обновления `sessions` в пользовательской `PDA`.
|
||||||
|
|
||||||
|
Отображается:
|
||||||
|
|
||||||
|
- успех или ошибка;
|
||||||
|
- короткое сообщение;
|
||||||
|
- при успехе краткий `tx signature`.
|
||||||
|
|
||||||
|
Кнопки:
|
||||||
|
|
||||||
|
- `BACK HOME`
|
||||||
|
- `ACCOUNT`
|
||||||
|
|
||||||
|
Поведение:
|
||||||
|
|
||||||
|
- при ошибке текст ошибки сохраняется в ту же USB/NVS-диагностику, что и регистрация;
|
||||||
|
- после успешного обновления выполняется повторная проверка `PDA`, и основной экран должен перейти в состояние `ok`.
|
||||||
|
|
||||||
## Экран STATUS
|
## Экран STATUS
|
||||||
|
|
||||||
Показывает сводку:
|
Показывает сводку:
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.175
|
client.version=1.2.176
|
||||||
server.version=1.2.164
|
server.version=1.2.165
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user