成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

剝開比原看代碼12:比原是如何通過/create-account-receiver創(chuàng)建地址的?

oneasp / 2896人閱讀

摘要:繼續(xù)看生成地址的方法由于這個(gè)方法里傳過來的是而不是對(duì)象,所以還需要再用查一遍,然后,再調(diào)用這個(gè)私有方法創(chuàng)建地址該方法可以分成部分在第塊中主要關(guān)注的是返回值。

作者:freewind

比原項(xiàng)目倉(cāng)庫(kù):

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockc...

在比原的dashboard中,我們可以為一個(gè)帳戶創(chuàng)建地址(address),這樣就可以在兩個(gè)地址之間轉(zhuǎn)帳了。在本文,我們將結(jié)合代碼先研究一下,比原是如何創(chuàng)建一個(gè)地址的。

首先看看我們?cè)赿ashboard中的是如何操作的。

我們可以點(diǎn)擊左側(cè)的"Accounts",在右邊顯示我的帳戶信息。注意右上角有一個(gè)“Create Address”鏈接:

點(diǎn)擊后,比原會(huì)為我當(dāng)前選擇的這個(gè)帳戶生成一個(gè)地址,馬上就可以使用了:

本文我們就要研究一下這個(gè)過程是怎么實(shí)現(xiàn)的,分成了兩個(gè)小問題:

前端是如何向后臺(tái)接口發(fā)送請(qǐng)求的?

比原后臺(tái)是如何創(chuàng)建地址的?

前端是如何向后臺(tái)接口發(fā)送請(qǐng)求的?

在前一篇文章中,我們也是先從前端開始,在React組件中一步步找到了使用了接口,以前發(fā)送的數(shù)據(jù)。由于這些過程比較相似,在本文我們就簡(jiǎn)化了,直接給出找到的代碼。

首先是頁(yè)面中的"Create Address"對(duì)應(yīng)的React組件:

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(

{lang === "zh" ? "拷貝這個(gè)地址以用于交易中:" : "Copy this address to use in a transaction:"}

) }) } render() { // ... view = {lang === "zh" ? "新建地址" : "Create address"} , ]} /> // ... } // ... } }

上面的第1處就是"Create Address"鏈接對(duì)應(yīng)的代碼,它實(shí)際上是一個(gè)Button,當(dāng)點(diǎn)擊后,會(huì)調(diào)用createAddress方法。而第2處就是這個(gè)createAddress方法,在它里面的第3處,又將調(diào)用this.props.createAddress,也就是由外部傳進(jìn)來的createAddress函數(shù)。同時(shí),它還要發(fā)送一個(gè)參數(shù)account_alias,它對(duì)應(yīng)就是當(dāng)前帳戶的alias。

繼續(xù)可以找到createAddress的定義:

https://github.com/Bytom/dash...

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

可以看到,它調(diào)用的比原接口是/create-account-receiver。

然后我們就將進(jìn)入比原后臺(tái)。

比原后臺(tái)是如何創(chuàng)建地址的?

在比原的代碼中,我們可以找到接口/create-account-receiver對(duì)應(yīng)的handler:

api/api.go#L164-L174

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

原來是a.createAccountReceiver。我們繼續(xù)進(jìn)去:

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,
    })
}

方法中的代碼可以分成4塊,看起來還是比較清楚:

第1塊的關(guān)注點(diǎn)主要在參數(shù)這塊??梢钥吹?,這個(gè)接口可以接收2個(gè)參數(shù)account_idaccount_alias,但是剛才的前端代碼中傳過來了account_alias這一個(gè),怎么回事?

從第2塊這里可以看出,如果傳了account_alias這個(gè)參數(shù),則會(huì)以它為準(zhǔn),用它去查找相應(yīng)的account,再拿到相應(yīng)的id。否則的話,才使用account_id當(dāng)作account的id

第3塊是為accountID相應(yīng)的account創(chuàng)建一個(gè)地址

第4塊返回成功信息,經(jīng)由外面的jsonHandler轉(zhuǎn)換為JSON對(duì)象后發(fā)給前端

這里面,需要我們關(guān)注的只有兩個(gè)方法,即第2塊中的a.wallet.AccountMgr.FindByAlias和第3塊中的a.wallet.AccountMgr.CreateAddress,我們依次研究。

a.wallet.AccountMgr.FindByAlias

直接上代碼:

account/accounts.go#L176-L195

// FindByAlias retrieves an account"s Signer record by its alias
func (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)
}

該方法的結(jié)構(gòu)同樣比較簡(jiǎn)單,分成了3塊:

