摘要:而本文將繼續討論,比原是如何通過接口來創建帳戶的。把各信息打包在一起,稱之為另外,在第處還是一個需要注意的。比原在代碼中使用它保存各種數據,比如區塊帳戶等。到這里,我們已經差不多清楚了比原的是如何根據用戶提交的參數來創建帳戶的。
作者:freewind
比原項目倉庫:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
在前面,我們探討了從瀏覽器的dashboard中進行注冊的時候,數據是如何從前端發到后端的,并且后端是如何創建密鑰的。而本文將繼續討論,比原是如何通過/create-account接口來創建帳戶的。
在前面我們知道在API.buildHandler中配置了與創建帳戶相關的接口配置:
api/api.go#L164-L244
func (a *API) buildHandler() { // ... if a.wallet != nil { // ... m.Handle("/create-account", jsonHandler(a.createAccount)) // ...
可以看到,/create-account對應的handler是a.createAccount,它是我們本文將研究的重點。外面套著的jsonHandler是用來自動JSON與GO數據類型之間的轉換的,之前討論過,這里不再說。
我們先看一下a.createAccount的代碼:
api/accounts.go#L15-L30
// POST /create-account func (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) }
可以看到,它需要前端傳過來root_xpubs、quorum和alias這三個參數,我們在之前的文章中也看到,前端也的確傳了過來。這三個參數,通過jsonHandler的轉換,到這個方法的時候,已經成了合適的GO類型,我們可以直接使用。
這個方法主要分成了三塊:
使用a.wallet.AccountMgr.Create以及用戶發送的參數去創建相應的帳戶
調用account.Annotated(acc),把account對象轉換成可以被JSON化的對象
向前端發回成功信息。該信息會被jsonHandler自動轉為JSON發到前端,用于顯示提示信息
第3步沒什么好說的,我們主要把目光集中在前兩步,下面將依次結合源代碼詳解。
創建相應的帳戶創建帳戶使用的是a.wallet.AccountMgr.Create方法,先看代碼:
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 }
我們把該方法分成了6塊,這里依次講解:
把傳進來的帳戶別名進行標準化修正,比如去掉兩頭空白并小寫
從數據庫中尋找該別名是否已經用過。因為帳戶和別名是一一對應的,帳戶創建成功后,會在數據庫中把別名記錄下來。所以如果能從數據庫中查找,說明已經被占用,會返回一個錯誤信息。這樣前臺就可以提醒用戶更換。
創建一個Signer,實際上就是對xpubs、quorum等參數的正確性進行檢查,沒問題的話會把這些信息捆綁在一起,否則返回錯誤。這個Signer我感覺是檢查過沒問題簽個字的意思。
把第3步創建的signer和id,還有前面的標準化之后的別名拿起來,放在一起,就組成了一個帳戶
把帳戶對象變成JSON,方便后面往數據庫里存
把帳戶相關的數據保存在數據庫,其中別名與id對應(方便以后查詢別名是否存在),id與account對象(JSON格式)對應,保存具體的信息
這幾步中的第3步中涉及到的方法比較多,需要再細致分析一下:
signers.Createblockchain/signers/signers.go#L67-L90
// Create creates and stores a Signer in the database func 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 }
這個方法可以分成4塊,主要就是檢查參數是否正確,還是比較清楚的:
xpubs不能為空
xpubs不能有重復的。檢查的時候就先排序,再看相鄰的兩個是否相等。我覺得這一塊代碼應該抽出來,比如findDuplicated這樣的方法,直接放在這里太過于細節了。
檢查quorum,它是意思是“所需的簽名數量”,它必須小于等于xpubs的個數,但不能為0。這個參數到底有什么用這個可能已經觸及到比較核心的東西,放在以后研究。
把各信息打包在一起,稱之為Singer
另外,在第2處還是一個需要注意的sortKeys。它實際上對應的是type sortKeys []chainkd.XPub,為什么要這么做,而不是直接把xpubs傳給sort.Sort呢?
這是因為,sort.Sort需要傳進來的對象擁有以下接口:
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) }
但是xpubs是沒有的。所以我們把它的類型重新定義成sortKeys后,就可以添加上這些方法了:
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()
然后是signers.Create("account", xpubs, quorum, m.getNextAccountIndex())中的m.getNextAccountIndex(),它的代碼如下:
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 }
從這個方法可以看出,它用于產生自增的數字。這個數字保存在數據庫中,其key為accountIndexKey(常量,值為[]byte("AccountIndex")),value的值第一次為1,之后每次調用都會把它加1,返回的同時把它也保存在數據庫里。這樣比原程序就算重啟該數字也不會丟失。
signers.IDGenerate()上代碼:
blockchain/signers/idgenerate.go#L21-L41
//IDGenerate generate signer unique id func 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 }
從代碼中可以看到,這個算法還是相當復雜的,從注釋上來看,它是要生成一個“不重復”的id。如果我們細看代碼中的算法,發現它沒并有和我們的密鑰或者帳戶有關系,所以我不太明白,如果僅僅是需要一個不重復的id,為什么不能直接使用如uuid這樣的算法。另外這個算法是否有名字呢?已經提了issue向開發人員詢問:https://github.com/Bytom/bytom/issues/926
現在可以回到我們的主線a.wallet.AccountMgr.Create上了。關于創建帳戶的流程,上面已經基本講了,但是還有一些地方我們還沒有分析:
上面多次提到使用了數據庫,那么使用的是什么數據庫?在哪里進行了初始化?
這個a.wallet.AccountMgr.Create方法中對應的AccountMgr對象是在哪里構造出來的?
數據庫與AccountMgr的初始化比原在內部使用了leveldb這個數據庫,從配置文件config.toml中就可以看出來:
$ cat config.toml fast_sync = true db_backend = "leveldb"
這是一個由Google開發的性能非常高的Key-Value型的NoSql數據庫,比特幣也用的是它。
比原在代碼中使用它保存各種數據,比如區塊、帳戶等。
我們看一下,它是在哪里進行了初始化。
可以看到,在創建比原節點對象的時候,有大量的與數據庫以及帳戶相關的初始化操作:
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) // ... } // ... }
那么我們在本文中用到的,就是這里的walletDB,在上面代碼中的數字1對應的地方。
另外,AccountMgr的初始化在也這個方法中進行了。可以看到,在第2處,生成的accounts對象,就是我們前面提到的a.wallet.AccountMgr中的AccountMgr。這可以從第3處看到,accounts以參數形式傳給了NewWallet生成了wallet對象,它對應的字段就是AccountMgr。
然后,當Node對象啟動時,它會啟動web api服務:
node/node.go#L169-L180
func (n *Node) OnStart() error { // ... n.initAndstartApiServer() // ... }
在initAndstartApiServer方法里,又會創建API對應的對象:
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) // ... }
可以看到,它把n.wallet對象傳給了NewAPI,所以/create-account對應的handlera.createAccount中才可以使用a.wallet.AccountMgr.Create,因為這里的a指的就是api。
這樣的話,與創建帳戶的流程及相關的對象的初始化我們就都清楚了。
Annotated(acc)下面就回到我們的API.createAccount中的第2塊代碼:
// 2. annotatedAccount := account.Annotated(acc) log.WithField("account ID", annotatedAccount.ID).Info("Created account")
我們來看一下account.Annotated(acc):
account/indexer.go#L27-L36
//Annotated init an annotated account object func Annotated(a *Account) *query.AnnotatedAccount { return &query.AnnotatedAccount{ ID: a.ID, Alias: a.Alias, Quorum: a.Quorum, XPubs: a.XPubs, KeyIndex: a.KeyIndex, } }
這里出現的query指的是比原項目中的一個包blockchain/query,相應的AnnotatedAccount的定義如下:
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"` }
可以看到,它的字段與之前我們在創建帳戶過程中出現的字段都差不多,不同的是后面多了一些與json相關的注解。在后在前面的account.Annotated方法中,也是簡單的把Account對象里的數字賦值給它。
為什么需要一個AnnotatedAccount呢?原因很簡單,因為我們需要把這些數據傳給前端。在API.createAccount的最后,第3步,會向前端返回NewSuccessResponse(annotatedAccount),由于這個值將會被jsonHandler轉換成JSON,所以它需要有一些跟json相關的注解才行。
同時,我們也可以根據AnnotatedAccount的字段來了解,我們最后將會向前端返回什么樣的數據。
到這里,我們已經差不多清楚了比原的/create-account是如何根據用戶提交的參數來創建帳戶的。
注:在閱讀代碼的過程中,對部分代碼進行了重構,主要是從一些大方法分解出來了一些更具有描述性的小方法,以及一些變量名稱的修改,增加可讀性。#924
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24181.html
摘要:下一步,將進入比原的節點也就是后端。它具體是怎么創建密鑰的,這在以后的文章中將詳細討論。當我們清楚了在本文中,前后端數據是如何交互的,就很容易推廣到更多的情景。 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前面一篇文章,我們粗略...
摘要:所以本文本來是想去研究一下,當別的節點把區塊數據發給我們之后,我們應該怎么處理,現在換成研究比原的是怎么做出來的。進去后會看到大量的與相關的配置。它的功能主要是為了在訪問與的函數之間增加了一層轉換。 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlo...
摘要:繼續看生成地址的方法由于這個方法里傳過來的是而不是對象,所以還需要再用查一遍,然后,再調用這個私有方法創建地址該方法可以分成部分在第塊中主要關注的是返回值。 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在比原的dashboard中...
摘要:如果傳的是,就會在內部使用默認的隨機數生成器生成隨機數并生成密鑰。使用的是,生成的是一個形如這樣的全球唯一的隨機數把密鑰以文件形式保存在硬盤上。 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇,我們探討了從瀏覽器的dashb...
摘要:前端是如何獲取交易數據并顯示出來的我們先在比原的前端代碼庫中尋找。這過程中的推導就不再詳說,需要的話可以看前面講解比原是如何顯示余額的那篇文章。的定義是其中的值是。 作者:freewind 比原項目倉庫: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇文章中,我們...
閱讀 2216·2021-09-07 09:58
閱讀 3391·2019-08-30 14:07
閱讀 1305·2019-08-29 12:32
閱讀 667·2019-08-29 11:06
閱讀 3692·2019-08-26 18:18
閱讀 3731·2019-08-26 17:35
閱讀 1381·2019-08-26 11:35
閱讀 611·2019-08-26 11:35