Peel the comparison to the original look at Code 11: How the original account is created via interface/create-account

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 previous section, we explored how the data was sent from the front end to the backend when registering from the browser's dashboard, and how the backend created the key. This article will continue to discuss how the account is created by the interface than the original /create-account .

In front we know that the API.buildHandler interface configuration associated with creating accounts is configured in:

api/api.go#l164-l244

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

As you can see, /create-account the corresponding handler is a.createAccount that it is the focus of our research in this paper. What's on the outside jsonHandler is the conversion between the automatic JSON and the go data type, as discussed before, no longer mentioned here.

Let's look at a.createAccount the code first:

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 {    // 1.     acc, err := a.wallet.AccountMgr.Create(ctx, ins.RootXPubs, ins.Quorum, ins.Alias)    if err != nil {        return NewErrorResponse(err)    }    // 2.     annotatedAccount := account.Annotated(acc)    log.WithField("account ID", annotatedAccount.ID).Info("Created account")    // 3.    return NewSuccessResponse(annotatedAccount)}

As you can see, it requires the front end to pass over root_xpubs , quorum and alias These three parameters, we also saw in the previous article, the front end also did pass over. These three parameters, through jsonHandler the conversion, to this method, has become the appropriate go type, we can directly use.

This method is divided into three main pieces:

    1. Use a.wallet.AccountMgr.Create and user-sent parameters to create the appropriate account
    2. Called to convert the account account.Annotated(acc) object into an object that can be JSON
    3. Sends the success message back to the front end. This information is automatically converted to JSON by Jsonhandler to the front end for displaying the prompt message

3rd Step Nothing to say, we focus on the first two steps, the following will be combined with the source code in detail.

Create the appropriate account

Create an account using the a.wallet.AccountMgr.Create method that looks at the code first:

account/accounts.go#l145-l174

Create creates a new Account.func (M *manager) Create (CTX context. Context, Xpubs []CHAINKD.    xpub, quorum int, alias string) (*account, error) {M.accountmu.lock () defer m.accountmu.unlock ()//1. Normalizedalias: = Strings. ToLower (Strings.    Trimspace (alias))//2. If existed: = M.db.get (Aliaskey (Normalizedalias));     existed! = Nil {return nil, Errduplicatealias}//3. Signer, err: = Signers. Create ("Account", xpubs, Quorum, M.getnextaccountindex ()) ID: = Signers. Idgenerate () if err! = Nil {return nil, errors.    Wrap (ERR)}//4.     Account: = &account{signer:signer, Id:id, Alias:normalizedalias}//5. Rawaccount, err: = json.     Marshal (account) if err! = Nil {return nil, errmarshalaccount}//6. Storebatch: = M.db.newbatch () AccountID: = Key (ID) storebatch.set (accountID, Rawaccount) Storebatch.set (Aliaskey ( Normalizedalias), []byte (ID)) Storebatch.write () return account, nil}

We divide the method into 6 pieces, which are explained in turn:

    1. Standardize correction of incoming account aliases, such as removing two blanks and lowercase
    2. Find out if the alias has been used in the database. Since the account and alias are one by one, the account creation succeeds and the alias is recorded in the database. So if you can find it from the database, the description is already occupied and an error message is returned. So the front desk can remind the user to change.
    3. Create a Signer , in fact, the correctness of the xpubs parameters, quorum such as check, no problem will be the information bundled together, or return an error. SignerI feel like I've checked the meaning of a signed word.
    4. The 3rd step created by the signer and ID, as well as the previous standardized aliases picked up, put together, formed an account
    5. Turn the account object into JSON to facilitate subsequent storage
    6. Keep the account related data in the database, where the aliases correspond to the IDs (to facilitate the existence of the query aliases later), and the IDs correspond to the Accounts object (JSON format) to save the specific information

The 3rd step in these steps involves a lot of methods that need to be analyzed in more detail:

Signers. Create

Blockchain/signers/signers.go#l67-l90

// Create creates and stores a Signer in the databasefunc Create(signerType string, xpubs []chainkd.XPub, quorum int, keyIndex uint64) (*Signer, error) {    // 1.     if len(xpubs) == 0 {        return nil, errors.Wrap(ErrNoXPubs)    }    // 2.    sort.Sort(sortKeys(xpubs)) // this transforms the input slice    for i := 1; i < len(xpubs); i++ {        if bytes.Equal(xpubs[i][:], xpubs[i-1][:]) {            return nil, errors.WithDetailf(ErrDupeXPub, "duplicated key=%x", xpubs[i])        }    }    // 3.     if quorum == 0 || quorum > len(xpubs) {        return nil, errors.Wrap(ErrBadQuorum)    }    // 4.    return &Signer{        Type:     signerType,        XPubs:    xpubs,        Quorum:   quorum,        KeyIndex: keyIndex,    }, nil}

This method can be divided into 4 pieces, mainly check whether the parameters are correct, or relatively clear:

    1. Xpubs cannot be empty
    2. Xpubs cannot have duplicates. Check the time to sort first, and then see whether the adjacent two are equal. I think this piece of code should be drawn out, such as findDuplicated this method, directly placed here too much detail.
    3. Check quorum that it means "required number of signatures", it must be less than or equal to the number of xpubs, but cannot be 0. What does this parameter have to do with something that might have touched the core of the comparison and put it in the future?
    4. Pack all the information together and call itSinger

In addition, the 2nd place is still a need to pay attention to sortKeys . What it actually corresponds to is type sortKeys []chainkd.XPub , why do you do it, instead of just passing it on xpubs sort.Sort ?

This is because the sort.Sort objects that need to be passed in have the following interfaces:

type Interface interface {    // Len is the number of elements in the collection.    Len() int    // Less reports whether the element with    // index i should sort before the element with index j.    Less(i, j int) bool    // Swap swaps the elements with indexes i and j.    Swap(i, j int)}

But xpubs it's not. So when we redefine its type sortKeys , we can add these methods:

Blockchain/signers/signers.go#l94-l96

func (s sortKeys) Len() int           { return len(s) }func (s sortKeys) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }func (s sortKeys) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

M.getnextaccountindex ()

And then signers.Create("account", xpubs, quorum, m.getNextAccountIndex()) it's in the m.getNextAccountIndex() code as follows:

account/accounts.go#l119-l130

func (m *Manager) getNextAccountIndex() uint64 {    m.accIndexMu.Lock()    defer m.accIndexMu.Unlock()    var nextIndex uint64 = 1    if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {        nextIndex = common.BytesToUnit64(rawIndexBytes) + 1    }    m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))    return nextIndex}

As can be seen from this method, it is used to generate self-increasing numbers. This number is stored in the database, its key is accountIndexKey (constant, value []byte("AccountIndex") ), the value of the first time 1 , and then each call will add 1, and return it also saved in the database. This will not be lost if the number is restarted than the original program.

Signers. Idgenerate ()

On the code:

Blockchain/signers/idgenerate.go#l21-l41

//IDGenerate generate signer unique idfunc IDGenerate() string {    var ourEpochMS uint64 = 1496635208000    var n uint64    nowMS := uint64(time.Now().UnixNano() / 1e6)    seqIndex := uint64(nextSeqID())    seqID := uint64(seqIndex % 1024)    shardID := uint64(5)    n = (nowMS - ourEpochMS) << 23    n = n | (shardID << 10)    n = n | seqID    bin := make([]byte, 8)    binary.BigEndian.PutUint64(bin, n)    encodeString := base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(bin)    return encodeString}

As you can see from the code, the algorithm is quite complex and, from the annotations, it is to generate a "No Duplicates" ID. If we look at the algorithm in the code and find that it doesn't have a relationship with our key or account, I don't quite understand why it's not possible to use an algorithm like UUID directly if you just need a duplicate ID. Does the algorithm have a name? Already mentioned issue asked the developer: https://github.com/Bytom/bytom/issues/926

Now it's time to go back to our main line a.wallet.AccountMgr.Create . The process of creating an account has been basically said, but there are still some places we haven't analyzed:

    1. The database has been mentioned many times, so what database is used? Where did the initialization take place?
    2. a.wallet.AccountMgr.Create AccountMgr where are the corresponding objects in this method constructed?

Initialization of AccountMgr the database and

Compared to the original internal use of the LEVELDB database, from the configuration file config.toml can be seen:

$ cat config.tomlfast_sync = truedb_backend = "leveldb"

This is a very high-performance Key-value NoSQL database developed by Google, and bitcoin is used for it.

Use it in code to save a variety of data, such as chunks, accounts, and so on.

Let's take a look at where it was initialized.

As you can see, there are a large number of initialization operations associated with the database and account when creating the original node object:

node/node.go#l59-l142

func NewNode(config *cfg.Config) *Node {    // ...    // Get store    coreDB := dbm.NewDB("core", config.DBBackend, config.DBDir())    store := leveldb.NewStore(coreDB)    tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())    accessTokens := accesstoken.NewStore(tokenDB)    // ...    txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())    txFeed = txfeed.NewTracker(txFeedDB, chain)    // ...    if !config.Wallet.Disable {        // 1.         walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())        // 2.        accounts = account.NewManager(walletDB, chain)        assets = asset.NewRegistry(walletDB, chain)        // 3.         wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)        // ...    }    // ...}

So what we're using in this article is here, where the walletDB number 1 in the code above corresponds.

In addition, AccountMgr the initialization is done in this method as well. As you can see, in the 2nd place, the generated accounts object is the one we mentioned earlier a.wallet.AccountMgr AccountMgr . This can be seen from the 3rd place, accounts passed as a parameter to the NewWallet generated wallet object, the corresponding field is AccountMgr .

Then, when the node object starts, it starts the Web API service:

node/node.go#l169-l180

func (n *Node) OnStart() error {    // ...    n.initAndstartApiServer()    // ...}

In the initAndstartApiServer method, the API corresponding object is created:

node/node.go#l161-l167

func (n *Node) initAndstartApiServer() {    n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)    // ...}

As you can see, it n.wallet passes the object NewAPI , so it /create-account a.createAccount can be used in the corresponding handler, because that's what it a.wallet.AccountMgr.Create a means api .

In this case, the process of creating the account and the initialization of the associated object are all clear to us.

Annotated (ACC)

Here's the 2nd piece of code that goes back to us API.createAccount :

    // 2.     annotatedAccount := account.Annotated(acc)    log.WithField("account ID", annotatedAccount.ID).Info("Created account")

Let's take a look account.Annotated(acc) :

Account/indexer.go#l27-l36

//Annotated init an annotated account objectfunc Annotated(a *Account) *query.AnnotatedAccount {    return &query.AnnotatedAccount{        ID:       a.ID,        Alias:    a.Alias,        Quorum:   a.Quorum,        XPubs:    a.XPubs,        KeyIndex: a.KeyIndex,    }}

What appears here query refers to a package in the original project, the blockchain/query corresponding definition is AnnotatedAccount as follows:

Blockchain/query/annotated.go#l57-l63

type AnnotatedAccount struct {    ID       string           `json:"id"`    Alias    string           `json:"alias,omitempty"`    XPubs    []chainkd.XPub   `json:"xpubs"`    Quorum   int              `json:"quorum"`    KeyIndex uint64           `json:"key_index"`}

As you can see, its fields are similar to the ones we had in the process of creating the account, and the difference is that there are some JSON-related annotations behind it. In the previous account.Annotated method, it is also simple to assign the Account numbers in the object to it.

Why do you need one AnnotatedAccount ? The reason is simple because we need to pass this data to the front end. At API.createAccount the end of the 3rd step, it will return to the front end NewSuccessResponse(annotatedAccount) , since this value will be jsonHandler converted to JSON, so it needs some JSON-related annotations.

At the same time, we can also AnnotatedAccount find out what kind of data we will return to the front end, based on the fields.

Here, we are almost clear /create-account about how to create an account based on user-submitted parameters.

Note: In the process of reading code, part of the code is reconstructed, mainly from a number of large methods to decompose some more descriptive small methods, as well as some variable name modification, increase readability. #924

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.