溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

比原怎么通過create-account-receiver創(chuàng)建地址

發(fā)布時間:2021-12-20 16:44:41 來源:億速云 閱讀:124 作者:iii 欄目:互聯(lián)網(wǎng)科技

本篇內(nèi)容介紹了“比原怎么通過create-account-receiver創(chuàng)建地址”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

前端是如何向后臺接口發(fā)送請求的?

首先是頁面中的"Create Address"對應的React組件

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 to use in a transaction:'}</p>
        <CopyableBlock value={data.address} lang={lang}/>
      </div>)
    })
  }

  render() {
      // ...
      view = 
        <PageTitle
          title={title}
          actions={[
            // 1.
            <button className='btn btn-link' onClick={this.createAddress}>
              {lang === 'zh' ? '新建地址' : 'Create address'}
            </button>,
          ]}
        />
       // ...
    }
    // ...
  }
}

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

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

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

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

然后我們就將進入比原后臺。

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

在比原的代碼中,我們可以找到接口/create-account-receiver對應的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ù)進去:

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. 第1塊的關注點主要在參數(shù)這塊。可以看到,這個接口可以接收2個參數(shù)account_idaccount_alias,但是剛才的前端代碼中傳過來了account_alias這一個,怎么回事?

  2. 從第2塊這里可以看出,如果傳了account_alias這個參數(shù),則會以它為準,用它去查找相應的account,再拿到相應的id。否則的話,才使用account_id當作account的id

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

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

這里面,需要我們關注的只有兩個方法,即第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)同樣比較簡單,分成了3塊:

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

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

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

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

account/accounts.go#L78-L85

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

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

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

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

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

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

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

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

這里感覺沒什么說的,因為基本上在前一篇都涉及到了。

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

由于這個方法里傳過來的是accountID而不是account對象,所以還需要再用FindByID查一遍,然后,再調(diào)用createAddress這個私有方法創(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. 在第1塊中主要關注的是返回值。方法名為CreateAddress,但是返回值或者CtrlProgram,那么Address在哪兒?實際上AddressCtrlProgram中的一個字段,所以調(diào)用者可以拿到Address

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

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

我們先看第2塊代碼中的帳戶只有一個密鑰的情況,所調(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
}

不好意思,這個方法的代碼一看我就搞不定了,看起來是觸及到了比較比原鏈中比較核心的地方。我們很難通過這幾行代碼以及快速的查閱來對它進行合理的解釋,所以本篇只能跳過,以后再專門研究。同樣,m.createP2SH也是一樣的,我們也先跳過。我們早晚要把這一塊解決的,請等待。

我們繼續(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
}

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

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

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

account/accounts.go#L57-L59

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

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

“比原怎么通過create-account-receiver創(chuàng)建地址”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI