Author: freewind
Biyuan Project warehouse:
Making address: https://github.com/Bytom/bytom
Gitee address: https://gitee.com/BytomBlockchain/bytom
In the previous article, we tried to understand how Biyuan trades, but due to the amount of content, we broke it down into a few small problems, and in the previous article we addressed “how to submit transaction information in Dashboard” and “how biyuan works in the background.”
In this paper, we continue to study the next question: after the submitted transaction is successfully completed, the front end will display the transaction information in the form of a list. How does it get the data from the background? Here’s how it works:
Since it involves both the front end and the back end, we’ve also broken it down into two small problems:
- How does the front end take the transaction data and display it?
- How does the back end find transaction data?
So let’s do it one by one.
How does the front end take the transaction data and display it?
Let’s start by looking in the original front-end code base. As a result of this function is to list “paging” shows that this reminds me of the front has a similar function is paging display balance, there is the SRC/features/Shared/components/BaseList common component, so this should be the same.
After viewing, indeed as expected in SRC/features/the transactions/components/find the List below. The JSX file, and directory structure with SRC/features/balances/components/very similar, and generally have a look at the code, You can be sure you’re using the same method.
But if you think back, the process seems a little different. In the balances display, we manually click the left bar menu to make the front end switch to the /balances, and the balances become become
src/features/shared/routes.js#L5-L44
const makeRoutes = (store, type, List, New, Show, options = {}) = > {
// ...
return {
path: options.path || type + 's'.component: RoutingContainer,
name: options.name || humanize(type + 's'),
name_zh: options.name_zh,
indexRoute: {
component: List,
onEnter: (nextState, replace) = > {
loadPage(nextState, replace)
},
onChange: (_, nextState, replace) = > { loadPage(nextState, replace) }
},
childRoutes: childRoutes
}
}
Copy the code
The onEnter or onChange in can trigger the loadPage, and the backend interface /list-balances is finally called. In this example, after submitting the “Submit transaction” form successfully, it will automatically go to the “list show transaction” page. Will it also trigger onEnter or onChange?
The answer is yes, because the function dealSignSubmitResp is called after the submitForm is executed and the last request to the background /submit-transaction succeeds.
src/features/transactions/actions.js#L120-L132
const dealSignSubmitResp = resp= > {
// ...
dispatch(push({
pathname: '/transactions'.state: {
preserveFlash: true}}}))Copy the code
As you can see, it also switches the front end route to /transactions, which is exactly the same route as showing the balance. So if you look at the /list-transactions interface in the background, you’ll end up accessing the /list-transactions interface.
The derivation of this process will not be detailed, if you need to see the previous explanation of “how to display the balance than the original” that article.
Finally, I got a look at how the data returned in the background is presented in tabular form, which was also mentioned in that article and skipped here.
How does the back end find transaction data?
Once we know that the front end is accessing the /list-transactions interface in the background, we can easily find the following code in the original main project repository:
api/api.go#L164-L244
func (a *API) buildHandler(a) {
// ...
ifa.wallet ! =nil {
// ...
m.Handle("/list-transactions", jsonHandler(a.listTransactions))
// ...
}
Copy the code
{{list-transactions}} {{list-transactions}} {{list-transactions}}
api/query.go#L83-L107
func (a *API) listTransactions(ctx context.Context, filter struct {
ID string `json:"id"`
AccountID string `json:"account_id"`
Detail bool `json:"detail"`
}) Response {
transactions := []*query.AnnotatedTx{}
var err error
/ / 1.
iffilter.AccountID ! ="" {
transactions, err = a.wallet.GetTransactionsByAccountID(filter.AccountID)
} else {
transactions, err = a.wallet.GetTransactionsByTxID(filter.ID)
}
// ...
/ / 2.
if filter.Detail == false {
txSummary := a.wallet.GetTransactionsSummary(transactions)
return NewSuccessResponse(txSummary)
}
return NewSuccessResponse(transactions)
}
Copy the code
As you can see from the parameters of this method, the front end can pass in the id, account_id, and detail parameters. In this example, no parameters are passed because it is a direct route to /transactions.
I split the code into two pieces and omitted some error handling. Explain in turn:
- The first one wants to get it by argument
transactions
. ifaccount_id
If there is a value, take it, that is, a transaction owned by an account; Otherwise, useid
Go get it. This ID is the id of the transaction. If neither has a value, it should be processed in the second branch, i.ea.wallet.GetTransactionsByTxID
It should also be possible to handle an empty string as an argument - Code number 2, if
detail
forfalse
(If no value is passed in front, it should be the defaultfalse
, then get the one in fronttransactions
Become a summary, return only partial information; Otherwise, return full information.
Our advanced 1 place in the code aleem walji allet. GetTransactionsByAccountID:
wallet/indexer.go#L505-L523
func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
/ / 1.
txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
defer txIter.Release()
/ / 2.
for txIter.Next() {
/ / 3.
annotatedTx := &query.AnnotatedTx{}
iferr := json.Unmarshal(txIter.Value(), &annotatedTx); err ! =nil {
return nil, err
}
/ / 4.
if findTransactionsByAccount(annotatedTx, accountID) {
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
annotatedTxs = append(annotatedTxs, annotatedTx)
}
}
return annotatedTxs, nil
}
Copy the code
Here the code is broken into four pieces:
- The first code iterates through the database
TxPrefix
Is the prefix value, whereTxPrefix
forTXS:
, and now we’re going to iterate. Here,DB
There is no doubt that iswallet
The leveldb - The traversal starts at 2
- And the third place is where you put each element
Value
Take it out, it’s in JSON format, and convert it toAnnotatedTx
Object.txIter
Each element of is a key-pair that hasKey()
, there areValue()
Here we only useValue()
- The fourth place is the current one
annotatedTx
Object if its input or output contains an account id equal toaccountId
, then it is what we need. Use laterannotateTxsAsset
Methods theannotatedTx
Asset-related properties in the object (e.galias
Etc.) completion.
AnnotatedTx’s definition is worth a look:
blockchain/query/annotated.go#L12-L22
type AnnotatedTx struct {
ID bc.Hash `json:"tx_id"`
Timestamp uint64 `json:"block_time"`
BlockID bc.Hash `json:"block_hash"`
BlockHeight uint64 `json:"block_height"`
Position uint32 `json:"block_index"`
BlockTransactionsCount uint32 `json:"block_transactions_count,omitempty"`
Inputs []*AnnotatedInput `json:"inputs"`
Outputs []*AnnotatedOutput `json:"outputs"`
StatusFail bool `json:"status_fail"`
}
Copy the code
It actually holds the data that is finally returned to the front end, and makes it easy to convert to JSON by adding jSON-related annotations to each field.
If the front not preach account_id parameters, will enter another branch, corresponding aleem walji allet. GetTransactionsByTxID (filter. ID) :
wallet/indexer.go#L426-L450
func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
formatKey := ""
iftxID ! ="" {
rawFormatKey := w.DB.Get(calcTxIndexKey(txID))
if rawFormatKey == nil {
return nil, fmt.Errorf("No transaction(txid=%s) ", txID)
}
formatKey = string(rawFormatKey)
}
txIter := w.DB.IteratorPrefix(calcAnnotatedKey(formatKey))
defer txIter.Release()
for txIter.Next() {
annotatedTx := &query.AnnotatedTx{}
iferr := json.Unmarshal(txIter.Value(), annotatedTx); err ! =nil {
return nil, err
}
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
}
return annotatedTxs, nil
}
Copy the code
This method seems long, but the logic is actually quite simple. If the txID is passed, the wallet will look for the transaction object with the specified ID. Otherwise, extract them all (which is the case in this article). CalcTxIndexKey (txID)
wallet/indexer.go#L61-L63
func calcTxIndexKey(txID string) []byte {
return []byte(TxIndexPrefix + txID)
}
Copy the code
Where TxIndexPrefix is TID:.
CalcAnnotatedKey (formatKey) is defined as:
wallet/indexer.go#L53-L55
func calcAnnotatedKey(formatKey string) []byte {
return []byte(TxPrefix + formatKey)
}
Copy the code
The value of TxPrefix is TXS:.
Let’s go to the second part of the listTransactions, the detail. If the detail is false, only need to summary, so will call aleem walji allet. GetTransactionsSummary (the transactions) :
wallet/indexer.go#L453-L486
func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
Txs := []TxSummary{}
for _, annotatedTx := range transactions {
tmpTxSummary := TxSummary{
Inputs: make([]Summary, len(annotatedTx.Inputs)),
Outputs: make([]Summary, len(annotatedTx.Outputs)),
ID: annotatedTx.ID,
Timestamp: annotatedTx.Timestamp,
}
for i, input := range annotatedTx.Inputs {
tmpTxSummary.Inputs[i].Type = input.Type
tmpTxSummary.Inputs[i].AccountID = input.AccountID
tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
tmpTxSummary.Inputs[i].AssetID = input.AssetID
tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
tmpTxSummary.Inputs[i].Amount = input.Amount
tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
}
for j, output := range annotatedTx.Outputs {
tmpTxSummary.Outputs[j].Type = output.Type
tmpTxSummary.Outputs[j].AccountID = output.AccountID
tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
tmpTxSummary.Outputs[j].AssetID = output.AssetID
tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
tmpTxSummary.Outputs[j].Amount = output.Amount
}
Txs = append(Txs, tmpTxSummary)
}
return Txs
}
Copy the code
This section of code is quite straightforward, that is, take part of the important information from the transactions element, form a new TxSummary object, return to the past. Finally, these objects are returned to the front end as JSON.
So today’s small problem can be solved, because of the previous experience can be used, so the feeling is relatively simple.