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:
- How does the frontend send the request to the background interface?
- 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:
- 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?
- 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
- The 3rd block is to
accountID
create an address for the corresponding account.
- 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:
- Use alias directly in the memory cache
aliasCache
to find the appropriate ID, find the call to find the FindByID
full account data
- 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
- 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.Cache
is 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:
- Find it in the memory cache first
cache
and return it directly. m.cache
is also defined Manager
as an lru.Cache
object in
- Not in the memory cache, just look in the database and find the corresponding JSON-formatted account object data based on the ID.
- The JSON-formatted data into a
Account
type of data, which is what is needed earlier
- 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:
- The main concern in block 1th is the return value. Method name
CreateAddress
, but return value or CtrlProgram
, Address
where is it? Address
is actually CtrlProgram
a field in, so the caller can get the address
- 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
- 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.