這篇文章主要介紹“比原是怎么通過接口create-account創(chuàng)建帳戶”,在日常操作中,相信很多人在比原是怎么通過接口create-account創(chuàng)建帳戶問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”比原是怎么通過接口create-account創(chuàng)建帳戶”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)公司長(zhǎng)期為千余家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為翁牛特企業(yè)提供專業(yè)的網(wǎng)站制作、網(wǎng)站建設(shè),翁牛特網(wǎng)站改版等技術(shù)服務(wù)。擁有十載豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
在API.buildHandler
中配置與創(chuàng)建帳戶相關(guān)的接口配置:
api/api.go#L164-L244
func (a *API) buildHandler() { // ... if a.wallet != nil { // ... m.Handle("/create-account", jsonHandler(a.createAccount)) // ...
可以看到,/create-account
對(duì)應(yīng)的handler是a.createAccount
,它是我們本文將研究的重點(diǎn)。外面套著的jsonHandler
是用來自動(dòng)JSON與GO數(shù)據(jù)類型之間的轉(zhuǎn)換的,之前討論過,這里不再說。
我們先看一下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
這三個(gè)參數(shù),我們?cè)谥暗奈恼轮幸部吹?,前端也的確傳了過來。這三個(gè)參數(shù),通過jsonHandler
的轉(zhuǎn)換,到這個(gè)方法的時(shí)候,已經(jīng)成了合適的GO類型,我們可以直接使用。
這個(gè)方法主要分成了三塊:
使用a.wallet.AccountMgr.Create
以及用戶發(fā)送的參數(shù)去創(chuàng)建相應(yīng)的帳戶
調(diào)用account.Annotated(acc)
,把a(bǔ)ccount對(duì)象轉(zhuǎn)換成可以被JSON化的對(duì)象
向前端發(fā)回成功信息。該信息會(huì)被jsonHandler自動(dòng)轉(zhuǎn)為JSON發(fā)到前端,用于顯示提示信息
第3步?jīng)]什么好說的,我們主要把目光集中在前兩步,下面將依次結(jié)合源代碼詳解。
創(chuàng)建帳戶使用的是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塊,這里依次講解:
把傳進(jìn)來的帳戶別名進(jìn)行標(biāo)準(zhǔn)化修正,比如去掉兩頭空白并小寫
從數(shù)據(jù)庫中尋找該別名是否已經(jīng)用過。因?yàn)閹艉蛣e名是一一對(duì)應(yīng)的,帳戶創(chuàng)建成功后,會(huì)在數(shù)據(jù)庫中把別名記錄下來。所以如果能從數(shù)據(jù)庫中查找,說明已經(jīng)被占用,會(huì)返回一個(gè)錯(cuò)誤信息。這樣前臺(tái)就可以提醒用戶更換。
創(chuàng)建一個(gè)Signer
,實(shí)際上就是對(duì)xpubs
、quorum
等參數(shù)的正確性進(jìn)行檢查,沒問題的話會(huì)把這些信息捆綁在一起,否則返回錯(cuò)誤。這個(gè)Signer
我感覺是檢查過沒問題簽個(gè)字的意思。
把第3步創(chuàng)建的signer和id,還有前面的標(biāo)準(zhǔn)化之后的別名拿起來,放在一起,就組成了一個(gè)帳戶
把帳戶對(duì)象變成JSON,方便后面往數(shù)據(jù)庫里存
把帳戶相關(guān)的數(shù)據(jù)保存在數(shù)據(jù)庫,其中別名與id對(duì)應(yīng)(方便以后查詢別名是否存在),id與account對(duì)象(JSON格式)對(duì)應(yīng),保存具體的信息
這幾步中的第3步中涉及到的方法比較多,需要再細(xì)致分析一下:
blockchain/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 }
這個(gè)方法可以分成4塊,主要就是檢查參數(shù)是否正確,還是比較清楚的:
xpubs不能為空
xpubs不能有重復(fù)的。檢查的時(shí)候就先排序,再看相鄰的兩個(gè)是否相等。我覺得這一塊代碼應(yīng)該抽出來,比如findDuplicated
這樣的方法,直接放在這里太過于細(xì)節(jié)了。
檢查quorum
,它是意思是“所需的簽名數(shù)量”,它必須小于等于xpubs的個(gè)數(shù),但不能為0。這個(gè)參數(shù)到底有什么用這個(gè)可能已經(jīng)觸及到比較核心的東西,放在以后研究。
把各信息打包在一起,稱之為Singer
另外,在第2處還是一個(gè)需要注意的sortKeys
。它實(shí)際上對(duì)應(yīng)的是type sortKeys []chainkd.XPub
,為什么要這么做,而不是直接把xpubs
傳給sort.Sort
呢?
這是因?yàn)椋?code>sort.Sort需要傳進(jìn)來的對(duì)象擁有以下接口:
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] }
然后是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 }
從這個(gè)方法可以看出,它用于產(chǎn)生自增的數(shù)字。這個(gè)數(shù)字保存在數(shù)據(jù)庫中,其key為accountIndexKey
(常量,值為[]byte("AccountIndex")
),value的值第一次為1
,之后每次調(diào)用都會(huì)把它加1,返回的同時(shí)把它也保存在數(shù)據(jù)庫里。這樣比原程序就算重啟該數(shù)字也不會(huì)丟失。
上代碼:
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 }
從代碼中可以看到,這個(gè)算法還是相當(dāng)復(fù)雜的,從注釋上來看,它是要生成一個(gè)“不重復(fù)”的id。如果我們細(xì)看代碼中的算法,發(fā)現(xiàn)它沒并有和我們的密鑰或者帳戶有關(guān)系,所以我不太明白,如果僅僅是需要一個(gè)不重復(fù)的id,為什么不能直接使用如uuid這樣的算法。另外這個(gè)算法是否有名字呢?已經(jīng)提了issue向開發(fā)人員詢問:https://github.com/Bytom/bytom/issues/926
現(xiàn)在可以回到我們的主線a.wallet.AccountMgr.Create
上了。關(guān)于創(chuàng)建帳戶的流程,上面已經(jīng)基本講了,但是還有一些地方我們還沒有分析:
上面多次提到使用了數(shù)據(jù)庫,那么使用的是什么數(shù)據(jù)庫?在哪里進(jìn)行了初始化?
這個(gè)a.wallet.AccountMgr.Create
方法中對(duì)應(yīng)的AccountMgr
對(duì)象是在哪里構(gòu)造出來的?
AccountMgr
的初始化比原在內(nèi)部使用了leveldb這個(gè)數(shù)據(jù)庫,從配置文件config.toml
中就可以看出來:
$ cat config.toml fast_sync = true db_backend = "leveldb"
這是一個(gè)由Google開發(fā)的性能非常高的Key-Value型的NoSql數(shù)據(jù)庫,比特幣也用的是它。
比原在代碼中使用它保存各種數(shù)據(jù),比如區(qū)塊、帳戶等。
我們看一下,它是在哪里進(jìn)行了初始化。
可以看到,在創(chuàng)建比原節(jié)點(diǎn)對(duì)象的時(shí)候,有大量的與數(shù)據(jù)庫以及帳戶相關(guān)的初始化操作:
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) // ... } // ... }
那么我們?cè)诒疚闹杏玫降?,就是這里的walletDB
,在上面代碼中的數(shù)字1對(duì)應(yīng)的地方。
另外,AccountMgr
的初始化在也這個(gè)方法中進(jìn)行了??梢钥吹?,在第2處,生成的accounts
對(duì)象,就是我們前面提到的a.wallet.AccountMgr
中的AccountMgr
。這可以從第3處看到,accounts
以參數(shù)形式傳給了NewWallet
生成了wallet
對(duì)象,它對(duì)應(yīng)的字段就是AccountMgr
。
然后,當(dāng)Node對(duì)象啟動(dòng)時(shí),它會(huì)啟動(dòng)web api服務(wù):
node/node.go#L169-L180
func (n *Node) OnStart() error { // ... n.initAndstartApiServer() // ... }
在initAndstartApiServer
方法里,又會(huì)創(chuàng)建API
對(duì)應(yīng)的對(duì)象:
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
對(duì)象傳給了NewAPI
,所以/create-account
對(duì)應(yīng)的handlera.createAccount
中才可以使用a.wallet.AccountMgr.Create
,因?yàn)檫@里的a
指的就是api
。
這樣的話,與創(chuàng)建帳戶的流程及相關(guān)的對(duì)象的初始化我們就都清楚了。
下面就回到我們的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, } }
這里出現(xiàn)的query
指的是比原項(xiàng)目中的一個(gè)包blockchain/query
,相應(yīng)的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"` }
可以看到,它的字段與之前我們?cè)趧?chuàng)建帳戶過程中出現(xiàn)的字段都差不多,不同的是后面多了一些與json相關(guān)的注解。在后在前面的account.Annotated
方法中,也是簡(jiǎn)單的把Account
對(duì)象里的數(shù)字賦值給它。
為什么需要一個(gè)AnnotatedAccount
呢?原因很簡(jiǎn)單,因?yàn)槲覀冃枰堰@些數(shù)據(jù)傳給前端。在API.createAccount
的最后,第3步,會(huì)向前端返回NewSuccessResponse(annotatedAccount)
,由于這個(gè)值將會(huì)被jsonHandler
轉(zhuǎn)換成JSON,所以它需要有一些跟json相關(guān)的注解才行。
同時(shí),我們也可以根據(jù)AnnotatedAccount
的字段來了解,我們最后將會(huì)向前端返回什么樣的數(shù)據(jù)。
到這里,我們已經(jīng)差不多清楚了比原的/create-account
是如何根據(jù)用戶提交的參數(shù)來創(chuàng)建帳戶的。
到此,關(guān)于“比原是怎么通過接口create-account創(chuàng)建帳戶”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!