直接用alias在內(nèi)存緩存aliasCache里找相應(yīng)的id,找到的話調(diào)用FindByID找出完整的account數(shù)據(jù)

如果cache中沒找到,則將該alias變成數(shù)據(jù)庫(kù)需要的形式,在數(shù)據(jù)庫(kù)里找id。如果找不到,報(bào)錯(cuò)

找到的話,把a(bǔ)lias和id放在內(nèi)存cache中,以備后用,同時(shí)調(diào)用FindByID找出完整的account數(shù)據(jù)

上面提到的aliasCache是定義于Manager類型中的一個(gè)字段:

account/accounts.go#L78-L85

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

lru.Cache是由Go語(yǔ)言提供的,我們就不深究了。

然后就是用到多次的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
}

這個(gè)方法跟前面的套路一樣,也比較清楚:

先在內(nèi)存緩存cache中找,找到就直接返回。m.cache也是定義于Manager中的一個(gè)lru.Cache對(duì)象

內(nèi)存緩存中沒有,就到數(shù)據(jù)庫(kù)里找,根據(jù)id找到相應(yīng)的JSON格式的account對(duì)象數(shù)據(jù)

把JSON格式的數(shù)據(jù)變成Account類型的數(shù)據(jù),也就是前面需要的

把它放到內(nèi)存緩存cache中,以id為key

這里感覺沒什么說的,因?yàn)榛旧显谇耙黄忌婕暗搅恕?/p> a.wallet.AccountMgr.CreateAddress

繼續(xù)看生成地址的方法:

account/accounts.go#L239-L246

// CreateAddress generate an address for the select account
func (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)
}

由于這個(gè)方法里傳過來的是accountID而不是account對(duì)象,所以還需要再用FindByID查一遍,然后,再調(diào)用createAddress這個(gè)私有方法創(chuàng)建地址:

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
}

該方法可以分成3部分:

在第1塊中主要關(guān)注的是返回值。方法名為CreateAddress,但是返回值或者CtrlProgram,那么Address在哪兒?實(shí)際上AddressCtrlProgram中的一個(gè)字段,所以調(diào)用者可以拿到Address

在第2塊代碼這里有一個(gè)新的發(fā)現(xiàn),原來一個(gè)帳戶是可以有多個(gè)密鑰對(duì)的(提醒:在橢圓算法中一個(gè)私鑰只能有一個(gè)公鑰)。因?yàn)檫@里將根據(jù)該account所擁有的公鑰數(shù)量不同,調(diào)用不同的方法。如果公鑰數(shù)量為1,說明該帳戶是一個(gè)獨(dú)享帳戶(由一個(gè)密鑰管理),將調(diào)用m.createP2PKH;否則的話,說明這個(gè)帳戶由多個(gè)公鑰共同管理(可能是一個(gè)聯(lián)合帳戶),需要調(diào)用m.createP2SH。這兩個(gè)方法,返回的對(duì)象cp,指的是ControlProgram,強(qiáng)調(diào)了它是一種控制程序,而不是一個(gè)地址,地址Address只是它的一個(gè)字段

創(chuàng)建好以后,把該控制程序插入到該帳戶中

我們先看第2塊代碼中的帳戶只有一個(gè)密鑰的情況,所調(diào)用的方法為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 := signers.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
}

不好意思,這個(gè)方法的代碼一看我就搞不定了,看起來是觸及到了比較比原鏈中比較核心的地方。我們很難通過這幾行代碼以及快速的查閱來對(duì)它進(jìn)行合理的解釋,所以本篇只能跳過,以后再專門研究。同樣,m.createP2SH也是一樣的,我們也先跳過。我們?cè)缤硪堰@一塊解決的,請(qǐng)等待。

我們繼續(xù)看第3塊中m.insertAccountControlProgram方法:

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
}

這個(gè)方法看起來就容易多了,主要是把前面創(chuàng)建好的CtrlProgram傳過來,對(duì)它進(jìn)行保存數(shù)據(jù)庫(kù)的操作。注意這個(gè)方法的第2個(gè)參數(shù)是...*CtrlProgram,它是一個(gè)可變參數(shù),不過在本文中用到的時(shí)候,只傳了一個(gè)值(在其它使用的地方有傳入多個(gè)的)。

在方法中,對(duì)progs進(jìn)行變量,對(duì)其中的每一個(gè),都先把它轉(zhuǎn)換成JSON格式,然后再對(duì)它進(jìn)行摘要,最后通過ContractKey函數(shù)給摘要加一個(gè)Contract:的前綴,放在數(shù)據(jù)庫(kù)中。這里的m.db在之前文章中分析過,它就是那個(gè)名為wallet的leveldb數(shù)據(jù)庫(kù)。這個(gè)數(shù)據(jù)庫(kù)的Key挺雜的,保存了各種類型的數(shù)據(jù),以前綴區(qū)分。

