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:

  1. How does the front end take the transaction data and display it?
  2. 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:

  1. The first one wants to get it by argumenttransactions. ifaccount_idIf there is a value, take it, that is, a transaction owned by an account; Otherwise, useidGo 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.GetTransactionsByTxIDIt should also be possible to handle an empty string as an argument
  2. Code number 2, ifdetailforfalse(If no value is passed in front, it should be the defaultfalse, then get the one in fronttransactionsBecome 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:

  1. The first code iterates through the databaseTxPrefixIs the prefix value, whereTxPrefixforTXS:, and now we’re going to iterate. Here,DBThere is no doubt that iswalletThe leveldb
  2. The traversal starts at 2
  3. And the third place is where you put each elementValueTake it out, it’s in JSON format, and convert it toAnnotatedTxObject.txIterEach element of is a key-pair that hasKey(), there areValue()Here we only useValue()
  4. The fourth place is the current oneannotatedTxObject if its input or output contains an account id equal toaccountId, then it is what we need. Use laterannotateTxsAssetMethods theannotatedTxAsset-related properties in the object (e.galiasEtc.) 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.