Author: freewind

Biyuan Project warehouse:

Making address: https://github.com/Bytom/bytom

Gitee address: https://gitee.com/BytomBlockchain/bytom

In the previous article, we took a cursory look at how the original Dashboard was built, but didn’t delve into the details of the functionality mentioned. Starting with this article, we will study each of these features separately during this period.

In the previous article, when we first opened Dashboard in our browser, we were prompted for some aliases and passwords to create a key and account for us because we hadn’t created a key yet. Which corresponds to the following picture:

This article will examine how the parameters we fill in the front page are transferred to the back end step by step after we click the “Register” button.

As before, we’ll break this down into smaller pieces:

  1. Front end: When we fill out the form and click submit, how does the front end send data?
  2. Back end: How does the original back end receive data?

Front end: When we fill out the form and click submit, which interface is the data sent to the back end?

When we click the “Register” button, somewhere in the front page an operation must be triggered to request the webAPI interface of the original node. Which Web API is accessed? What about the submitted data? Let’s take a look at the front-end code first.

Note that the original front-end code is located in another project repository, ByTom/Dashboard. In order to match the original v1.0.1 code we are using in this series, I found the v1.0.0 code for Dashboard and submitted it to a separate project: Freewind/byTOM-dashboard-v1.0.0. Note that no changes have been made to the project code and that the master branch corresponds to the v1.0.0 branch of the official repository. The reason for making a separate one is that every time we refer to a piece of code in this article, we give a link to it on Github so readers can skip over to see the whole thing. Using a separate project makes the process much easier.

Since the React front page is mostly used, I suspect there will also be a component named Register in the code, or a button named Register in a form. After searching, we were lucky enough to find the register.jsx component file, which was exactly what we needed.

The highly simplified code looks like this:

src/features/app/components/Register/Register.jsx#L9-L148

class Register extends React.Component {
  // ...
  / / 4.
  submitWithErrors(data) {
    return new Promise((resolve, reject) = > {
      / / 5.
      this.props.registerKey(data)
        .catch((err) = > reject({_error: err.message}))
    })
  }
  // ...

