create account(比原是怎么通过接口create-account创建帐户)

发布时间:2025-12-11 00:05:50 浏览次数:28

API.buildHandler中配置与创建帐户相关的接口配置:

api/api.go#L164-L244

func(a*API)buildHandler(){//...ifa.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-accountfunc(a*API)createAccount(ctxcontext.Context,insstruct{RootXPubs[]chainkd.XPub`json:"root_xpubs"`Quorumint`json:"quorum"`Aliasstring`json:"alias"`})Response{//1.acc,err:=a.wallet.AccountMgr.Create(ctx,ins.RootXPubs,ins.Quorum,ins.Alias)iferr!=nil{returnNewErrorResponse(err)}//2.annotatedAccount:=account.Annotated(acc)log.WithField("accountID",annotatedAccount.ID).Info("Createdaccount")//3.returnNewSuccessResponse(annotatedAccount)}

可以看到,它需要前端传过来root_xpubsquorumalias这三个参数,我们在之前的文章中也看到,前端也的确传了过来。这三个参数,通过jsonHandler的转换,到这个方法的时候,已经成了合适的GO类型,我们可以直接使用。

这个方法主要分成了三块:

  1. 使用a.wallet.AccountMgr.Create以及用户发送的参数去创建相应的帐户

  2. 调用account.Annotated(acc),把account对象转换成可以被JSON化的对象

  3. 向前端发回成功信息。该信息会被jsonHandler自动转为JSON发到前端,用于显示提示信息

第3步没什么好说的,我们主要把目光集中在前两步,下面将依次结合源代码详解。

创建相应的帐户

创建帐户使用的是a.wallet.AccountMgr.Create方法,先看代码:

account/accounts.go#L145-L174

//CreatecreatesanewAccount.func(m*Manager)Create(ctxcontext.Context,xpubs[]chainkd.XPub,quorumint,aliasstring)(*Account,error){m.accountMu.Lock()deferm.accountMu.Unlock()//1.normalizedAlias:=strings.ToLower(strings.TrimSpace(alias))//2.ifexisted:=m.db.Get(aliasKey(normalizedAlias));existed!=nil{returnnil,ErrDuplicateAlias}//3.signer,err:=signers.Create("account",xpubs,quorum,m.getNextAccountIndex())id:=signers.IDGenerate()iferr!=nil{returnnil,errors.Wrap(err)}//4.account:=&Account{Signer:signer,ID:id,Alias:normalizedAlias}//5.rawAccount,err:=json.Marshal(account)iferr!=nil{returnnil,ErrMarshalAccount}//6.storeBatch:=m.db.NewBatch()accountID:=Key(id)storeBatch.Set(accountID,rawAccount)storeBatch.Set(aliasKey(normalizedAlias),[]byte(id))storeBatch.Write()returnaccount,nil}

我们把该方法分成了6块,这里依次讲解:

  1. 把传进来的帐户别名进行标准化修正,比如去掉两头空白并小写

  2. 从数据库中寻找该别名是否已经用过。因为帐户和别名是一一对应的,帐户创建成功后,会在数据库中把别名记录下来。所以如果能从数据库中查找,说明已经被占用,会返回一个错误信息。这样前台就可以提醒用户更换。

  3. 创建一个Signer,实际上就是对xpubsquorum等参数的正确性进行检查,没问题的话会把这些信息捆绑在一起,否则返回错误。这个Signer我感觉是检查过没问题签个字的意思。

  4. 把第3步创建的signer和id,还有前面的标准化之后的别名拿起来,放在一起,就组成了一个帐户

  5. 把帐户对象变成JSON,方便后面往数据库里存

  6. 把帐户相关的数据保存在数据库,其中别名与id对应(方便以后查询别名是否存在),id与account对象(JSON格式)对应,保存具体的信息

这几步中的第3步中涉及到的方法比较多,需要再细致分析一下:

signers.Create

blockchain/signers/signers.go#L67-L90

//CreatecreatesandstoresaSignerinthedatabasefuncCreate(signerTypestring,xpubs[]chainkd.XPub,quorumint,keyIndexuint64)(*Signer,error){//1.iflen(xpubs)==0{returnnil,errors.Wrap(ErrNoXPubs)}//2.sort.Sort(sortKeys(xpubs))//thistransformstheinputslicefori:=1;i<len(xpubs);i++{ifbytes.Equal(xpubs[i][:],xpubs[i-1][:]){returnnil,errors.WithDetailf(ErrDupeXPub,"duplicatedkey=%x",xpubs[i])}}//3.ifquorum==0||quorum>len(xpubs){returnnil,errors.Wrap(ErrBadQuorum)}//4.return&Signer{Type:signerType,XPubs:xpubs,Quorum:quorum,KeyIndex:keyIndex,},nil}

这个方法可以分成4块,主要就是检查参数是否正确,还是比较清楚的:

  1. xpubs不能为空

  2. xpubs不能有重复的。检查的时候就先排序,再看相邻的两个是否相等。我觉得这一块代码应该抽出来,比如findDuplicated这样的方法,直接放在这里太过于细节了。

  3. 检查quorum,它是意思是“所需的签名数量”,它必须小于等于xpubs的个数,但不能为0。这个参数到底有什么用这个可能已经触及到比较核心的东西,放在以后研究。

  4. 把各信息打包在一起,称之为Singer

另外,在第2处还是一个需要注意的sortKeys。它实际上对应的是type sortKeys []chainkd.XPub,为什么要这么做,而不是直接把xpubs传给sort.Sort呢?

这是因为,sort.Sort需要传进来的对象拥有以下接口:

typeInterfaceinterface{//Lenisthenumberofelementsinthecollection.Len()int//Lessreportswhethertheelementwith//indexishouldsortbeforetheelementwithindexj.Less(i,jint)bool//Swapswapstheelementswithindexesiandj.Swap(i,jint)}

但是xpubs是没有的。所以我们把它的类型重新定义成sortKeys后,就可以添加上这些方法了:

blockchain/signers/signers.go#L94-L96

func(ssortKeys)Len()int{returnlen(s)}func(ssortKeys)Less(i,jint)bool{returnbytes.Compare(s[i][:],s[j][:])<0}func(ssortKeys)Swap(i,jint){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()deferm.accIndexMu.Unlock()varnextIndexuint64=1ifrawIndexBytes:=m.db.Get(accountIndexKey);rawIndexBytes!=nil{nextIndex=common.BytesToUnit64(rawIndexBytes)+1}m.db.Set(accountIndexKey,common.Unit64ToBytes(nextIndex))returnnextIndex}

从这个方法可以看出,它用于产生自增的数字。这个数字保存在数据库中,其key为accountIndexKey(常量,值为[]byte("AccountIndex")),value的值第一次为1,之后每次调用都会把它加1,返回的同时把它也保存在数据库里。这样比原程序就算重启该数字也不会丢失。

signers.IDGenerate()

上代码:

blockchain/signers/idgenerate.go#L21-L41

//IDGenerategeneratesigneruniqueidfuncIDGenerate()string{varourEpochMSuint64=1496635208000varnuint64nowMS:=uint64(time.Now().UnixNano()/1e6)seqIndex:=uint64(nextSeqID())seqID:=uint64(seqIndex%1024)shardID:=uint64(5)n=(nowMS-ourEpochMS)<<23n=n|(shardID<<10)n=n|seqIDbin:=make([]byte,8)binary.BigEndian.PutUint64(bin,n)encodeString:=base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(bin)returnencodeString}

从代码中可以看到,这个算法还是相当复杂的,从注释上来看,它是要生成一个“不重复”的id。如果我们细看代码中的算法,发现它没并有和我们的密钥或者帐户有关系,所以我不太明白,如果仅仅是需要一个不重复的id,为什么不能直接使用如uuid这样的算法。另外这个算法是否有名字呢?已经提了issue向开发人员询问:https://github.com/Bytom/bytom/issues/926

现在可以回到我们的主线a.wallet.AccountMgr.Create上了。关于创建帐户的流程,上面已经基本讲了,但是还有一些地方我们还没有分析:

  1. 上面多次提到使用了数据库,那么使用的是什么数据库?在哪里进行了初始化?

  2. 这个a.wallet.AccountMgr.Create方法中对应的AccountMgr对象是在哪里构造出来的?

数据库与AccountMgr的初始化

比原在内部使用了leveldb这个数据库,从配置文件config.toml中就可以看出来:

$catconfig.tomlfast_sync=truedb_backend="leveldb"

这是一个由Google开发的性能非常高的Key-Value型的NoSql数据库,比特币也用的是它。

比原在代码中使用它保存各种数据,比如区块、帐户等。

我们看一下,它是在哪里进行了初始化。

可以看到,在创建比原节点对象的时候,有大量的与数据库以及帐户相关的初始化操作:

node/node.go#L59-L142

funcNewNode(config*cfg.Config)*Node{//...//GetstorecoreDB:=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("accountID",annotatedAccount.ID).Info("Createdaccount")

我们来看一下account.Annotated(acc):

account/indexer.go#L27-L36

//AnnotatedinitanannotatedaccountobjectfuncAnnotated(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

typeAnnotatedAccountstruct{IDstring`json:"id"`Aliasstring`json:"alias,omitempty"`XPubs[]chainkd.XPub`json:"xpubs"`Quorumint`json:"quorum"`KeyIndexuint64`json:"key_index"`}

可以看到,它的字段与之前我们在创建帐户过程中出现的字段都差不多,不同的是后面多了一些与json相关的注解。在后在前面的account.Annotated方法中,也是简单的把Account对象里的数字赋值给它。

为什么需要一个AnnotatedAccount呢?原因很简单,因为我们需要把这些数据传给前端。在API.createAccount的最后,第3步,会向前端返回NewSuccessResponse(annotatedAccount),由于这个值将会被jsonHandler转换成JSON,所以它需要有一些跟json相关的注解才行。

同时,我们也可以根据AnnotatedAccount的字段来了解,我们最后将会向前端返回什么样的数据。

到这里,我们已经差不多清楚了比原的/create-account是如何根据用户提交的参数来创建帐户的。

到此,关于“比原是怎么通过接口create-account创建帐户”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注本站网站,小编会继续努力为大家带来更多实用的文章!

create account
需要做网站?需要网络推广?欢迎咨询客户经理 13272073477