ESP32: server login и NTP для регистрации
This commit is contained in:
parent
08628704c7
commit
4b94303d67
@ -12,6 +12,8 @@
|
|||||||
#include <mbedtls/base64.h>
|
#include <mbedtls/base64.h>
|
||||||
#include <Ed25519.h>
|
#include <Ed25519.h>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#define XPOWERS_CHIP_AXP2101
|
#define XPOWERS_CHIP_AXP2101
|
||||||
#include "XPowersLib.h"
|
#include "XPowersLib.h"
|
||||||
@ -80,6 +82,7 @@ static const char *kUsersEconomyConfigSeed = "shine_users_economy_config";
|
|||||||
static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault";
|
static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault";
|
||||||
static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK";
|
static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK";
|
||||||
static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress";
|
static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress";
|
||||||
|
static const char *kDefaultShineServerLogin = "shineupme";
|
||||||
static const uint8_t kBlockTypeRecoveryKey = 0;
|
static const uint8_t kBlockTypeRecoveryKey = 0;
|
||||||
static const uint8_t kBlockTypeRootKey = 1;
|
static const uint8_t kBlockTypeRootKey = 1;
|
||||||
static const uint8_t kBlockTypeClientKey = 2;
|
static const uint8_t kBlockTypeClientKey = 2;
|
||||||
@ -322,8 +325,10 @@ static int gScanResultCount = 0;
|
|||||||
static WifiViewMode gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
static WifiViewMode gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
||||||
|
|
||||||
static String gSolanaRpcUrl = "https://api.devnet.solana.com";
|
static String gSolanaRpcUrl = "https://api.devnet.solana.com";
|
||||||
static String gShineServerUrl = "https://shineup.me";
|
static String gShineServerLogin = kDefaultShineServerLogin;
|
||||||
static String gServerStatusMessage = "Edit RPC or shine host";
|
static String gShineServerUrl;
|
||||||
|
static String gResolvedShineServerLogin;
|
||||||
|
static String gServerStatusMessage = "Edit RPC or server login";
|
||||||
static String gLoginValue;
|
static String gLoginValue;
|
||||||
static String gHomeserverValue = "homeserver1";
|
static String gHomeserverValue = "homeserver1";
|
||||||
static bool gSecretConfigured = false;
|
static bool gSecretConfigured = false;
|
||||||
@ -527,6 +532,11 @@ static void pushU64LE(std::vector<uint8_t> &out, uint64_t value);
|
|||||||
static void pushStrU8(std::vector<uint8_t> &out, const String &value);
|
static void pushStrU8(std::vector<uint8_t> &out, const String &value);
|
||||||
static void pushFixed(std::vector<uint8_t> &out, const uint8_t *data, size_t len);
|
static void pushFixed(std::vector<uint8_t> &out, const uint8_t *data, size_t len);
|
||||||
static String bytesToBase58(const uint8_t *data, size_t len);
|
static String bytesToBase58(const uint8_t *data, size_t len);
|
||||||
|
static bool getSystemEpochMs(uint64_t &epochMsOut);
|
||||||
|
static bool ensureNtpTimeSynced(String &errorOut);
|
||||||
|
static bool resolveShineServerUrlFromLogin(const String &serverLogin, String &serverUrlOut, String &errorOut);
|
||||||
|
static String currentShineServerLoginSource();
|
||||||
|
static bool ensureCurrentShineServerUrl(String &errorOut);
|
||||||
static String buildBaseRpcRequest(const char *method, const String ¶msJson);
|
static String buildBaseRpcRequest(const char *method, const String ¶msJson);
|
||||||
static bool rpcCallSolana(const char *method, const String ¶msJson, String &payloadOut);
|
static bool rpcCallSolana(const char *method, const String ¶msJson, String &payloadOut);
|
||||||
static bool rpcResponseHasError(const String &payload);
|
static bool rpcResponseHasError(const String &payload);
|
||||||
@ -550,7 +560,7 @@ static std::vector<uint8_t> buildLastBlockStateBytes(const String &login,
|
|||||||
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
||||||
const String &login,
|
const String &login,
|
||||||
const String &blockchainName,
|
const String &blockchainName,
|
||||||
const String &serverAddress,
|
const std::vector<String> &accessServers,
|
||||||
const uint8_t recoveryPub[32],
|
const uint8_t recoveryPub[32],
|
||||||
const uint8_t rootPub[32],
|
const uint8_t rootPub[32],
|
||||||
const uint8_t clientPub[32],
|
const uint8_t clientPub[32],
|
||||||
@ -561,7 +571,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
static std::vector<uint8_t> buildCreateInstructionData(
|
static std::vector<uint8_t> buildCreateInstructionData(
|
||||||
const String &login,
|
const String &login,
|
||||||
const String &blockchainName,
|
const String &blockchainName,
|
||||||
const String &serverAddress,
|
const std::vector<String> &accessServers,
|
||||||
const uint8_t recoveryPub[32],
|
const uint8_t recoveryPub[32],
|
||||||
const uint8_t rootPub[32],
|
const uint8_t rootPub[32],
|
||||||
const uint8_t clientPub[32],
|
const uint8_t clientPub[32],
|
||||||
@ -890,6 +900,19 @@ static String normalizeLoginValue(const String &value) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isValidShineServerLoginValue(const String &value) {
|
||||||
|
if (value.isEmpty() || value.length() > 20) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < value.length(); ++i) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
if (!(isAlphaNumeric((unsigned char)ch) || ch == '_')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static String abbreviateValue(const String &value, size_t head = 8, size_t tail = 6) {
|
static String abbreviateValue(const String &value, size_t head = 8, size_t tail = 6) {
|
||||||
if (value.length() <= head + tail + 3) {
|
if (value.length() <= head + tail + 3) {
|
||||||
return value;
|
return value;
|
||||||
@ -1622,6 +1645,16 @@ static String balanceHomeLine() {
|
|||||||
return gBalanceStatusMessage;
|
return gBalanceStatusMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String shineServerDisplayLabel() {
|
||||||
|
if (!gShineServerUrl.isEmpty()) {
|
||||||
|
return gShineServerUrl;
|
||||||
|
}
|
||||||
|
if (!currentShineServerLoginSource().isEmpty()) {
|
||||||
|
return currentShineServerLoginSource();
|
||||||
|
}
|
||||||
|
return "not set";
|
||||||
|
}
|
||||||
|
|
||||||
static String wifiHomeRichLine() {
|
static String wifiHomeRichLine() {
|
||||||
String ssid = gWifiSavedSsid.isEmpty() ? String("not configured") : gWifiSavedSsid;
|
String ssid = gWifiSavedSsid.isEmpty() ? String("not configured") : gWifiSavedSsid;
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
@ -1631,7 +1664,7 @@ static String wifiHomeRichLine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String shineHomeRichLine() {
|
static String shineHomeRichLine() {
|
||||||
String serverLabel = gShineServerUrl.isEmpty() ? String("not set") : gShineServerUrl;
|
String serverLabel = shineServerDisplayLabel();
|
||||||
if (gShineStatusLine.endsWith(" connected")) {
|
if (gShineStatusLine.endsWith(" connected")) {
|
||||||
return String("SHiNE: ") + serverLabel + " #38B26D connected#";
|
return String("SHiNE: ") + serverLabel + " #38B26D connected#";
|
||||||
}
|
}
|
||||||
@ -1711,6 +1744,96 @@ static uint64_t shineNowMs() {
|
|||||||
return value > 0 ? (uint64_t)value : (uint64_t)millis();
|
return value > 0 ? (uint64_t)value : (uint64_t)millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool getSystemEpochMs(uint64_t &epochMsOut) {
|
||||||
|
struct timeval tv {};
|
||||||
|
if (gettimeofday(&tv, nullptr) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tv.tv_sec < 1700000000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
epochMsOut = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ensureNtpTimeSynced(String &errorOut) {
|
||||||
|
errorOut = "";
|
||||||
|
uint64_t epochMs = 0;
|
||||||
|
if (getSystemEpochMs(epochMs)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
configTime(0, 0, "pool.ntp.org", "time.cloudflare.com", "time.google.com");
|
||||||
|
for (int i = 0; i < 30; ++i) {
|
||||||
|
delay(500);
|
||||||
|
if (getSystemEpochMs(epochMs)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorOut = "NTP time sync failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool resolveShineServerUrlFromLogin(const String &serverLogin, String &serverUrlOut, String &errorOut) {
|
||||||
|
errorOut = "";
|
||||||
|
serverUrlOut = "";
|
||||||
|
String cleanLogin = normalizeLoginValue(serverLogin);
|
||||||
|
if (cleanLogin.isEmpty()) {
|
||||||
|
errorOut = "Shine server login is not set";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShinePdaUserState serverState;
|
||||||
|
if (!readShineUserPda(cleanLogin, serverState, errorOut)) {
|
||||||
|
if (errorOut.isEmpty()) {
|
||||||
|
errorOut = "Failed to read Shine server PDA";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!serverState.found) {
|
||||||
|
errorOut = "Shine server PDA not found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!serverState.isServer) {
|
||||||
|
errorOut = "Shine server PDA is not a server";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (serverState.serverAddress.isEmpty()) {
|
||||||
|
errorOut = "Shine server address is empty";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverUrlOut = serverState.serverAddress;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String currentShineServerLoginSource() {
|
||||||
|
if (gCachedAccountPdaValid && gCachedAccountPdaLogin == normalizeLoginValue(gLoginValue) && !gCachedAccountPdaState.accessServers.empty()) {
|
||||||
|
String fromPda = normalizeLoginValue(gCachedAccountPdaState.accessServers.front());
|
||||||
|
if (!fromPda.isEmpty()) {
|
||||||
|
return fromPda;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalizeLoginValue(gShineServerLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ensureCurrentShineServerUrl(String &errorOut) {
|
||||||
|
errorOut = "";
|
||||||
|
String login = currentShineServerLoginSource();
|
||||||
|
if (login.isEmpty()) {
|
||||||
|
errorOut = "Shine server login is not set";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (gShineServerUrl.isEmpty() || gResolvedShineServerLogin != login) {
|
||||||
|
String resolvedUrl;
|
||||||
|
if (!resolveShineServerUrlFromLogin(login, resolvedUrl, errorOut)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gShineServerUrl = resolvedUrl;
|
||||||
|
gResolvedShineServerLogin = login;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void shortVecEncode(size_t value, std::vector<uint8_t> &out) {
|
static void shortVecEncode(size_t value, std::vector<uint8_t> &out) {
|
||||||
do {
|
do {
|
||||||
uint8_t byte = value & 0x7F;
|
uint8_t byte = value & 0x7F;
|
||||||
@ -1948,7 +2071,7 @@ static std::vector<uint8_t> buildUpdateInstructionData(const ShinePdaUserState &
|
|||||||
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
static std::vector<uint8_t> buildUnsignedCreateRecord(
|
||||||
const String &login,
|
const String &login,
|
||||||
const String &blockchainName,
|
const String &blockchainName,
|
||||||
const String &serverAddress,
|
const std::vector<String> &accessServers,
|
||||||
const uint8_t recoveryPub[32],
|
const uint8_t recoveryPub[32],
|
||||||
const uint8_t rootPub[32],
|
const uint8_t rootPub[32],
|
||||||
const uint8_t clientPub[32],
|
const uint8_t clientPub[32],
|
||||||
@ -1968,7 +2091,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
pushU32LE(out, 0);
|
pushU32LE(out, 0);
|
||||||
out.insert(out.end(), 32, 0);
|
out.insert(out.end(), 32, 0);
|
||||||
pushStrU8(out, login);
|
pushStrU8(out, login);
|
||||||
out.push_back(8);
|
out.push_back(7);
|
||||||
|
|
||||||
out.push_back(kBlockTypeRecoveryKey);
|
out.push_back(kBlockTypeRecoveryKey);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
@ -1995,17 +2118,12 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
pushFixed(out, lastBlockSignature, 64);
|
pushFixed(out, lastBlockSignature, 64);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
|
|
||||||
out.push_back(kBlockTypeServerProfile);
|
|
||||||
out.push_back(0);
|
|
||||||
out.push_back(1);
|
|
||||||
out.push_back(0);
|
|
||||||
out.push_back(0);
|
|
||||||
pushStrU8(out, serverAddress);
|
|
||||||
out.push_back(0);
|
|
||||||
|
|
||||||
out.push_back(kBlockTypeAccessServers);
|
out.push_back(kBlockTypeAccessServers);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
out.push_back(0);
|
out.push_back((uint8_t)accessServers.size());
|
||||||
|
for (const auto &value : accessServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
out.push_back(kBlockTypeSessions);
|
out.push_back(kBlockTypeSessions);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
@ -2025,7 +2143,7 @@ static std::vector<uint8_t> buildUnsignedCreateRecord(
|
|||||||
static std::vector<uint8_t> buildCreateInstructionData(
|
static std::vector<uint8_t> buildCreateInstructionData(
|
||||||
const String &login,
|
const String &login,
|
||||||
const String &blockchainName,
|
const String &blockchainName,
|
||||||
const String &serverAddress,
|
const std::vector<String> &accessServers,
|
||||||
const uint8_t recoveryPub[32],
|
const uint8_t recoveryPub[32],
|
||||||
const uint8_t rootPub[32],
|
const uint8_t rootPub[32],
|
||||||
const uint8_t clientPub[32],
|
const uint8_t clientPub[32],
|
||||||
@ -2049,12 +2167,11 @@ static std::vector<uint8_t> buildCreateInstructionData(
|
|||||||
out.insert(out.end(), 32, 0);
|
out.insert(out.end(), 32, 0);
|
||||||
pushFixed(out, lastBlockSignature, 64);
|
pushFixed(out, lastBlockSignature, 64);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
out.push_back(1);
|
|
||||||
out.push_back(0);
|
|
||||||
out.push_back(0);
|
|
||||||
pushStrU8(out, serverAddress);
|
|
||||||
out.push_back(0);
|
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
|
out.push_back((uint8_t)accessServers.size());
|
||||||
|
for (const auto &value : accessServers) {
|
||||||
|
pushStrU8(out, value);
|
||||||
|
}
|
||||||
out.push_back(1);
|
out.push_back(1);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
out.push_back(0);
|
out.push_back(0);
|
||||||
@ -2683,11 +2800,15 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
String cleanLogin = normalizeLoginValue(gLoginValue);
|
String cleanLogin = normalizeLoginValue(gLoginValue);
|
||||||
|
String accessServerLogin = normalizeLoginValue(gShineServerLogin);
|
||||||
|
if (!isValidShineServerLoginValue(accessServerLogin)) {
|
||||||
|
accessServerLogin = kDefaultShineServerLogin;
|
||||||
|
}
|
||||||
diagDetails += String("trigger=") + gRegisterTriggerSource + "\n";
|
diagDetails += String("trigger=") + gRegisterTriggerSource + "\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";
|
||||||
diagDetails += String("shine_server=") + gShineServerUrl + "\n";
|
diagDetails += String("shine_server_login=") + accessServerLogin + "\n";
|
||||||
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
||||||
|
|
||||||
if (cleanLogin.isEmpty()) {
|
if (cleanLogin.isEmpty()) {
|
||||||
@ -2715,7 +2836,7 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
gAccountPdaStatusMessage = "User is already registered";
|
gAccountPdaStatusMessage = "User is already registered";
|
||||||
gShowRegisterAccountButton = false;
|
gShowRegisterAccountButton = false;
|
||||||
gAccountStatusMessage = "User is already registered";
|
gAccountStatusMessage = "User is already registered";
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
gShineStatusLine = String("SHiNE: ") + (!gShineServerUrl.isEmpty() ? gShineServerUrl : accessServerLogin) + " registered";
|
||||||
refreshAccountPdaStatus();
|
refreshAccountPdaStatus();
|
||||||
diagDetails += String("user_pda=") + existingPda + "\n";
|
diagDetails += String("user_pda=") + existingPda + "\n";
|
||||||
saveRegisterDiag("ok", "User is already registered", diagDetails);
|
saveRegisterDiag("ok", "User is already registered", diagDetails);
|
||||||
@ -2812,10 +2933,18 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
diagDetails += String("last_block_hash=") + bytesToHexString(lastBlockHash, 32) + "\n";
|
diagDetails += String("last_block_hash=") + bytesToHexString(lastBlockHash, 32) + "\n";
|
||||||
diagDetails += String("last_block_signature_b64=") + bytesToBase64String(lastBlockSignature, 64) + "\n";
|
diagDetails += String("last_block_signature_b64=") + bytesToBase64String(lastBlockSignature, 64) + "\n";
|
||||||
|
|
||||||
uint64_t createdAtMs = shineNowMs();
|
if (!ensureNtpTimeSynced(messageOut)) {
|
||||||
|
diagDetails += String("ntp_error=") + messageOut + "\n";
|
||||||
|
return failWithDiag(messageOut);
|
||||||
|
}
|
||||||
|
uint64_t createdAtMs = 0;
|
||||||
|
if (!getSystemEpochMs(createdAtMs)) {
|
||||||
|
return failWithDiag("NTP time is not ready");
|
||||||
|
}
|
||||||
diagDetails += String("created_at_ms=") + String((unsigned long long)createdAtMs) + "\n";
|
diagDetails += String("created_at_ms=") + String((unsigned long long)createdAtMs) + "\n";
|
||||||
|
std::vector<String> accessServers = {accessServerLogin};
|
||||||
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
|
std::vector<uint8_t> unsignedRecord = buildUnsignedCreateRecord(
|
||||||
cleanLogin, blockchainName, gShineServerUrl,
|
cleanLogin, blockchainName, accessServers,
|
||||||
recoveryPub,
|
recoveryPub,
|
||||||
rootPub, clientPub, blockchainPub,
|
rootPub, clientPub, blockchainPub,
|
||||||
lastBlockSignature, startBonusLimit, createdAtMs);
|
lastBlockSignature, startBonusLimit, createdAtMs);
|
||||||
@ -2830,7 +2959,7 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
diagDetails += String("root_signature_b64=") + bytesToBase64String(rootSignature, 64) + "\n";
|
diagDetails += String("root_signature_b64=") + bytesToBase64String(rootSignature, 64) + "\n";
|
||||||
|
|
||||||
std::vector<uint8_t> createData = buildCreateInstructionData(
|
std::vector<uint8_t> createData = buildCreateInstructionData(
|
||||||
cleanLogin, blockchainName, gShineServerUrl,
|
cleanLogin, blockchainName, accessServers,
|
||||||
recoveryPub,
|
recoveryPub,
|
||||||
rootPub, clientPub, blockchainPub,
|
rootPub, clientPub, blockchainPub,
|
||||||
lastBlockSignature, rootSignature, createdAtMs);
|
lastBlockSignature, rootSignature, createdAtMs);
|
||||||
@ -2901,7 +3030,7 @@ static bool registerHomeserverOnSolana(String &messageOut) {
|
|||||||
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
||||||
gAccountPdaStatusMessage = "User registered";
|
gAccountPdaStatusMessage = "User registered";
|
||||||
gShowRegisterAccountButton = false;
|
gShowRegisterAccountButton = false;
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered";
|
gShineStatusLine = String("SHiNE: ") + (!gShineServerUrl.isEmpty() ? gShineServerUrl : accessServerLogin) + " registered";
|
||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
refreshAccountPdaStatus();
|
refreshAccountPdaStatus();
|
||||||
messageOut = "Solana registration confirmed";
|
messageOut = "Solana registration confirmed";
|
||||||
@ -3604,7 +3733,7 @@ static void refreshAccountPdaStatus() {
|
|||||||
if (gLoginValue.isEmpty() || !gSecretConfigured) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured) {
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN;
|
||||||
gAccountPdaStatusMessage = "account not configured";
|
gAccountPdaStatusMessage = "account not configured";
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3614,7 +3743,7 @@ static void refreshAccountPdaStatus() {
|
|||||||
if (!readShineUserPda(gLoginValue, pdaState, error)) {
|
if (!readShineUserPda(gLoginValue, pdaState, error)) {
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
||||||
gAccountPdaStatusMessage = error.isEmpty() ? "solana check failed" : error;
|
gAccountPdaStatusMessage = error.isEmpty() ? "solana check failed" : error;
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " unavailable";
|
gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " unavailable";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
if (error == "Solana RPC unavailable") {
|
if (error == "Solana RPC unavailable") {
|
||||||
gAccountCheckPending = true;
|
gAccountCheckPending = true;
|
||||||
@ -3626,7 +3755,7 @@ static void refreshAccountPdaStatus() {
|
|||||||
gAccountPdaStatus = ACCOUNT_PDA_NOT_FOUND;
|
gAccountPdaStatus = ACCOUNT_PDA_NOT_FOUND;
|
||||||
gAccountPdaStatusMessage = "user not found";
|
gAccountPdaStatusMessage = "user not found";
|
||||||
gShowRegisterAccountButton = true;
|
gShowRegisterAccountButton = true;
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3641,7 +3770,7 @@ static void refreshAccountPdaStatus() {
|
|||||||
|| !base58ToFixed32(gBlockchainPubB58, blockchainPub)) {
|
|| !base58ToFixed32(gBlockchainPubB58, blockchainPub)) {
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
gAccountPdaStatus = ACCOUNT_PDA_MISMATCH;
|
||||||
gAccountPdaStatusMessage = "local keys invalid";
|
gAccountPdaStatusMessage = "local keys invalid";
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3694,13 +3823,13 @@ static void refreshAccountPdaStatus() {
|
|||||||
gHomeserverPdaActionReason = mismatch;
|
gHomeserverPdaActionReason = mismatch;
|
||||||
gHomeserverPdaCanFix = true;
|
gHomeserverPdaCanFix = true;
|
||||||
}
|
}
|
||||||
gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gCachedAccountPdaState = pdaState;
|
gCachedAccountPdaState = pdaState;
|
||||||
gCachedAccountPdaLogin = gLoginValue;
|
gCachedAccountPdaLogin = normalizeLoginValue(gLoginValue);
|
||||||
gCachedAccountPdaValid = true;
|
gCachedAccountPdaValid = true;
|
||||||
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
gAccountPdaStatus = ACCOUNT_PDA_OK;
|
||||||
gAccountPdaStatusMessage = "ok";
|
gAccountPdaStatusMessage = "ok";
|
||||||
@ -4338,8 +4467,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
diagDetails += String("uptime_ms=") + String(millis()) + "\n";
|
diagDetails += String("uptime_ms=") + String(millis()) + "\n";
|
||||||
diagDetails += String("login=") + gLoginValue + "\n";
|
diagDetails += String("login=") + gLoginValue + "\n";
|
||||||
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
diagDetails += String("homeserver=") + gHomeserverValue + "\n";
|
||||||
diagDetails += String("server_url=") + gShineServerUrl + "\n";
|
diagDetails += String("server_login=") + currentShineServerLoginSource() + "\n";
|
||||||
diagDetails += String("ws_url=") + shineWsUrl() + "\n";
|
|
||||||
diagDetails += String("pda_status=") + gAccountPdaStatusMessage + "\n";
|
diagDetails += String("pda_status=") + gAccountPdaStatusMessage + "\n";
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
diagDetails += "wifi=disconnected\n";
|
diagDetails += "wifi=disconnected\n";
|
||||||
@ -4352,6 +4480,12 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
if (gAccountPdaStatus != ACCOUNT_PDA_OK) {
|
if (gAccountPdaStatus != ACCOUNT_PDA_OK) {
|
||||||
return failWithDiag("account not ready");
|
return failWithDiag("account not ready");
|
||||||
}
|
}
|
||||||
|
if (!ensureCurrentShineServerUrl(errorOut)) {
|
||||||
|
diagDetails += String("server_resolve_error=") + errorOut + "\n";
|
||||||
|
return failWithDiag(errorOut);
|
||||||
|
}
|
||||||
|
diagDetails += String("server_url=") + gShineServerUrl + "\n";
|
||||||
|
diagDetails += String("ws_url=") + shineWsUrl() + "\n";
|
||||||
|
|
||||||
String wsUrl = shineWsUrl();
|
String wsUrl = shineWsUrl();
|
||||||
if (wsUrl.isEmpty()) {
|
if (wsUrl.isEmpty()) {
|
||||||
@ -4513,7 +4647,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void manageShineConnection() {
|
static void manageShineConnection() {
|
||||||
String serverLabel = gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl;
|
String serverLabel = shineServerDisplayLabel();
|
||||||
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
||||||
gShineStatusLine = String("SHiNE: ") + serverLabel + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + serverLabel + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
@ -4543,6 +4677,13 @@ static void manageShineConnection() {
|
|||||||
}
|
}
|
||||||
gLastShineAttemptMs = now;
|
gLastShineAttemptMs = now;
|
||||||
String error;
|
String error;
|
||||||
|
if (!ensureCurrentShineServerUrl(error)) {
|
||||||
|
gShineStatusLine = String("SHiNE: ") + serverLabel + " unavailable";
|
||||||
|
clearShineSessionState(false);
|
||||||
|
gShineReconnectDelayMs = min(gShineReconnectDelayMs + SHINE_RECONNECT_MIN_MS, (unsigned long)SHINE_RECONNECT_MAX_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverLabel = shineServerDisplayLabel();
|
||||||
if (ensureShineSessionAuthenticated(error)) {
|
if (ensureShineSessionAuthenticated(error)) {
|
||||||
gShineStatusLine = String("SHiNE: ") + serverLabel + " connected";
|
gShineStatusLine = String("SHiNE: ") + serverLabel + " connected";
|
||||||
gLastShinePingMs = now;
|
gLastShinePingMs = now;
|
||||||
@ -4626,7 +4767,18 @@ static void loadPrefs() {
|
|||||||
upsertKnownWifi(gWifiSavedSsid, gWifiSavedPassword);
|
upsertKnownWifi(gWifiSavedSsid, gWifiSavedPassword);
|
||||||
}
|
}
|
||||||
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
|
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
|
||||||
gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me");
|
String storedShineServerLogin = normalizeLoginValue(gPrefs.getString("shine_server_login", ""));
|
||||||
|
if (!isValidShineServerLoginValue(storedShineServerLogin)) {
|
||||||
|
String legacyShineServer = normalizeLoginValue(gPrefs.getString("shine_server", ""));
|
||||||
|
if (isValidShineServerLoginValue(legacyShineServer)) {
|
||||||
|
storedShineServerLogin = legacyShineServer;
|
||||||
|
} else {
|
||||||
|
storedShineServerLogin = kDefaultShineServerLogin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gShineServerLogin = storedShineServerLogin;
|
||||||
|
gShineServerUrl = "";
|
||||||
|
gResolvedShineServerLogin = "";
|
||||||
gLoginValue = gPrefs.getString("login", "");
|
gLoginValue = gPrefs.getString("login", "");
|
||||||
gHomeserverValue = gPrefs.getString("homeserver", "homeserver1");
|
gHomeserverValue = gPrefs.getString("homeserver", "homeserver1");
|
||||||
String walletTypeStored = gPrefs.getString("wallet_type", "client.key");
|
String walletTypeStored = gPrefs.getString("wallet_type", "client.key");
|
||||||
@ -4695,7 +4847,8 @@ static void saveWifiPrefs() {
|
|||||||
|
|
||||||
static void saveServerPrefs() {
|
static void saveServerPrefs() {
|
||||||
gPrefs.putString("solana_rpc", gSolanaRpcUrl);
|
gPrefs.putString("solana_rpc", gSolanaRpcUrl);
|
||||||
gPrefs.putString("shine_server", gShineServerUrl);
|
gPrefs.putString("shine_server_login", gShineServerLogin);
|
||||||
|
gPrefs.remove("shine_server");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void saveAccountPrefs() {
|
static void saveAccountPrefs() {
|
||||||
@ -5217,13 +5370,18 @@ static void applyEditorValue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gEditContext == EDIT_CONTEXT_SHINE_SERVER) {
|
if (gEditContext == EDIT_CONTEXT_SHINE_SERVER) {
|
||||||
gShineServerUrl = value;
|
gShineServerLogin = normalizeLoginValue(value);
|
||||||
|
if (!isValidShineServerLoginValue(gShineServerLogin)) {
|
||||||
|
gShineServerLogin = kDefaultShineServerLogin;
|
||||||
|
}
|
||||||
|
gShineServerUrl = "";
|
||||||
|
gResolvedShineServerLogin = "";
|
||||||
saveServerPrefs();
|
saveServerPrefs();
|
||||||
gServerStatusMessage = "Shine server saved";
|
gServerStatusMessage = "SHiNE server login saved";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
gShineReconnectDelayMs = SHINE_RECONNECT_MIN_MS;
|
gShineReconnectDelayMs = SHINE_RECONNECT_MIN_MS;
|
||||||
gLastShineAttemptMs = 0;
|
gLastShineAttemptMs = 0;
|
||||||
gShineStatusLine = "SHiNE: reconnect pending";
|
gShineStatusLine = String("SHiNE: ") + gShineServerLogin + " reconnect pending";
|
||||||
showScreen(SCREEN_SERVER);
|
showScreen(SCREEN_SERVER);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -5577,9 +5735,9 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
case ACTION_SERVER_EDIT_SHINE:
|
case ACTION_SERVER_EDIT_SHINE:
|
||||||
openEditor(EDIT_CONTEXT_SHINE_SERVER,
|
openEditor(EDIT_CONTEXT_SHINE_SERVER,
|
||||||
SCREEN_SERVER,
|
SCREEN_SERVER,
|
||||||
"EDIT SHINE HOST",
|
"EDIT SHINE SERVER LOGIN",
|
||||||
"",
|
"",
|
||||||
gShineServerUrl,
|
gShineServerLogin,
|
||||||
false);
|
false);
|
||||||
break;
|
break;
|
||||||
case ACTION_ACCOUNT_EDIT_LOGIN:
|
case ACTION_ACCOUNT_EDIT_LOGIN:
|
||||||
@ -6324,8 +6482,12 @@ static void drawServerScreen() {
|
|||||||
showMessageAt(gServerStatusMessage, 56);
|
showMessageAt(gServerStatusMessage, 56);
|
||||||
showMessageAt(String("Solana: ") + gSolanaRpcUrl, 96);
|
showMessageAt(String("Solana: ") + gSolanaRpcUrl, 96);
|
||||||
makeButton("SOLANA RPC", 22, 146, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SOLANA, &lv_font_montserrat_24);
|
makeButton("SOLANA RPC", 22, 146, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SOLANA, &lv_font_montserrat_24);
|
||||||
showMessageAt(String("Shine: ") + gShineServerUrl, 248);
|
showMessageAt(String("SHiNE: ") + shineServerDisplayLabel(), 248);
|
||||||
makeButton("SHINE SERVER", 22, 298, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SHINE, &lv_font_montserrat_24);
|
if (gUserPdaAddress.isEmpty()) {
|
||||||
|
makeButton("SHiNE SERVER LOGIN", 22, 298, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SHINE, &lv_font_montserrat_22);
|
||||||
|
} else {
|
||||||
|
makeBody("SHiNE server login is read from PDA.", 312, 360);
|
||||||
|
}
|
||||||
makeBody("Swipe right to return to Settings.", 396, 420);
|
makeBody("Swipe right to return to Settings.", 396, 420);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,12 +150,12 @@
|
|||||||
- статусное сообщение;
|
- статусное сообщение;
|
||||||
- текущий `Solana RPC` адрес;
|
- текущий `Solana RPC` адрес;
|
||||||
- кнопку `SOLANA RPC`;
|
- кнопку `SOLANA RPC`;
|
||||||
- текущий `Shine server` адрес;
|
- текущий `SHiNE server login` или уже резолвленный адрес;
|
||||||
- кнопку `SHINE SERVER`.
|
- кнопку `SHiNE SERVER LOGIN`, если обычный `user PDA` ещё не зарегистрирован.
|
||||||
|
|
||||||
Значения по умолчанию:
|
Значения по умолчанию:
|
||||||
- Solana RPC: `https://api.devnet.solana.com`
|
- Solana RPC: `https://api.devnet.solana.com`
|
||||||
- Shine server: `https://shineup.me`
|
- SHiNE server login: `shineupme`
|
||||||
|
|
||||||
Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`.
|
Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`.
|
||||||
|
|
||||||
@ -229,7 +229,7 @@
|
|||||||
Используется для:
|
Используется для:
|
||||||
- пароля Wi-Fi;
|
- пароля Wi-Fi;
|
||||||
- Solana RPC;
|
- Solana RPC;
|
||||||
- Shine server.
|
- SHiNE server login.
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- заголовок;
|
- заголовок;
|
||||||
@ -291,7 +291,7 @@
|
|||||||
|
|
||||||
Используется `Preferences` (NVS памяти ESP32):
|
Используется `Preferences` (NVS памяти ESP32):
|
||||||
- `solana_rpc`
|
- `solana_rpc`
|
||||||
- `shine_server`
|
- `shine_server_login`
|
||||||
|
|
||||||
## Хранение аккаунта
|
## Хранение аккаунта
|
||||||
|
|
||||||
|
|||||||
@ -65,12 +65,13 @@
|
|||||||
- `user pda address`;
|
- `user pda address`;
|
||||||
- `registration signature`;
|
- `registration signature`;
|
||||||
- `balance`;
|
- `balance`;
|
||||||
- `server api url`;
|
- `server login` для первичной привязки;
|
||||||
- `server rpc url`;
|
- `resolved server api url` / `rpc url` / `ws url` после чтения PDA сервера;
|
||||||
- `server ws url`;
|
|
||||||
- флаги:
|
- флаги:
|
||||||
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
|
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
|
||||||
|
|
||||||
|
Для первой регистрации обычного `user PDA` устройство берёт `createdAtMs` / `updatedAtMs` из NTP прямо перед отправкой транзакции в Solana. Дальше в `user PDA` сохраняется `accessServers`, где по умолчанию лежит `shineupme`.
|
||||||
|
|
||||||
## Правило серверной сессии SHiNE
|
## Правило серверной сессии SHiNE
|
||||||
|
|
||||||
При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс:
|
При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс:
|
||||||
@ -86,7 +87,7 @@
|
|||||||
Кнопка регистрации доступна только если одновременно выполнены условия:
|
Кнопка регистрации доступна только если одновременно выполнены условия:
|
||||||
|
|
||||||
1. настроен и подтверждён `Wi-Fi`;
|
1. настроен и подтверждён `Wi-Fi`;
|
||||||
2. заполнены и подтверждены серверные адреса;
|
2. задан и подтверждён `SHiNE server login`;
|
||||||
3. задан логин;
|
3. задан логин;
|
||||||
4. сгенерирован или введён секрет;
|
4. сгенерирован или введён секрет;
|
||||||
5. баланс кошелька не меньше `0.20 SOL`;
|
5. баланс кошелька не меньше `0.20 SOL`;
|
||||||
@ -628,7 +629,7 @@
|
|||||||
2. открыть `Подключение -> Wi-Fi`;
|
2. открыть `Подключение -> Wi-Fi`;
|
||||||
3. ввести `SSID` и пароль, нажать `Проверить`;
|
3. ввести `SSID` и пароль, нажать `Проверить`;
|
||||||
4. открыть `Подключение -> Серверы`;
|
4. открыть `Подключение -> Серверы`;
|
||||||
5. проверить или задать серверные адреса;
|
5. проверить или задать `SHiNE server login` (по умолчанию `shineupme`);
|
||||||
6. открыть `Аккаунт`;
|
6. открыть `Аккаунт`;
|
||||||
7. ввести логин;
|
7. ввести логин;
|
||||||
8. задать имя homeserver;
|
8. задать имя homeserver;
|
||||||
@ -637,14 +638,15 @@
|
|||||||
11. при необходимости пополнить баланс;
|
11. при необходимости пополнить баланс;
|
||||||
12. вернуться на `HOME`;
|
12. вернуться на `HOME`;
|
||||||
13. нажать `REGISTER ACCOUNT`;
|
13. нажать `REGISTER ACCOUNT`;
|
||||||
14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1` и при необходимости сообщение о неподключённом `Wi-Fi`;
|
14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1`, серверный login и при необходимости сообщение о неподключённом `Wi-Fi`;
|
||||||
15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`;
|
15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`;
|
||||||
16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку;
|
16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку;
|
||||||
17. после успешной регистрации увидеть статус `Homeserver активен`.
|
17. после успешной регистрации увидеть статус `Homeserver активен`.
|
||||||
|
|
||||||
Примечание:
|
Примечание:
|
||||||
|
|
||||||
- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`.
|
- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`;
|
||||||
|
- при первой регистрации для обычного `user PDA` не заполняется `serverAddress`, а `accessServers` получает `shineupme` или другой выбранный `SHiNE server login`.
|
||||||
|
|
||||||
## Сценарий входящего запроса
|
## Сценарий входящего запроса
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.245
|
client.version=1.2.246
|
||||||
server.version=1.2.230
|
server.version=1.2.231
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user