  render() {
    // ...
    return (
      // ...
      / / 3.
      <form className={styles.form} onSubmit={handleSubmit(this.submitWithErrors)}>
        / / 1.
        <TextField
          title={lang === 'zh' ? 'Account Alias' : 'Account Alias'}
          placeholder={lang === 'zh' ? 'Please enter the account alias... ' : 'Please enter the account alias... '} fieldProps={accountAlias} /> <TextField title={lang === 'zh' ? 'Key Alias' : 'Key Alias'} placeholder={lang === 'zh'? 'Please enter key alias... ' : 'Please enter the key alias... '} fieldProps={keyAlias}/> <TextField title={lang === 'zh' ? 'Password' :' Password'} placeholder={lang === 'zh'? 'Please enter the key password... ' : 'Please enter the key password... '} fieldProps={password} type='password'/> <TextField title={lang === 'zh' ? } placeholder={lang === 'zh'? 'Please repeat the key password... ' : 'Please repeat the key password... '} fieldProps={repeatPassword} type='password'/> // 2. <button type='submit' className='btn btn-primary' disabled={submitting}> {lang === 'zh' ? </button> //.... </form> // ... ) }}Copy the code

There are five things to notice about the code above, and I’ve numbered them. Note that the five numbers are not listed from top to bottom, but in the order we care about them:

  1. The fields on the form are where we fill in our aliases and passwords. The thing to focus on here is eachTextFieldthefieldPropsProperty, which corresponds to the data we submit to the backgroundname
  2. That’s the Register button. Note that it’stypeissubmitThat is, clicking on it will trigger the locationformtheonSubmitmethods
  3. Back to theformIn the beginning. Pay attention to itonSubmitInside, it callshandleSubmit(this.submitWithErrors). One of thehandleSubmitIs the third party used by the formredux-formWe don’t care about it here, just know that we need to put our own handler functionthis.submitWithErrorsTo it. In the latter case, we will invoke the Web API provided by the original node
  4. In step 3this.submitWithErrorsI’m going to end up with something that’s defined heresubmitWithErrorsfunction
  5. submitWithErrorsAn asynchronous request will be made and the final call will be sent in from outsideregisterKeyfunction

From here we can’t see which API is being called, so we have to keep looking for registerKey. RegisterKey is soon found in the same file:

src/features/app/components/Register/Register.jsx#L176-L180

(dispatch) => ({
    registerKey: (token) = > dispatch(actions.core.registerKey(token)),
    // ...
  })
Copy the code

It will also call actions. Core. RegisterKey:

src/features/core/actions.js#L44-L87

const registerKey = (data) = > {
  return (dispatch) = > {
    // ...
    / / 1.1
    const keyData = {
      'alias': data.keyAlias,
      'password': data.password
    }
    / / 1.2
    return chainClient().mockHsm.keys.create(keyData)
      .then((resp) = > {
        // ...
        / / 2.1
        const accountData = {
          'root_xpubs':[resp.data.xpub],
          'quorum':1.'alias': data.accountAlias}
        / / 2.2
        dispatch({type: 'CREATE_REGISTER_KEY', data})

        / / 2.3
        chainClient().accounts.create(accountData)
          .then((resp) = > {
            // ...
            / / 2.4
            if(resp.status === 'success') {
              dispatch({type: 'CREATE_REGISTER_ACCOUNT', resp})
            }
          })
    // ...
      })
    // ...}}Copy the code

As you can see, there’s a lot of work to be done in this function. And instead of calling the background interface once, as I initially expected, I called it twice (creating the key and creating the account). The following analysis:

  1. 1.1Is a parameter that needs to be prepared for the background to create a keyalias, one ispasswordThey are all filled in by the user
  2. 1.2Is to call the interface used to create the key in the backgroundkeyDataPass it on and get it backrespThen, perform subsequent operations
  3. 2.1Are parameters required for creating an account in the background, respectivelyroot_xpubs.quorumandalias, includingroot_xpubsIs the public key returned after the key is created.quorumI don’t know yet (TODO),aliasIs the account alias entered by the user
  4. 2.2This one doesn’t work (officially confirmed) because I can’t find a handle in the codeCREATE_REGISTER_KEYThe code. You can look at this issue# 28
  5. 2.3Call the background to create an accountaccountDataPass it. You can get it backresp
  6. 2.4After the call succeeds, use redux’sdispatchThe function distributes oneCREATE_REGISTER_ACCOUNTInformation. But this information doesn’t seem to be of much use.

For CREATE_REGISTER_ACCOUNT, I find two things relevant in the code:

  1. src/features/core/reducers.js#L229-L234
const accountInit = (state = false, action) = > {
  if (action.type == 'CREATE_REGISTER_ACCOUNT') {
    return true
  }
  return state
}
Copy the code
  1. src/features/app/reducers.js#L10-L115
export const flashMessages = (state = {}, action) = > {
  switch (action.type) {
    // ...
    case 'CREATE_REGISTER_ACCOUNT': {
      return newSuccess(state, 'CREATE_REGISTER_ACCOUNT')}// ...}}Copy the code

The first one looks useless, and the second one should be used to display relevant error messages when the operation is complete.

Let’s focus on the 1.2 and 2.3 background calls.

  1. chainClient().mockHsm.keys.create(keyData)The corresponding is:

src/sdk/api/mockHsmKeys.js#L3-L31

const mockHsmKeysAPI = (client) = > {
  return {
    create: (params, cb) = > {
      let body = Object.assign({}, params)
      const uri = body.xprv ? '/import-private-key' : '/create-key'

      return shared.tryCallback(
        client.request(uri, body).then(data= > data),
        cb
      )
    },
    // ...}}Copy the code

You can see that in the create method, if body.xprv is not found (which is the case in this article), the background /create-key interface is called. After a long trail, we finally found one.

  1. chainClient().accounts.create(accountData)The corresponding is:

src/sdk/api/accounts.js#L3-L30

const accountsAPI = (client) = > {
  return {
    create: (params, cb) = > shared.create(client, '/create-account', params, {cb, skipArray: true}),
    // ...}}Copy the code

And pretty soon, we also found that the interface that was called to create the account was /create-account

On the front end, we’re finally done. Next, you go to the original node (the back end).

Back end: How does the original back end receive data?

If we have any impression of the previous article, I will remember than the original after the start, in the Node. InitAndstartApiServer method start web API corresponding HTTP service, and in the API. The buildHandler () method of the function of the configuration will be many points, There must be an interface that we’re calling here.

Let’s look at the api.buildHandler method:

api/api.go#L164-L244

func (a *API) buildHandler(a) {
    walletEnable := false
    m := http.NewServeMux()

	ifa.wallet ! =nil {
		walletEnable = true
        // ...
		m.Handle("/create-account", jsonHandler(a.createAccount))
        // ...
		m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
		// ...
Copy the code

Soon, we found out:

  1. /create-account: the correspondinga.createAccount
  2. /create-key: the correspondinga.pseudohsmCreateKey

Let’s first look at a.pseudohsmCreateKey:

api/hsm.go#L23-L32

func (a *API) pseudohsmCreateKey(ctx context.Context, in struct {
	Alias    string `json:"alias"`
	Password string `json:"password"`
}) Response {
	// ...
}
Copy the code

As you can see, the second argument to pseudohsmCreateKey is a struct with two fields, Alias and Password, which corresponds to keyData passed from the front end. How is the value of this parameter converted from the submitted JSON data? As we said last time, this is done by the jsonHandler that surrounds a.pseudohsmCreateKey, which handles HTTP operations and converts JSON data to Go parameters that we need here. PseudohsmCreateKey can be used directly.

Since the boundary of our problem in this small problem is how the original background gets the data, we can stop analyzing this method at this point. How it creates the key will be discussed in more detail in a future article.

Then look at a.c reateAccount:

api/accounts.go#L15-L30

// POST /create-account
func (a *API) createAccount(ctx context.Context, ins struct {
	RootXPubs []chainkd.XPub `json:"root_xpubs"`
	Quorum    int            `json:"quorum"`
	Alias     string         `json:"alias"`
}) Response {
	// ...
}
Copy the code

As before, the parameters RootXPubs, Quorum, and Alias for this method are submitted by the front end and automatically converted by the jsonHandler.

Once we understand how the front and back end data interact in this article, we can easily generalize to more scenarios. There are still many pages and forms in the front end, and many places need to call the interface of the back end. I believe that according to the ideas of this article, it should be quickly found. If there are special cases, we will write a special article later.