package app import ( "encoding/json" "io/ioutil" "net/http" "github.com/grvlle/constellation_wallet/backend/models" ) // formTXChain 在所有账户中保留区块链的顺序。 // 同时调用创建块对象的方法(将 json 写入文件), // 以及将它们推送到网络的方法 (HTTP POST)。 // 为了保持顺序,formTXChain 将轮询最后发送的 TX 的 Failed 状态。 // 如果最后一个 TX 失败,它将切换顺序以考虑不会中断链。 // 这意味着所有创建数据块的失败尝试也会存储在数据库中,并带有 // Failed (失败) 状态。 func (a *WalletApplication) formTXChain(amount int64, fee int64, address string, ptxObj *Transaction, ltxObj *Transaction) { statusLastTX := models.TXHistory{} if err := a.DB.Last(&statusLastTX).Error; err != nil { a.log.Warnln("未检测到此钱包的先前 TX。原因: ", err) } if statusLastTX.Failed { a.log.Warnln("Previous Transaction 具有 failed 状态。适应...", statusLastTX.Failed) } // 查询此钱包的先前交易数量。 numberOfTX := a.DB.Model(&a.wallet).Association("TXHistory").Count() // 第一个 TX 不包含 TXref if numberOfTX == 0 { a.log.Infoln("Detected that this is the first TX sent from this key.") a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile) a.sendTransaction(a.paths.LastTXFile) return } a.log.Infoln("Number of previous TX's detected: ", numberOfTX) // 手动控制第二个 TX,确保顺序如下 if numberOfTX == 1 { // 如果第一个事务失败,则强制执行顺序。 if statusLastTX.Failed { a.log.Warnln("Detected that the previous transaction failed.") a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile) a.sendTransaction(a.paths.LastTXFile) return } // 检查已写入 PrevTXFile 并需要引用的边缘情况。 // 当导入具有 1 个 tx 的钱包时,会发生这种情况。 prevTXFileContents := a.loadTXFromFile(a.paths.PrevTXFile) if a.WalletImported && prevTXFileContents != "" { a.log.Warnln("One previous transaction has been imported. Using that as reference.") a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile) a.sendTransaction(a.paths.LastTXFile) return } a.produceTXObject(amount, fee, address, a.paths.PrevTXFile, a.paths.LastTXFile) a.sendTransaction(a.paths.PrevTXFile) return } //返回具有最高序数的 TX 对象(最高确定它是要引用还是引用另一个 tx) newTX := a.determineBlockOrder(ptxObj, ltxObj) // 如果最后一个 TX 处于 failed 状态,我们将重置顺序。 if newTX == a.paths.PrevTXFile && statusLastTX.Failed { a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile) a.sendTransaction(a.paths.LastTXFile) return } if newTX != a.paths.PrevTXFile && !statusLastTX.Failed { a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile) a.sendTransaction(a.paths.LastTXFile) return } a.produceTXObject(amount, fee, address, a.paths.PrevTXFile, a.paths.LastTXFile) a.sendTransaction(a.paths.PrevTXFile) } func (a *WalletApplication) determineBlockOrder(ptxObj, ltxObj *Transaction) string { // 较高的序数将始终是带有 TX Ref 的 TX。 a.log.Info("Last TX Ordinal: ", ltxObj.LastTxRef.Ordinal, " Previous TX Ordinal: ", ptxObj.LastTxRef.Ordinal) if ltxObj.LastTxRef.Ordinal > ptxObj.LastTxRef.Ordinal { return a.paths.PrevTXFile } return a.paths.LastTXFile } // convertToTXObject 获取 prev_tx 的 Path 并last_tx文件,并返回一个指向两个 workable 对象的指针。 func (a *WalletApplication) convertToTXObject(ptx, ltx string) (*Transaction, *Transaction) { var ptxObj Transaction var ltxObj Transaction rbytes := []byte(ptx) lbytes := []byte(ltx) err := json.Unmarshal(rbytes, &ptxObj) if err != nil { a.log.Warnln("TX Object: ", string(rbytes), err) } err = json.Unmarshal(lbytes, <xObj) if err != nil { a.log.Warnln("TX Object: ", string(rbytes), err) } return &ptxObj, <xObj } /* 在钱包导入时调用 */ // TXReference 用于解析导入的 wallet 之前的 tx。 type TXReference struct { Hash string `json:"hash"` Amount int64 `json:"amount"` Receiver string `json:"receiver"` Sender string `json:"sender"` Fee int `json:"fee"` IsDummy bool `json:"isDummy"` LastTransactionRef struct { PrevHash string `json:"prevHash"` Ordinal int `json:"ordinal"` } `json:"lastTransactionRef"` SnapshotHash string `json:"snapshotHash"` CheckpointBlock string `json:"checkpointBlock"` TransactionOriginal struct { Edge struct { ObservationEdge struct { Parents []struct { HashReference string `json:"hashReference"` HashType string `json:"hashType"` BaseHash string `json:"baseHash"` } `json:"parents"` Data struct { HashReference string `json:"hashReference"` HashType string `json:"hashType"` BaseHash string `json:"baseHash"` } `json:"data"` } `json:"observationEdge"` SignedObservationEdge struct { SignatureBatch struct { Hash string `json:"hash"` Signatures []struct { Signature string `json:"signature"` ID struct { Hex string `json:"hex"` } `json:"id"` } `json:"signatures"` } `json:"signatureBatch"` } `json:"signedObservationEdge"` Data struct { Amount int64 `json:"amount"` LastTxRef struct { PrevHash string `json:"prevHash"` Ordinal int `json:"ordinal"` } `json:"lastTxRef"` Fee interface{} `json:"fee"` Salt int64 `json:"salt"` } `json:"data"` } `json:"edge"` LastTxRef struct { PrevHash string `json:"prevHash"` Ordinal int `json:"ordinal"` } `json:"lastTxRef"` IsDummy bool `json:"isDummy"` IsTest bool `json:"isTest"` } `json:"transactionOriginal"` } // rebuildTxChainState 将查询区块浏览器以获取交易并将其写入 a.paths.PrevTXFile。 这将允许导入的钱包引用最后发送的交易。 func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error { a.log.Info("向区块浏览器发送 API 调用: " + a.Network.BlockExplorer.Handles.Transactions) resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.Transactions + lastTXHash) if err != nil { a.log.Errorln("发送 HTTP 请求失败。原因: ", err) 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.LoginError("无法从 BlockExplorer 收集以前的 TX。请检查您的互联网连接。") return err } defer resp.Body.Close() if resp.Body != nil { bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { a.LoginError("无法从 BlockExplorer 收集以前的 TX。请稍后重试。") a.log.Errorln("无法从 BlockExplorer 收集以前的 TX。原因: ", err) } ok, e := a.verifyAPIResponse(bodyBytes) // 当没有找到以前的交易时,BlockExplorer 返回以下字符串 if !ok && e != "Cannot find transaction" { a.log.Errorln("API 返回以下错误", e) // 如果无法导入最后一笔交易,请从数据库中删除钱包并注销。 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("无法导入以前的交易") // TODO: logout user from wallet a.LoginError("钱包导入失败。请检查您的互联网连接,然后重试。") return err } // Parsing JSON object to TXReference -> lastTX := TXReference{} err = json.Unmarshal(bodyBytes, &lastTX) if err != nil { a.log.Errorln("无法从区块浏览器获取 TX 历史记录。原因: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err) return err } // Marshal so that we can unmarshat into tx object -> b, err := json.Marshal(lastTX.TransactionOriginal) if err != nil { a.log.Errorln("无法解析最后一个交易哈希。原因: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err) return err } // Populating tx object -> tx := Transaction{} err = json.Unmarshal(b, &tx) if err != nil { a.log.Errorln("无法解析最后一个交易哈希。原因: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err) return err } // Converting to json txBytes, err := json.Marshal(tx) if err != nil { a.log.Errorln("无法解析最后一个交易哈希。原因: ", err) a.sendError("无法从区块浏览器获取 TX 历史记录。原因: ", err) return err } err = WriteToFile(a.paths.PrevTXFile, txBytes) if err != nil { return err } } return nil }