Author: freewind
Compared to the original project warehouse:
GitHub Address: Https://github.com/Bytom/bytom
Gitee Address: Https://gitee.com/BytomBlockc ...
In the previous article, we have a rough look at how the original dashboard is done, but the various details mentioned in the function, and not in-depth study. So from the beginning of this article, we will study each of the functions mentioned in this period of time.
In the previous article, when we first opened dashboard in a browser, because we had not created a key, we were prompted to enter some aliases and passwords, creating a key and corresponding account for us. That's what the following picture corresponds to:
Then this article will study, when we click on the "Register" button, we fill in the front page of the parameters, exactly how to step by step to the back end of the original.
As before, we will subdivide this question and then conquer:
- Front End: When we fill out the form, point to the submission, than the original in the front-end is how to send data?
- Backend: How does the back end of the original receive the data?
Front End: When we fill out the form and click Submit, which interface does the data send to the backend?
When we click on the "Register" button, in the front page, there will be a certain place to trigger a request to the original node Webapi interface to make the operation. Which Web API are you accessing? What about the data submitted? Let's look at the front-end code first.
Note that the original front-end code is located in another project warehouse Bytom/dashboard. In order to match the code that we used in this series of articles over the original v1.0.1, I found the code for v1.0.0 in dashboard and submitted it to a separate project: freewind/bytom-dashboard-v1.0.0. Note that the project code has not been modified, and its master
branches correspond to branches of the official code warehouse v1.0.0
. The reason to get a separate one is because in the article, every time we quote a piece of code, we give you a link on GitHub that makes it easier for readers to jump over to see the full picture and use a separate project.
Since the front-end page is React
primarily used, I suspect that in the code there will be a component called register, or a button named register in the form. After searching, we were fortunate to find the Register.jsx component file, which is exactly what we need.
The highly simplified code is as follows:
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 ': ' Accounts alias '} Placeholder={lang = = = ' en '? ' Enter account alias ... ': ' Please enter the accounts alias ... '} Fieldprops={accountalias}/> <textfield Title={lang = = = ' zh '? ' Keys alias ': ' Key alias '} Placeholder={lang = = = ' en '? ' Enter the key alias ... ': ' Please enter the key alias ... '} fieldprops={keyalias}/> <textfield title={l Ang = = = ' zh '? ' Secret key ': ' Key Password '} Placeholder={lang = = = ' en '? ' Enter key password ... ': ' Please enter ' the key password ... '} FIELDPROps={password} type= ' password '/> <textfield Title={lang = = = ' en '? ' Re-enter key password ': ' Repeat your Key password '} Placeholder={lang = = = ' en '? ' Please re-enter the key password ... ': ' Repeat the key password ... '} fieldprops={repeatpassword} type= ' Password '/> 2. <button type= ' Submit ' classname= ' btn btn-primary ' disabled={submitting}> {lang = = ' en '? ' Registration ': ' Register '} </button>//.... }}
The above code, a total of 5 places to pay attention to, I used the number marked out. Note that these 5 numbers are not labeled from top to bottom, but in the order in which we are concerned:
- The individual input boxes on the form are where we fill in the aliases and passwords. The focus here is on each
TextField
fieldProps
attribute, which corresponds to the data we submit to the background.name
- That's the "Register" button. Note that it
type
is, that is, submit
when you click on it, it will trigger form
the onSubmit
method
- Back to
form
the beginning. Notice the inside of it onSubmit
, the call is handleSubmit(this.submitWithErrors)
. One of these handleSubmit
is from the third-party redux-form used in the form to handle the form submission, and we don't care about it here, just know that we need to pass our handler function to this.submitWithErrors
it. In the latter case, we will invoke a Web API that is more than the original node
- The 3rd step
this.submitWithErrors
will eventually go to the function defined here submitWithErrors
submitWithErrors
An asynchronous request will be initiated, eventually invoking a function passed in by the external registerKey
From here we don't see which API is called, so we have to go on looking for it registerKey
. It was soon found in the same document registerKey
:
src/features/app/components/register/register.jsx#l176-l180
(dispatch) => ({ registerKey: (token) => dispatch(actions.core.registerKey(token)), // ... })
It will then call actions.core.registerKey
this function:
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}) } }) // ... }) // ... }}
As you can see, there are still a lot of things to do in this function. And it wasn't the first time I had expected to call the background interface, but it was called two times (creating the key and creating the account, respectively). The following analysis:
1.1
is for the background to create the key and need to prepare the parameters, one is alias
, one is password
, they are filled by the user
1.2
is to invoke the interface used to create the key in the background, pass keyData
it over, and get the return, and then resp
do the subsequent processing
2.1
is to let the background create the account and need to prepare the parameters, respectively root_xpubs
, quorum
and alias
, which root_xpubs
is the public key that is returned after the key is created, is quorum
not currently known (TODO), alias
is the user filled out the account alias
2.2
This sentence has no effect (officially confirmed), because I did not find the code in the code to process CREATE_REGISTER_KEY
. You can see this issue#28.
2.3
Call the background to create the account, pass accountData
it over, you can get the returnresp
2.4
After the call succeeds, the Redux function is used to dispatch
distribute a CREATE_REGISTER_ACCOUNT
message. But the message doesn't seem to be much use.
About CREATE_REGISTER_ACCOUNT
, I found two correlations in the code:
- src/features/core/reducers.js#l229-l234
const accountInit = (state = false, action) => { if (action.type == 'CREATE_REGISTER_ACCOUNT') { return true } return state}
- 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') } // ... }}
The first one looks useless, and the second one should be used to display the relevant error message after the operation is complete.
So let's focus on the place where 1.2
the 2.3
two backstage calls.
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 ) }, // ... }}
You can see that in the create
method, if it is not found body.xprv
(which is the case in this article), the interface in the background is called /create-key
. After a long series of traces, we finally found one.
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}), // ... }}
Soon we'll be here. The interface that is called when the account is created is/create-account
On the front side, we finally finished the analysis. Next, you will go to the original node (i.e. the back end).
Backend: How does the back end of the original receive the data?
If we have an impression of the previous article, we will remember that the HTTP service corresponding to the Web API will be started in the method after startup, and there will be a Node.initAndstartApiServer
API.buildHandler()
lot of feature points in the method, which will definitely have the interface we call here.
Let's look at the API.buildHandler
method:
api/api.go#l164-l244
func (a *API) buildHandler() { walletEnable := false m := http.NewServeMux() if a.wallet != nil { walletEnable = true // ... m.Handle("/create-account", jsonHandler(a.createAccount)) // ... m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) // ...
Soon, we found:
/create-account
: correspondinga.createAccount
/create-key
: correspondinga.pseudohsmCreateKey
Let's take a look first 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 { // ...}
As you can see, pseudohsmCreateKey
the second parameter, is a struct
, it has two fields, respectively, Alias
and Password
this corresponds exactly to the parameters passed from the front end keyData
. So how does the value of this parameter be converted from the JSON data being submitted? The last time we talked about this, it's mostly done by the a.pseudohsmCreateKey
outside jsonHandler
, which handles the HTTP protocol, and converts the JSON data into the parameters of the go type needed here, which pseudohsmCreateKey
can be used directly.
Because in this small problem, the boundary of our problem is how to get the data from the original background, so we can stop the analysis of this method here. It specifically how to create the key, which will be discussed in detail later in the article.
Look again a.createAccount
:
Api/accounts.go#l15-l30
// POST /create-accountfunc (a *API) createAccount(ctx context.Context, ins struct { RootXPubs []chainkd.XPub `json:"root_xpubs"` Quorum int `json:"quorum"` Alias string `json:"alias"`}) Response { // ...}
As before, the parameters of this method RootXPubs
, Quorum
and Alias
are also submitted by the front end, and are jsonHandler
automatically converted by the good.
When we understand how the front-end data interacts in this article, it's easy to generalize to more scenarios. In front of a lot of pages and forms, in many places need to call back-end interface, I believe that according to the idea of this article, should be able to quickly find. If there is a more special situation, we will be a special article to explain later.