forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
546 lines
16 KiB
546 lines
16 KiB
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/grvlle/constellation_wallet/backend/models"
|
|
)
|
|
|
|
// ImportWallet 在用户首次登录新的 Molly 钱包时触发
|
|
func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPassword, alias string) bool {
|
|
|
|
alias = strings.ToLower(alias)
|
|
a.wallet = models.Wallet{
|
|
KeyStorePath: keystorePath,
|
|
WalletAlias: alias,
|
|
Currency: "USD"}
|
|
|
|
if runtime.GOOS == "windows" && !a.javaInstalled() {
|
|
a.LoginError("无法检测到 Java 路径。请确保已安装 Java。")
|
|
return false
|
|
}
|
|
|
|
if !a.TransactionFinished {
|
|
a.log.Warn("Cannot Import wallet in a pending transaction.")
|
|
a.LoginError("Cannot import a new wallet while there's a pending transaction.")
|
|
return false
|
|
}
|
|
|
|
if keystorePath == "" {
|
|
a.LoginError("请提供 KeyStore 文件的路径。")
|
|
return false
|
|
}
|
|
|
|
if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
|
|
a.log.Warnln("未提供一个或多个密码。")
|
|
return false
|
|
}
|
|
|
|
os.Setenv("CL_STOREPASS", keystorePassword)
|
|
os.Setenv("CL_KEYPASS", keyPassword)
|
|
|
|
a.wallet.Address = a.GenerateDAGAddress()
|
|
a.KeyStoreAccess = a.WalletKeystoreAccess()
|
|
|
|
if a.KeyStoreAccess {
|
|
if !a.DB.NewRecord(&a.wallet) {
|
|
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
|
|
if err != nil {
|
|
a.log.Errorln("无法生成密码哈希。原因: ", err)
|
|
a.LoginError("无法生成密码哈希。")
|
|
return false
|
|
}
|
|
|
|
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
|
|
if err != nil {
|
|
a.log.Errorln("无法生成密码哈希。原因: ", err)
|
|
a.LoginError("无法生成密码哈希。")
|
|
return false
|
|
}
|
|
|
|
if err := a.DB.Create(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("无法为导入的 wallet 创建数据库对象。原因: ", err)
|
|
a.LoginError("无法为导入的 wallet 创建数据库对象。也许它已经被导入了?尝试登录。")
|
|
return false
|
|
}
|
|
|
|
a.paths.LastTXFile = a.TempFileName("tx-")
|
|
a.paths.PrevTXFile = a.TempFileName("tx-")
|
|
a.paths.EmptyTXFile = a.TempFileName("tx-")
|
|
|
|
err = a.createTXFiles()
|
|
if err != nil {
|
|
a.log.Fatalln("无法创建 TX 文件。检查 fs 权限。原因: ", err)
|
|
a.sendError("无法创建 TX 文件。检查 fs 权限。原因: ", err)
|
|
}
|
|
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
|
|
a.log.Errorln("无法使用 tmp tx-paths 更新数据库记录。原因: ", err)
|
|
a.sendError("无法使用 tmp tx-paths 更新数据库记录。原因: ", err)
|
|
}
|
|
|
|
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
|
|
a.log.Errorln("无法查询导入的 wallet 的数据库对象。原因: ", err)
|
|
a.LoginError("无法查询导入的 wallet 的数据库对象。")
|
|
return false
|
|
}
|
|
|
|
a.UserLoggedIn = false
|
|
a.NewUser = true
|
|
a.WalletImported = true
|
|
err = a.initWallet(keystorePath)
|
|
if err != nil {
|
|
a.log.Errorln("初始化 wallet 失败。原因: ", err)
|
|
// 如果无法导入以前的交易,请从 DB 中删除钱包并注销。
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
return true
|
|
|
|
} else if a.DB.NewRecord(&a.wallet) { // 在某些情况下,可能已经存在 DB 记录。
|
|
a.DB.First(&a.wallet)
|
|
|
|
a.UserLoggedIn = false
|
|
a.NewUser = false
|
|
a.WalletImported = true
|
|
err := a.initWallet(a.wallet.KeyStorePath)
|
|
if err != nil {
|
|
a.log.Errorln("初始化 wallet。原因: ", err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// CreateWallet 在前端组件 Login.vue 中创建新钱包时调用
|
|
func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPassword, alias, label string) bool {
|
|
|
|
alias = strings.ToLower(alias)
|
|
|
|
if runtime.GOOS == "windows" && !a.javaInstalled() {
|
|
a.LoginError("无法检测到 Java 路径。请确保已安装 Java。")
|
|
return false
|
|
}
|
|
|
|
if !a.TransactionFinished {
|
|
a.log.Warn("无法在待处理交易中创建钱包。")
|
|
a.LoginError("当有待处理交易时,无法创建新钱包。")
|
|
return false
|
|
}
|
|
|
|
if keystorePath == "" {
|
|
a.LoginError("请提供用于存储 KeyStore 文件的路径。")
|
|
return false
|
|
}
|
|
|
|
if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
|
|
a.log.Warnln("未提供一个或多个密码。")
|
|
return false
|
|
}
|
|
|
|
if alias == "" {
|
|
alias = a.wallet.WalletAlias
|
|
}
|
|
|
|
os.Setenv("CL_STOREPASS", keystorePassword)
|
|
os.Setenv("CL_KEYPASS", keyPassword)
|
|
|
|
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
|
|
if err != nil {
|
|
a.log.Errorln("无法生成密码哈希。原因: ", err)
|
|
a.sendError("无法生成密码哈希。原因: ", err)
|
|
return false
|
|
}
|
|
|
|
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
|
|
if err != nil {
|
|
a.log.Errorln("无法生成密码哈希。原因: ", err)
|
|
a.sendError("无法生成密码哈希。原因: ", err)
|
|
return false
|
|
}
|
|
|
|
a.wallet = models.Wallet{
|
|
KeyStorePath: keystorePath,
|
|
KeystorePasswordHash: keystorePasswordHashed,
|
|
KeyPasswordHash: keyPasswordHashed,
|
|
WalletAlias: alias,
|
|
WalletTag: label}
|
|
|
|
if !a.DB.NewRecord(&a.wallet) {
|
|
if err := a.DB.Create(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("无法为新钱包创建数据库对象。原因: ", err)
|
|
a.LoginError("无法创建新钱包。别名 已存在。")
|
|
return false
|
|
}
|
|
|
|
if err := a.DB.Where("wallet_alias = ?", alias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
|
|
a.log.Errorln("创建钱包后,无法查询新钱包的数据库对象。原因: ", err)
|
|
a.sendError("创建钱包后,无法查询新钱包的数据库对象。原因: ", err)
|
|
return false
|
|
}
|
|
|
|
err = a.CreateEncryptedKeyStore()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
a.wallet.Address = a.GenerateDAGAddress()
|
|
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Address", a.wallet.Address).Error; err != nil {
|
|
a.log.Errorln("无法使用 DAG 地址更新 db 对象 new wallet。原因: ", err)
|
|
a.sendError("无法使用 DAG 地址更新 db 对象 new wallet。原因. Reason: ", err)
|
|
}
|
|
a.KeyStoreAccess = a.WalletKeystoreAccess()
|
|
|
|
if a.KeyStoreAccess {
|
|
a.paths.LastTXFile = a.TempFileName("tx-")
|
|
a.paths.PrevTXFile = a.TempFileName("tx-")
|
|
a.paths.EmptyTXFile = a.TempFileName("tx-")
|
|
|
|
err := a.createTXFiles()
|
|
if err != nil {
|
|
a.log.Fatalln("无法创建 TX 文件。检查 fs 权限。原因: ", err)
|
|
a.sendError("无法创建 TX 文件。检查 fs 权限。原因: ", err)
|
|
}
|
|
|
|
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
|
|
a.log.Errorln("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
|
|
a.sendError("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
|
|
}
|
|
|
|
a.UserLoggedIn = false
|
|
a.FirstTX = true
|
|
a.NewUser = true
|
|
|
|
a.initNewWallet()
|
|
|
|
return true
|
|
}
|
|
} else {
|
|
a.LoginError("Unable to create new wallet. Alias already exists.")
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// initWallet initializes a new wallet. This is called from login.vue
|
|
// only when a new wallet is created.
|
|
func (a *WalletApplication) initNewWallet() {
|
|
|
|
a.StoreImagePathInDB("faces/face-0.jpg")
|
|
|
|
//a.initTransactionHistory()
|
|
a.passKeysToFrontend()
|
|
|
|
if !a.WidgetRunning.DashboardWidgets {
|
|
a.initDashboardWidgets()
|
|
}
|
|
a.log.Infoln("A New wallet has been created successfully!")
|
|
}
|
|
|
|
// initExistingWallet queries the database for the user wallet and pushes
|
|
// the information to the front end components.
|
|
func (a *WalletApplication) initWallet(keystorePath string) error {
|
|
|
|
if a.NewUser {
|
|
err := a.initTXFromBlockExplorer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.StoreImagePathInDB("faces/face-0.jpg")
|
|
} else if !a.NewUser {
|
|
a.initTXFromDB() // Disregard upon import
|
|
a.initTXFilePath() // Update paths from DB.
|
|
}
|
|
|
|
a.RT.Events.Emit("wallet_init", a.wallet.TermsOfService, a.wallet.Currency)
|
|
|
|
if !a.WidgetRunning.DashboardWidgets {
|
|
a.initDashboardWidgets()
|
|
}
|
|
if !a.WidgetRunning.PassKeysToFrontend {
|
|
a.passKeysToFrontend()
|
|
}
|
|
|
|
a.log.Infoln("User has logged into the wallet")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *WalletApplication) initDashboardWidgets() {
|
|
// Initializes a struct containing all Chart Data on the dashboard
|
|
chartData := a.ChartDataInit()
|
|
|
|
// Below methods are continously updating the client side modules.
|
|
a.pollTokenBalance()
|
|
a.nodeStats(chartData)
|
|
a.txStats(chartData)
|
|
a.networkStats(chartData)
|
|
a.blockAmount()
|
|
a.pricePoller()
|
|
|
|
a.WidgetRunning.DashboardWidgets = true
|
|
}
|
|
|
|
func (a *WalletApplication) createTXFiles() error {
|
|
files := []string{a.paths.LastTXFile, a.paths.PrevTXFile, a.paths.EmptyTXFile}
|
|
|
|
for _, f := range files {
|
|
file, err := os.Create(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ImportKeys is called from the frontend to use a file dialog to select key file.
|
|
func (a *WalletApplication) ImportKeys() error {
|
|
filename := a.RT.Dialog.SelectFile()
|
|
a.log.Info("Path to keys that user wants to import: " + filename)
|
|
return nil
|
|
}
|
|
|
|
// ExportKeys is called from the frontend to use a file dialog to select directory
|
|
// where user wants to export the keys to.
|
|
func (a *WalletApplication) ExportKeys() error {
|
|
filename := a.RT.Dialog.SelectDirectory()
|
|
a.log.Info("File user wants to save to: " + filename)
|
|
return nil
|
|
}
|
|
|
|
func (a *WalletApplication) initTXFilePath() {
|
|
paths := &a.wallet.Path
|
|
if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("Path").Find(&paths).Error; err != nil {
|
|
a.log.Fatal("Unable to initialize TX filepaths. Reason: ", err)
|
|
a.sendError("Unable to initialize TX filepaths. Reason: ", err)
|
|
return
|
|
}
|
|
if a.wallet.Path.LastTXFile == "" && a.wallet.Path.PrevTXFile == "" {
|
|
a.log.Fatal("Unable to initialize TX filepaths. Both are empty after DB query.")
|
|
}
|
|
a.paths.LastTXFile = a.wallet.Path.LastTXFile
|
|
a.paths.PrevTXFile = a.wallet.Path.PrevTXFile
|
|
a.paths.EmptyTXFile = a.wallet.Path.EmptyTXFile
|
|
|
|
}
|
|
|
|
func (a *WalletApplication) initTXFromDB() {
|
|
if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("TXHistory").Find(&a.wallet.TXHistory).Error; err != nil {
|
|
a.log.Error("无法从 DB 初始化历史事务。原因: ", err)
|
|
a.sendError("无法从 DB 初始化历史事务。原因: ", err)
|
|
return
|
|
}
|
|
|
|
allTX := []models.TXHistory{}
|
|
|
|
for i, tx := range a.wallet.TXHistory {
|
|
allTX = append([]models.TXHistory{tx}, allTX...) // 在 FE 的 reverse list 之前
|
|
|
|
if a.wallet.TXHistory[i].Status == "Pending" {
|
|
a.TxPending(a.wallet.TXHistory[i].Hash)
|
|
}
|
|
}
|
|
a.RT.Events.Emit("update_tx_history", allTX) // 将 tx 作为新交易传递给前端。
|
|
}
|
|
|
|
// initTXFromBlockExplorer 在导入现有 wallet 时调用。
|
|
func (a *WalletApplication) initTXFromBlockExplorer() error {
|
|
a.log.Info("向区块浏览器发送 API 调用: " + a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
|
|
|
|
resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
|
|
if err != nil {
|
|
a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
|
|
a.LoginError("无法从 BlockExplorer 收集以前的交易。")
|
|
return err
|
|
}
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}(resp.Body)
|
|
|
|
if resp.Body != nil {
|
|
// bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
a.LoginError("无法从 BlockExplorer 收集以前的交易。 请稍后重试。")
|
|
a.log.Errorln("无法从 BlockExplorer 收集以前的交易。 原因: ", err)
|
|
return err
|
|
}
|
|
|
|
ok, e := a.verifyAPIResponse(bodyBytes)
|
|
// 当没有找到以前的交易时,BlockExplorer 返回以下字符串
|
|
if !ok {
|
|
if e != "Cannot find transactions for sender" {
|
|
a.log.Errorln("API 返回以下错误", e)
|
|
a.LoginError("钱包导入失败。请检查您的互联网连接,然后重试。")
|
|
return errors.New(e)
|
|
} else {
|
|
// 如果导入的钱包之前没有发生过交易 - 继续
|
|
a.log.Info("无法检测到任何以前的交易。")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var allTX []models.TXHistory
|
|
|
|
err = json.Unmarshal(bodyBytes, &allTX)
|
|
if err != nil {
|
|
a.log.Errorln("无法从区块浏览器获取 TX 历史记录。原因: ", err)
|
|
a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err)
|
|
return err
|
|
}
|
|
|
|
// 倒序
|
|
for i := len(allTX)/2 - 1; i >= 0; i-- {
|
|
opp := len(allTX) - 1 - i
|
|
allTX[i], allTX[opp] = allTX[opp], allTX[i]
|
|
}
|
|
|
|
a.log.Infof("已成功收集 %d 笔以前的交易。更新本地状态...", len(allTX))
|
|
|
|
for i, tx := range allTX {
|
|
|
|
txData := &models.TXHistory{
|
|
Amount: tx.Amount,
|
|
Receiver: tx.Receiver,
|
|
Fee: tx.Fee,
|
|
Hash: tx.Hash,
|
|
TS: time.Now().Format("Jan _2 15:04:05") + " (imported)",
|
|
Status: "Complete",
|
|
Failed: false,
|
|
}
|
|
a.storeTX(txData)
|
|
a.RT.Events.Emit("new_transaction", txData)
|
|
|
|
if i+1 == len(allTX) {
|
|
|
|
// 向区块链发送信息
|
|
err := a.rebuildTxChainState(tx.Hash)
|
|
if err != nil {
|
|
a.log.Errorln(err)
|
|
// 如何无法导入以前的交易, 从 DB 中删除钱包并注销。
|
|
// TODO logout
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("导入失败时无法删除 wallet。原因: ", err)
|
|
return err
|
|
}
|
|
a.log.Panicln("无法导入以前的交易")
|
|
a.LoginError("无法从 BlockExplorer 收集以前的 TX。请稍后重试。")
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
a.log.Info("无法检测到任何以前的交易。")
|
|
return nil
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
// passKeysToFrontend 将键发送到设置中。Vue 组件 5 秒间隔
|
|
func (a *WalletApplication) passKeysToFrontend() {
|
|
if a.wallet.KeyStorePath != "" && a.wallet.Address != "" {
|
|
go func() {
|
|
for {
|
|
a.RT.Events.Emit("wallet_keys", a.wallet.Address)
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
}()
|
|
a.WidgetRunning.PassKeysToFrontend = true
|
|
} else {
|
|
a.WidgetRunning.PassKeysToFrontend = false
|
|
}
|
|
}
|
|
|
|
func (a *WalletApplication) passwordsProvided(keystorePassword, keyPassword, alias string) bool {
|
|
if keystorePassword == "" {
|
|
a.LoginError("请提供密钥库密码。")
|
|
return false
|
|
} else if keyPassword == "" {
|
|
a.LoginError("请提供密钥密码。")
|
|
return false
|
|
} else if alias == "" {
|
|
a.LoginError("尚未提供别名。")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetTokenBalance 轮询并解析钱包的代币余额,并将其返回为 float64。
|
|
func (a *WalletApplication) GetTokenBalance() (float64, error) {
|
|
a.log.Debug("通过以下方式联系主网: " + a.Network.URL + a.Network.Handles.Balance + " 发送以下有效负载:" + a.wallet.Address)
|
|
|
|
resp, err := http.Get(a.Network.URL + a.Network.Handles.Balance + a.wallet.Address)
|
|
if err != nil {
|
|
a.log.Errorln("发送 HTTP 请求失败。原因: ", err)
|
|
return 0, err
|
|
}
|
|
if resp == nil {
|
|
a.log.Errorln("在尝试从主网获取余额 10 次失败后杀死 pollTokenBalance,原因: ", err)
|
|
a.sendWarning("无法显示当前余额。请检查您的互联网连接并重新启动钱包应用程序。")
|
|
return 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
a.log.Warn("无法更新 Token 余额。原因: ", err)
|
|
return 0, err
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
|
|
// 将 JSON 解组或解码到接口。
|
|
err = json.Unmarshal(bodyBytes, &result)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
s := result["balance"]
|
|
if s == "" {
|
|
s = "0" // Empty means zero
|
|
}
|
|
|
|
a.log.Infoln("解析了以下余额: ", s)
|
|
|
|
b, ok := s.(float64)
|
|
if !ok {
|
|
return 0, err
|
|
}
|
|
|
|
f := fmt.Sprintf("%.2f", b/1e8) // Reverse normalized float
|
|
|
|
a.log.Infoln("规范化以下余额: ", f)
|
|
|
|
balance, err := strconv.ParseFloat(f, 64)
|
|
if err != nil {
|
|
a.log.Warnln("无法为令牌余额 poller 键入将字符串转换为浮点数。检查您的互联网连接")
|
|
return 0, err
|
|
}
|
|
|
|
a.log.Infoln("返回以下余额: ", balance)
|
|
|
|
return balance, nil
|
|
}
|
|
|