Peel the comparison to the original look at code 12: How is the original address created by/create-account-receiver?

Source: Internet
Author: User

Author: freewind

Compared to the original project warehouse:

GitHub Address: Https://github.com/Bytom/bytom

Gitee Address: Https://gitee.com/BytomBlockc ...

In the dashboard, we can create an address for an account so that we can transfer between two addresses. In this article, we'll take a look at the code first, rather than how the original address was created.

First look at how we operate in the dashboard.

We can click on the "Accounts" on the left to display my account information on the right. Note there is a "Create Address" link in the upper-right corner:

Once clicked, it will generate an address for the account I am currently selecting, and will be ready for use:

In this article, we are going to look at how this process is implemented, divided into two small problems:

    1. How does the frontend send the request to the background interface?
    2. How to create an address than the original background?

How does the frontend send the request to the background interface?

In the previous article, we started with the front end, and in the react component, we found the data that was previously sent using the interface. Because these processes are similar, in this article we simplify and give the found code directly.

The first is the react component of "Create Address" in the page:

Https://github.com/Bytom/dash ...

 class Accountshow extends Baseshow {//...//2.     Createaddress () {///...//3.      This.props.createAddress ({Account_alias:this.props.item.alias}). Then (({data}) = {this.listaddress () This.props.showModal (<div> <p>{lang = = = ' zh '? ' Copy this address for trading: ': ' Copy this adress to use in a transaction: '}</p> <copyableblock value={data.address} LA Ng={lang}/> </div>)})} render () {//... view = <pagetitle Title={title            } actions={[//1. <button classname= ' btn btn-link ' onclick={this.createaddress}> {lang = = ' en '?    ' New address ': ' Create address '} </button>,]}/>//...} // ...  }}

The 1th place above is the "Create Address" link corresponding to the code, it is actually a button, when clicked, the method will be called createAddress . And the 2nd place is the createAddress method, in which the 3rd place is called, which is the this.props.createAddress function passed in by the outside createAddress . It also sends a parameter account_alias that corresponds to the alias of the current account.

Continue to find createAddress the definition:

Https://github.com/Bytom/dash ...

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

As you can see, it is called more than the original interface /create-account-receiver .

Then we will go into the background than the original.

How to create an address than the original background?

In the original code, we can find /create-account-receiver the interface corresponding to the handler:

api/api.go#l164-l174

func (a *API) buildHandler() {    // ...    if a.wallet != nil {        // ...        m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))

It turned out to be a.createAccountReceiver . Let's go in:

Api/receivers.go#l9-l32

// 1.func (a *API) createAccountReceiver(ctx context.Context, ins struct {    AccountID    string `json:"account_id"`    AccountAlias string `json:"account_alias"`}) Response {    // 2.    accountID := ins.AccountID    if ins.AccountAlias != "" {        account, err := a.wallet.AccountMgr.FindByAlias(ctx, ins.AccountAlias)        if err != nil {            return NewErrorResponse(err)        }        accountID = account.ID    }    // 3.    program, err := a.wallet.AccountMgr.CreateAddress(ctx, accountID, false)    if err != nil {        return NewErrorResponse(err)    }    // 4.     return NewSuccessResponse(&txbuilder.Receiver{        ControlProgram: program.ControlProgram,        Address:        program.Address,    })}

The code in the method can be divided into 4 pieces, which still looks quite clear:

    1. The focus of the 1th block is mainly in the parameter block. As can be seen, this interface can receive 2 parameters account_id and account_alias , but just the front-end code passed over account_alias this one, what's going on?
    2. From the 2nd block here we can see that if this parameter is passed, it account_alias will be used to find the corresponding account, and then get the corresponding ID. Otherwise, use account_id the ID as account
    3. The 3rd block is to accountID create an address for the corresponding account.
    4. The 4th block Returns success information, which jsonHandler is sent to the front end via the outside conversion to a JSON object

In this, there are only two methods that need our attention, that is, the 2nd block a.wallet.AccountMgr.FindByAlias and the 3rd one a.wallet.AccountMgr.CreateAddress , we study in turn.

a.wallet.AccountMgr.FindByAlias

Directly on the code:

account/accounts.go#l176-l195

// FindByAlias retrieves an account's Signer record by its aliasfunc (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, error) {    // 1.     m.cacheMu.Lock()    cachedID, ok := m.aliasCache.Get(alias)    m.cacheMu.Unlock()    if ok {        return m.FindByID(ctx, cachedID.(string))    }    // 2.     rawID := m.db.Get(aliasKey(alias))    if rawID == nil {        return nil, ErrFindAccount    }    // 3.    accountID := string(rawID)    m.cacheMu.Lock()    m.aliasCache.Add(alias, accountID)    m.cacheMu.Unlock()    return m.FindByID(ctx, accountID)}

The structure of the method is also relatively simple, divided into 3 pieces:

    1. Use alias directly in the memory cache aliasCache to find the appropriate ID, find the call to find the FindByID full account data
    2. If it is not found in the cache, the alias is changed into the form required by the database, and the ID is found in the database. If not found, error
    3. If found, place alias and ID in the memory cache for later use, and call to FindByID find the full account data

The above mentioned aliasCache is Manager a field defined in the type:

Account/accounts.go#l78-l85

type Manager struct {    // ...    aliasCache *lru.Cache

lru.Cacheis provided by the go language, we don't delve into it.

And then it's used several times FindByID :

account/accounts.go#l197-l220

// FindByID returns an account's Signer record by its ID.func (m *Manager) FindByID(ctx context.Context, id string) (*Account, error) {    // 1.     m.cacheMu.Lock()    cachedAccount, ok := m.cache.Get(id)    m.cacheMu.Unlock()    if ok {        return cachedAccount.(*Account), nil    }    // 2.    rawAccount := m.db.Get(Key(id))    if rawAccount == nil {        return nil, ErrFindAccount    }    // 3.    account := &Account{}    if err := json.Unmarshal(rawAccount, account); err != nil {        return nil, err    }    // 4.    m.cacheMu.Lock()    m.cache.Add(id, account)    m.cacheMu.Unlock()    return account, nil}

This method is the same as in the previous routine, it is also quite clear:

    1. Find it in the memory cache first cache and return it directly. m.cacheis also defined Manager as an lru.Cache object in
    2. Not in the memory cache, just look in the database and find the corresponding JSON-formatted account object data based on the ID.
    3. The JSON-formatted data into a Account type of data, which is what is needed earlier
    4. Put it in the memory cache to cache be the id key

There is nothing to say here, because basically in the previous article is involved.

a.wallet.AccountMgr.CreateAddress

Continue looking at the method of generating the address:

account/accounts.go#l239-l246

// CreateAddress generate an address for the select accountfunc (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {    account, err := m.FindByID(ctx, accountID)    if err != nil {        return nil, err    }    return m.createAddress(ctx, account, change)}

As this method passes over the accountID account object, it needs to be checked again, and then the FindByID private method is called to create the createAddress address:

account/accounts.go#l248-l263

// 1.func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {    // 2.     if len(account.XPubs) == 1 {        cp, err = m.createP2PKH(ctx, account, change)    } else {        cp, err = m.createP2SH(ctx, account, change)    }    if err != nil {        return nil, err    }    // 3.    if err = m.insertAccountControlProgram(ctx, cp); err != nil {        return nil, err    }    return cp, nil}

The method can be divided into 3 sections:

    1. The main concern in block 1th is the return value. Method name CreateAddress , but return value or CtrlProgram , Address where is it? Addressis actually CtrlProgram a field in, so the caller can get the address
    2. There is a new discovery in the 2nd block of code where an account can have multiple key pairs (a reminder that a private key can have only one public key in an ellipse algorithm). Because this will call different methods depending on the number of public keys owned by the account. If the number of public keys is 1, the account is an exclusive account (managed by a key) and will be called, m.createP2PKH otherwise the account is managed by multiple public keys (possibly a federated account) and needs to be called m.createP2SH . These two methods, returned by the object cp , refer ControlProgram to, emphasizing that it is a control program, not an address, Address but only one of its fields
    3. Once created, insert the control program into the account.

Let's look at the case where the account in the 2nd block of code has only one key, and the method called is createP2PKH :

account/accounts.go#l265-l290

 func (M *manager) Createp2pkh (CTX context. Context, account *account, change bool) (*ctrlprogram, error) {idx: = M.getnextcontractindex (account.id) path: = Si Gners. Path (account. Signer, signers. Accountkeyspace, idx) derivedxpubs: = Chainkd. Derivexpubs (account. Xpubs, path) DERIVEDPK: = Derivedxpubs[0]. PublicKey () Pubhash: = Crypto. Ripemd160 (DERIVEDPK)//Todo:pass different params due to config address, err: = Common. Newaddresswitnesspubkeyhash (Pubhash, &consensus. ACTIVENETPARAMS) If err! = Nil {return nil, err} control, err: = Vmutil. P2wpkhprogram ([]byte (Pubhash)) if err! = Nil {return nil, err} return &ctrlprogram{AccountID : Account.id, Address:address. Encodeaddress (), Keyindex:idx, Controlprogram:control, Change:change,}, Nil} 

I'm sorry, the code of this method can not be confused, it seems to have touched the more than the original chain in the core of the place. It is difficult for us to interpret it rationally through these lines of code and a quick look, so this article can only be skipped and studied later. Again, it m.createP2SH is the same, we skip first. We'll have to settle this piece sooner or later, please wait.

Let's keep looking at the 3rd block m.insertAccountControlProgram method:

account/accounts.go#l332-l344

func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {    var hash common.Hash    for _, prog := range progs {        accountCP, err := json.Marshal(prog)        if err != nil {            return err        }        sha3pool.Sum256(hash[:], prog.ControlProgram)        m.db.Set(ContractKey(hash), accountCP)    }    return nil}

This method seems to be much easier, the main is to put forward the creation of a good CtrlProgram pass to it to save the database operation. Note that the 2nd parameter of this method is ...*CtrlProgram that it is a mutable parameter, but when used in this article, only one value is passed (there are multiple incoming in other places).

In the method, the progs variable, for each of them, first convert it to a JSON format, and then digest it, and finally through ContractKey the function to add a Contract: prefix to the summary, put in the database. This m.db is the LEVELDB database named after the analysis in the previous article wallet . This database key is very miscellaneous, saving various types of data, the prefix is differentiated.

Let's look at ContractKey the function, which is simple:

account/accounts.go#l57-l59

func ContractKey(hash common.Hash) []byte {    return append(contractPrefix, hash[:]...)}

These are contractPrefix constants []byte("Contract:") . From this name we can then come into contact with a new concept: the contract (contract), it appears that the front CtrlProgram is a contract, and the account is only a part of the contract (if so, leave us to verify later)

Writing here, I think the problem to solve is "more than how it was created by the /create-account-receiver address" has been solved almost.

Although it is regrettable that the core-related issues encountered in the process, such as the details of the creation of the address, we do not understand at present, but we once again touched the core. As I said in the previous article, the core part of the original is very complex, so I will try a variety of heuristics from the periphery to the center, each time only touches the core but does not go deep, until accumulated enough knowledge to delve deeper into the core. After all, for a new contact with the blockchain, in their own way to interpret than the original source code, or a very challenging thing. Compared to the original developers have been very hard, I still try to less trouble them.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.