我們看一下ContractKey函數(shù),很簡(jiǎn)單:

account/accounts.go#L57-L59

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

其中的contractPrefix為常量[]byte("Contract:")。從這個(gè)名字我們可以又將接觸到一個(gè)新的概念:合約(Contract),看來前面的CtrlProgram就是一個(gè)合約,而帳戶只是合約中的一部分(是否如此,留待我們以后驗(yàn)證)

寫到這里,我覺得這次要解決的問題“比原是如何通過/create-account-receiver創(chuàng)建地址的”已經(jīng)解決的差不多了。

雖然很遺憾在過程中遇到的與核心相關(guān)的問題,比如創(chuàng)建地址的細(xì)節(jié),我們目前還沒法理解,但是我們又再一次觸及到了核心。在之前的文章中我說過,比原的核心部分是很復(fù)雜的,所以我將嘗試多種從外圍向中心的試探方式,每次只觸及核心但不深入,直到積累了足夠的知識(shí)再深入研究核心。畢竟對(duì)于一個(gè)剛接觸區(qū)塊鏈的新人來說,以自己獨(dú)立的方式來解讀比原源代碼,還是一件很有挑戰(zhàn)的事情。比原的開發(fā)人員已經(jīng)很辛苦了,我還是盡量少麻煩他們。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/24180.html

相關(guān)文章

  • 剝開原看代碼03:比原如何監(jiān)聽p2p端口

    摘要:?jiǎn)?dòng)直到進(jìn)入所以我們首先需要知道,比原在源代碼中是如何啟動(dòng),并且一步步走進(jìn)了的世界。后面省略了一些代碼,主要是用來獲取當(dāng)前監(jiān)聽的實(shí)際以及外網(wǎng),并記錄在日志中。 比原是如何監(jiān)聽p2p端口的 我們知道,在使用bytomd init --chain_id mainnet/testnet/solonet初始化比原的時(shí)候,它會(huì)根據(jù)給定的chain_id的不同,使用不同的端口(參看config/t...

    layman 評(píng)論0 收藏0
  • 剝開原看代碼10:比原如何通過/create-key接口創(chuàng)建密鑰

    摘要:如果傳的是,就會(huì)在內(nèi)部使用默認(rèn)的隨機(jī)數(shù)生成器生成隨機(jī)數(shù)并生成密鑰。使用的是,生成的是一個(gè)形如這樣的全球唯一的隨機(jī)數(shù)把密鑰以文件形式保存在硬盤上。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇,我們探討了從瀏覽器的dashb...

    ccj659 評(píng)論0 收藏0
  • 剝開原看代碼11:比原如何通過接口/create-account創(chuàng)建帳戶

    摘要:而本文將繼續(xù)討論,比原是如何通過接口來創(chuàng)建帳戶的。把各信息打包在一起,稱之為另外,在第處還是一個(gè)需要注意的。比原在代碼中使用它保存各種數(shù)據(jù),比如區(qū)塊帳戶等。到這里,我們已經(jīng)差不多清楚了比原的是如何根據(jù)用戶提交的參數(shù)來創(chuàng)建帳戶的。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://git...

    haobowd 評(píng)論0 收藏0
  • 剝開原看代碼06:比原如何把請(qǐng)求區(qū)塊數(shù)據(jù)信息發(fā)出去

    摘要:作者比原項(xiàng)目倉(cāng)庫(kù)地址地址在前一篇中,我們說到,當(dāng)比原向其它節(jié)點(diǎn)請(qǐng)求區(qū)塊數(shù)據(jù)時(shí),會(huì)發(fā)送一個(gè)把需要的區(qū)塊告訴對(duì)方,并把該信息對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)放入對(duì)應(yīng)的通道中,等待發(fā)送。這個(gè)就是真正與連接對(duì)象綁定的一個(gè)緩存區(qū),寫入到它里面的數(shù)據(jù),會(huì)被發(fā)送出去。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https:...

    CloudwiseAPM 評(píng)論0 收藏0
  • 剝開原看代碼16:比原如何通過/list-transactions顯示交易信息

    摘要:前端是如何獲取交易數(shù)據(jù)并顯示出來的我們先在比原的前端代碼庫(kù)中尋找。這過程中的推導(dǎo)就不再詳說,需要的話可以看前面講解比原是如何顯示余額的那篇文章。的定義是其中的值是。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇文章中,我們...

    hankkin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<