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.
252 lines
9.1 KiB
252 lines
9.1 KiB
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/grvlle/constellation_wallet/backend/models"
|
|
)
|
|
|
|
// formTXChain retains the order of the blockchain across all accounts.
|
|
// Also calls the methods to create block objects (write json to file),
|
|
// and the method that pushes them to the network(HTTP POST).
|
|
// To retain order, formTXChain will poll the last sent TX for it's Failed state.
|
|
// if the last TX failed, it'll switch up the order to account for that not to break the chain.
|
|
// This means that all failed attempts at creating a block is also stored in the DB with
|
|
// a Failed state.
|
|
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("No previous TX detected for this wallet. Reason: ", err)
|
|
}
|
|
|
|
if statusLastTX.Failed {
|
|
a.log.Warnln("Previous Transaction has a failed state. Adapting...", statusLastTX.Failed)
|
|
}
|
|
// Queries the number of previous transactions for this wallet.
|
|
numberOfTX := a.DB.Model(&a.wallet).Association("TXHistory").Count()
|
|
|
|
// First TX does not contain a 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)
|
|
|
|
// Manually control the second TX, to ensure the following order
|
|
if numberOfTX == 1 {
|
|
|
|
// If the first transaction failed, enforce the order.
|
|
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
|
|
}
|
|
|
|
// Check for edge case where PrevTXFile has already been written and needs to be referenced.
|
|
// This occurs when a wallet with 1 previous tx has been imported.
|
|
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
|
|
}
|
|
|
|
// Returns the TX object that has the highest ordinal (the highest determines if it's to be referenced or reference the other tx)
|
|
newTX := a.determineBlockOrder(ptxObj, ltxObj)
|
|
|
|
// If the last TX is in failed state, we reset the order.
|
|
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 {
|
|
// The higher ordinal will always be the TX carrying the TX Ref.
|
|
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 takes the Path to the prev_tx and last_tx files and returns a
|
|
// pointer to two workable objects.
|
|
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
|
|
}
|
|
|
|
/* Called upon wallet import */
|
|
|
|
// TXReference is used to parse the previous tx of an imported wallet.
|
|
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 will query the blockexplorer for a transacion and write it to a.paths.PrevTXFile.
|
|
// This will allow an imported wallet to reference the last transaction sent.
|
|
func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
|
|
a.log.Info("Sending API call to block explorer on: " + 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("Failed to send HTTP request. Reason: ", err)
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
|
|
return err
|
|
}
|
|
a.LoginError("Unable to collect previous TX's from blockexplorer. Please check your internet connection.")
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.Body != nil {
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
a.LoginError("Unable to collect previous TX's from blockexplorer. Please try again later.")
|
|
a.log.Errorln("Unable to collect previous TX's from blockexplorer. Reason: ", err)
|
|
}
|
|
ok, error := a.verifyAPIResponse(bodyBytes)
|
|
// Blockexplorer returns below string when no previous transactions are found
|
|
if !ok && error != "Cannot find transaction" {
|
|
a.log.Errorln("API returned the following error", error)
|
|
// If unable to import last transaction, remove wallet from DB and logout.
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
|
|
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
|
|
return err
|
|
}
|
|
a.log.Panicln("Unable to import previous transactions") // TODO: logout user from wallet
|
|
a.LoginError("The wallet import failed. Please check your internet connection and try again.")
|
|
return err
|
|
}
|
|
|
|
// Parsing JSON object to TXReference ->
|
|
lastTX := TXReference{}
|
|
err = json.Unmarshal(bodyBytes, &lastTX)
|
|
if err != nil {
|
|
a.log.Errorln("Unable to fetch TX history from block explorer. Reason: ", err)
|
|
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
|
|
return err
|
|
}
|
|
// Marshal so that we can unmarshat into tx object ->
|
|
b, err := json.Marshal(lastTX.TransactionOriginal)
|
|
if err != nil {
|
|
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
|
|
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
|
|
return err
|
|
}
|
|
|
|
// Populating tx object ->
|
|
tx := Transaction{}
|
|
err = json.Unmarshal(b, &tx)
|
|
if err != nil {
|
|
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
|
|
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
|
|
return err
|
|
}
|
|
|
|
// Converting to json
|
|
txBytes, err := json.Marshal(tx)
|
|
if err != nil {
|
|
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
|
|
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
|
|
return err
|
|
}
|
|
|
|
err = WriteToFile(a.paths.PrevTXFile, txBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|