亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

剝開(kāi)比原看代碼15:比原是如何轉(zhuǎn)帳的

Tony / 1883人閱讀

摘要:圖中,轉(zhuǎn)帳表單是如何把轉(zhuǎn)帳數(shù)據(jù)提交到后臺(tái)的由于是前端,所以我們要去從前端的代碼庫(kù)中尋找。繼續(xù)看這段代碼內(nèi)容還是比較多的,但總體基本上還是對(duì)參數(shù)進(jìn)行驗(yàn)證補(bǔ)全和轉(zhuǎn)換,然后交給后面的方法處理。

作者:freewind

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

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

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

在前面幾篇中,我們做了足夠了準(zhǔn)備,現(xiàn)在終于可以試一試轉(zhuǎn)帳功能了!

這里的轉(zhuǎn)帳最好使用solonet,再按前一篇文章的辦法修改代碼后產(chǎn)生單機(jī)測(cè)試幣,然后再試。在此之前,如果需要的話,請(qǐng)先備份好你之前的帳戶,然后刪除(或重命名)你的數(shù)據(jù)目錄,再使用bytomd init --chain_id=solonet重新初始化。

下面是我通過(guò)dashboard進(jìn)行的轉(zhuǎn)帳操作,在操作之前,我先建立了兩個(gè)帳戶,然后把錢(qián)從一個(gè)帳戶轉(zhuǎn)到另一個(gè)帳戶的地址中:

新建一個(gè)交易,填上把哪個(gè)帳戶的哪種資產(chǎn)轉(zhuǎn)到某個(gè)地址上??梢钥吹竭€要消耗一定的gas:

(上圖為圖1)

轉(zhuǎn)帳成功后,如下:

(上圖為圖2)

我們看一下這個(gè)交易的詳細(xì)信息,由于太長(zhǎng),截成了兩個(gè)圖:


(上面兩圖合稱為圖3)

我們今天(以及往后的幾天)就是把這一塊流程搞清楚。

由于上面展示的操作還是有點(diǎn)多的,所以我們還是按之前的套路,先把它分解成多個(gè)小問(wèn)題,一一解決:

圖1中,轉(zhuǎn)帳界面是如何把轉(zhuǎn)帳數(shù)據(jù)提交到后臺(tái)的?

圖1中,后臺(tái)是如何接收到轉(zhuǎn)帳數(shù)據(jù)并執(zhí)行轉(zhuǎn)帳操作的?

圖2中,前臺(tái)是如何拿到后臺(tái)的數(shù)據(jù)并展示出來(lái)的?

圖3中,前臺(tái)是如何拿到后臺(tái)的數(shù)據(jù)并展示出來(lái)的?

今天的文章,我們主要是研究前兩個(gè)問(wèn)題,即跟圖1相關(guān)的邏輯。

圖1中,轉(zhuǎn)帳表單是如何把轉(zhuǎn)帳數(shù)據(jù)提交到后臺(tái)的?

由于是前端,所以我們要去從前端的代碼庫(kù)中尋找。通過(guò)搜索“簡(jiǎn)單交易”這個(gè)詞,我們很快定位到下面這塊代碼:

src/features/transactions/components/New/New.jsx#L275-L480

    return (
      
      // ...
      
    )

由于上面的代碼實(shí)在太長(zhǎng)太細(xì)節(jié),全是一些jsx用于生成表單的代碼,我們就跳過(guò)算了,有興趣的同學(xué)可以自行看細(xì)節(jié)。我們需要關(guān)注的是,當(dāng)我們單擊了“提交交易”的按鈕以后,this.submitWithValidation會(huì)被調(diào)用,而它對(duì)應(yīng)的代碼是:

src/features/transactions/components/New/New.jsx#L159-L177

  submitWithValidation(data) {
    return new Promise((resolve, reject) => {
      this.props.submitForm(Object.assign({}, data, {state: this.state}))
        .catch((err) => {
          // ...
          return reject(response)
        })
    })
  }

通常我們應(yīng)該會(huì)在這個(gè)函數(shù)里找到一些線索,發(fā)現(xiàn)數(shù)據(jù)會(huì)提交到后臺(tái)哪個(gè)接口。但是這次卻好像沒(méi)有有用的信息,只有一個(gè)來(lái)自于props的看起來(lái)非常通用的submitForm??磥?lái)需要多找找線索。

好在很快在同一個(gè)文件的最后面,看到了用于把React組件與Redux連接起來(lái)的代碼,非常有用:

src/features/transactions/components/New/New.jsx#L515-L572

export default BaseNew.connect(
  (state) => {
    // ...
    return {
      // ...
    }
  },
  (dispatch) => ({
    // ...
    ...BaseNew.mapDispatchToProps("transaction")(dispatch)
  }),
  // ...
  )(Form)
)

我把不太關(guān)注的內(nèi)容都省略了,需要關(guān)注的是BaseNew.mapDispatchToProps("transaction")(dispatch)這一行。

為什么要關(guān)注mapDispatchToProps這個(gè)方法呢?這是因?yàn)楫?dāng)我們點(diǎn)擊了表單中的提交按鈕后,不論中間怎么操作,最后一定要調(diào)用dispatch來(lái)處理某個(gè)action。而在前面看到,點(diǎn)擊“提交交易”后,執(zhí)行的是this.props.submitForm,通過(guò)this.props.可以看出,這個(gè)submitForm是從外部傳進(jìn)來(lái)的,而mapDispatchToPros就是把dispatch操作映射在props上,讓props中有我們需要的函數(shù)。所以如果我們不能從其它地方看到明顯的線索的時(shí)候,應(yīng)該考慮去看看這個(gè)。

BaseNew.mapDispatchToProps是來(lái)自于BaseNew,我們又找到了相應(yīng)的代碼:

src/features/shared/components/BaseNew.jsx#L9-L16

import actions from "actions"

// ...

export const mapDispatchToProps = (type) => (dispatch) => ({
  submitForm: (data) => {
    return dispatch(actions[type].submitForm(data)).then((resp) => {
      dispatch(actions.tutorial.submitTutorialForm(data, type))
      return resp
    })
  }
})

果然在里面找到了submitForm的定義。在里面第一個(gè)dispatch處,傳入了參數(shù)actions[type].submitForm(data),這里的type應(yīng)該是transaction,而actions應(yīng)該是之前某處定義的各種action的集合。

根據(jù)import actions from "actions",我們發(fā)現(xiàn)from后面的"actions"不是相對(duì)路徑,那么它對(duì)應(yīng)的就是js的源代碼根目錄src下的某個(gè)文件,比如actions.js

找到后打開(kāi)一看,里面果然有transaction

src/actions.js#L15-L29

// ...
import { actions as transaction } from "features/transactions"
// ...

const actions = {
  // ...
  transaction,
  // ...
}

我們繼續(xù)進(jìn)入features/transactions/探索,很快找到:

src/features/transactions/actions.js#L100-L200

form.submitForm = (formParams) => function (dispatch) {
  // ...
  // 2.
  const buildPromise = connection.request("/build-transaction", {actions: processed.actions})

  const signAndSubmitTransaction = (transaction, password) => {
    // 4. 
    return connection.request("/sign-transaction", {
      password,
      transaction
    }).then(resp => {
      if (resp.status === "fail") {
        throw new Error(resp.msg)
      }

      const rawTransaction = resp.data.transaction.rawTransaction
      // 5. 
      return connection.request("/submit-transaction", {rawTransaction})
    }).then(dealSignSubmitResp)
  }
  // ...
  if (formParams.submitAction == "submit") {
    // 1. 
    return buildPromise
      .then((resp) => {
        if (resp.status === "fail") {
          throw new Error(resp.msg)
        }
        // 3.
        return signAndSubmitTransaction(resp.data, formParams.password)
      })
  }
  // ...
}

上面的代碼經(jīng)過(guò)了我的簡(jiǎn)化,其實(shí)它本來(lái)是有很多分支的(因?yàn)楸韱沃谐恕昂?jiǎn)單交易”還有“高級(jí)交易”等情況)。即使如此,也可以看出來(lái)這個(gè)過(guò)程還是比較復(fù)雜的,經(jīng)過(guò)了好幾次的后臺(tái)接口訪問(wèn):

第1處代碼就是對(duì)應(yīng)我們“簡(jiǎn)單交易”的情況,它會(huì)調(diào)用buildPromise,這里面應(yīng)該包括了對(duì)后臺(tái)的訪問(wèn)

第2處就是buildPromise的定義,可以看到會(huì)訪問(wèn)/build-transaction

第3處是如果前一個(gè)訪問(wèn)是正常的,那么會(huì)繼續(xù)調(diào)用signAndSubmitTransaction

第4處就進(jìn)入到signAndSubmitTransaction內(nèi)部了,可以看到,它會(huì)訪問(wèn)一個(gè)新的接口/sign-transaction

第5處是在前一個(gè)正常的情況下,進(jìn)行最后的提交,訪問(wèn)接口/submit-transaction。后面的dealSignSubmitResp是一些對(duì)前端的操作,所以就不看它了

可以看到,這一個(gè)表單的提交,在內(nèi)部對(duì)應(yīng)著好幾個(gè)接口的訪問(wèn),每個(gè)提交的數(shù)據(jù)也不一樣,代碼跟蹤起來(lái)不太方便。但是好在只要我們知道了這一條主線,那么尋找其它的信息就會(huì)簡(jiǎn)單一些。不過(guò)我們也沒(méi)有必要執(zhí)著于全部從源代碼中找到答案,因?yàn)槲覀兊哪康牟⒉皇菍W(xué)習(xí)React/Redux,而是理解比原的邏輯,所以我們可以借助別的工具(比如Chrome的Developer Tools),來(lái)捕獲請(qǐng)求的數(shù)據(jù),從而推理出邏輯。

我已經(jīng)從Chrome的開(kāi)發(fā)工具中取得了前端向下面幾個(gè)接口發(fā)送的數(shù)據(jù):

/build-transaction

/sign-transaction

/submit-transaction

但是由于我們?cè)谶@個(gè)小問(wèn)題中,關(guān)注的重點(diǎn)是前端如何把數(shù)據(jù)提交給后臺(tái)的,所以對(duì)于這里提交的數(shù)據(jù)的意義暫時(shí)不討論,留待下個(gè)小問(wèn)題中一一解答。

圖1中,后臺(tái)是如何接收到轉(zhuǎn)帳數(shù)據(jù)并執(zhí)行轉(zhuǎn)帳操作的?

由于在圖1中前端一共訪問(wèn)了3個(gè)不同的后端接口,所以在這里我們就需要依次分開(kāi)討論。

/build-transaction

下面是我通過(guò)Chrome的開(kāi)發(fā)工具捕獲的數(shù)據(jù),看起來(lái)還比較多:

/build-transaction

提交的數(shù)據(jù):
{
    "actions": [{
        "amount": 437400,
        "type": "spend_account",
        "receiver": null,
        "account_alias": "freewind",
        "account_id": "",
        "asset_alias": "BTM",
        "reference_data": null
    }, {
        "amount": 23400000000,
        "type": "spend_account",
        "receiver": null,
        "account_alias": "freewind",
        "account_id": "",
        "asset_alias": "BTM",
        "asset_id": "",
        "reference_data": null
    }, {
        "address": "sm1qe4z3ava34wv5njdgekcgdlrckc95gnljazezva",
        "amount": 23400000000,
        "type": "control_address",
        "receiver": null,
        "asset_alias": "BTM",
        "asset_id": "",
        "reference_data": null
    }]
}

可以看到前端向/build-transaction發(fā)送的數(shù)據(jù)包含了三個(gè)元素,其中前兩個(gè)是來(lái)源帳戶的信息,第三個(gè)是目的帳戶地址。這三個(gè)元素都包含一個(gè)叫amount的key,它的值對(duì)應(yīng)的是相應(yīng)資產(chǎn)的數(shù)量,如果是BTM的話,這個(gè)數(shù)字就需要從右向左數(shù)8位,再加上一個(gè)小數(shù)點(diǎn)。也就是說(shuō),第一個(gè)amount對(duì)應(yīng)的是0.00437400個(gè)BTM,第二個(gè)是234.00000000,第三個(gè)是234.00000000

第一個(gè)元素對(duì)應(yīng)的費(fèi)用是gas,也就是圖1中顯示出來(lái)的估算的手續(xù)費(fèi)。第二個(gè)是要從相應(yīng)帳戶中轉(zhuǎn)出234個(gè)BTM,第三個(gè)是要轉(zhuǎn)入234個(gè)BTM。

另外,前兩個(gè)的typespend_account,表明了是帳戶,但是spend是什么意思目前還不清楚(TODO);第三個(gè)是control_address,表示是一個(gè)地址。

通過(guò)這些數(shù)據(jù),比原的后臺(tái)就知道該怎么做了。

得到的回應(yīng):
{
    "status": "success",
    "data": {
        "raw_transaction": "070100010161015f643bef0936443042ccb1e94213ed52af72488088702d88e7fc3580359a19a522ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d599010001160014108c5ba0934951a12755523f8a1fe42a6c24342f010002013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8ebaabf4201160014b111c8114dc7ee02050598022b46855fd482d27300013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d4fe955701160014cd451eb3b1ab9949c9a8cdb086fc78b60b444ff200",
        "signing_instructions": [{
            "position": 0,
            "witness_components": [{
                "type": "raw_tx_signature",
                "quorum": 1,
                "keys": [{
                    "xpub": "f98b3a39b4eef67707cac85240ef07235c990301b2e0658001545bdb7fde3a21363a23682a1dfbb727dec7565624812c314ca9f31a7f7374101e0247d05cb248",
                    "derivation_path": ["010100000000000000", "0100000000000000"]
                }],
                "signatures": null
            }, {
                "type": "data",
                "value": "b826dcccff76d19d097ca207e053e67d67e3da3a90896ae9fa2d984c6f36d16c"
            }]
        }],
        "allow_additional_actions": false
    }
}

這個(gè)回應(yīng)信息是什么意思呢?我們現(xiàn)在開(kāi)始研究。

我們?cè)诒仍暮蠖舜a庫(kù)中,通過(guò)查找/build-transaction,很快找到了它的定義處:

api/api.go#L164-L244

func (a *API) buildHandler() {
    // ...
    if a.wallet != nil {
        // ...
        m.Handle("/build-transaction", jsonHandler(a.build))
        // ...
}

可以看到它對(duì)就的方法是a.build,其代碼為:

api/transact.go#L167-L176

func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
    subctx := reqid.NewSubContext(ctx, reqid.New())

    tmpl, err := a.buildSingle(subctx, buildReqs)
    if err != nil {
        return NewErrorResponse(err)
    }

    return NewSuccessResponse(tmpl)
}

其中的buildReqs就對(duì)應(yīng)著前端提交過(guò)來(lái)的參數(shù),只不過(guò)被jsonHandler自動(dòng)轉(zhuǎn)成了Go代碼。其中BuildRequest是這樣定義的:

api/request.go#L21-L26

type BuildRequest struct {
    Tx        *types.TxData            `json:"base_transaction"`
    Actions   []map[string]interface{} `json:"actions"`
    TTL       json.Duration            `json:"ttl"`
    TimeRange uint64                   `json:"time_range"`
}

可以看出來(lái)有一些字段比如base_transaction, ttl, time_range等在本例中并沒(méi)有提交上來(lái),它們應(yīng)該是可選的。

繼續(xù)看a.buildSingle

api/transact.go#L101-L164

func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
    // 1.
    err := a.filterAliases(ctx, req)
    // ...

    // 2.
    if onlyHaveSpendActions(req) {
        return nil, errors.New("transaction only contain spend actions, didn"t have output actions")
    }

    // 3.
    reqActions, err := mergeActions(req)
    // ...

    // 4. 
    actions := make([]txbuilder.Action, 0, len(reqActions))
    for i, act := range reqActions {
        typ, ok := act["type"].(string)
        // ...
        decoder, ok := a.actionDecoder(typ)
        // ...
        b, err := json.Marshal(act)
        // ...
        action, err := decoder(b)
        // ...
        actions = append(actions, action)
    }

    // 5. 
    ttl := req.TTL.Duration
    if ttl == 0 {
        ttl = defaultTxTTL
    }
    maxTime := time.Now().Add(ttl)

    // 6. 
    tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
    // ...
    return tpl, nil
}

這段代碼內(nèi)容還是比較多的,但總體基本上還是對(duì)參數(shù)進(jìn)行驗(yàn)證、補(bǔ)全和轉(zhuǎn)換,然后交給后面的方法處理。我分成了多塊,依次講解大意:

第1處的filterAliases主要是對(duì)傳進(jìn)來(lái)的參數(shù)進(jìn)行驗(yàn)證和補(bǔ)全。比如像account和asset,一般都有id和alias這兩個(gè)屬性,如果只提交了alias而沒(méi)有提交id的話,則filterAliases就會(huì)從數(shù)據(jù)庫(kù)或者緩存中查找到相應(yīng)的id補(bǔ)全。如果過(guò)程中出了錯(cuò),比如alias不存在,則報(bào)錯(cuò)返回

第2處的onlyHaveSpendActions是檢查如果這個(gè)交易中,只存在資金來(lái)源方,而沒(méi)有資金目標(biāo)方,顯示是不對(duì)的,報(bào)錯(cuò)返回

第3處的mergeActions是把請(qǐng)求數(shù)據(jù)中的spend_account進(jìn)行分組累加,把相同account的相同asset的數(shù)量累加到一起

第4處的代碼看著挺多,實(shí)際上只是把剛才處理過(guò)的請(qǐng)求數(shù)據(jù)由JSON轉(zhuǎn)換成相應(yīng)的Go對(duì)象。在actionDecoder(typ)里通過(guò)手動(dòng)比較type的值返回相應(yīng)的Decoder

第5處的ttl是指Time To Live,指的這個(gè)請(qǐng)求的存活時(shí)間,如果沒(méi)指明的話(本例就沒(méi)有),則設(shè)為默認(rèn)值5分鐘

第6處就是轉(zhuǎn)交給txbuilder.Build繼續(xù)處理

在這幾處里提到的方法和函數(shù)的代碼我就不貼出來(lái)了,因?yàn)榛旧隙际且恍┽槍?duì)map的低級(jí)操作,大片大片的看著很累,實(shí)際上沒(méi)做多少事。這種類型的代碼反復(fù)出現(xiàn),在別的語(yǔ)言中(甚至Java)都可以抽出來(lái)很多工具方法,但是在Go里由于語(yǔ)言特性(缺少泛型,麻煩的錯(cuò)誤處理),似乎不是很容易。看一眼廣大Go程序員的期盼:

https://github.com/golang/go/...

看看在Go2中會(huì)不會(huì)實(shí)現(xiàn)。

讓我們繼續(xù)看txbuilder.Build

blockchain/txbuilder/txbuilder.go#L40-L79

func Build(ctx context.Context, tx *types.TxData, actions []Action, maxTime time.Time, timeRange uint64) (*Template, error) {
    builder := TemplateBuilder{
        base:      tx,
        maxTime:   maxTime,
        timeRange: timeRange,
    }

    // Build all of the actions, updating the builder.
    var errs []error
    for i, action := range actions {
        err := action.Build(ctx, &builder)
        // ...
    }

    // If there were any errors, rollback and return a composite error.
    if len(errs) > 0 {
        builder.rollback()
        return nil, errors.WithData(ErrAction, "actions", errs)
    }

    // Build the transaction template.
    tpl, tx, err := builder.Build()
    // ...

    return tpl, nil
}

這塊代碼經(jīng)過(guò)簡(jiǎn)化后,還是比較清楚的,基本上就是想盡辦法把TemplateBuilder填滿。TemplateBuilder是這樣的:

blockchain/txbuilder/builder.go#L17-L28

type TemplateBuilder struct {
    base                *types.TxData
    inputs              []*types.TxInput
    outputs             []*types.TxOutput
    signingInstructions []*SigningInstruction
    minTime             time.Time
    maxTime             time.Time
    timeRange           uint64
    referenceData       []byte
    rollbacks           []func()
    callbacks           []func() error
}

可以看到有很多字段,但是只要清楚了它們的用途,我們也就清楚了交易transaction是怎么回事。但是我發(fā)現(xiàn)一旦深入下去,很快又觸及到比原的核心部分,所以就停在這里不去深究了。前面Build函數(shù)里面提到的其它的方法,比如action.Build等,我們也不進(jìn)去了,因?yàn)樗鼈兓旧隙际窃谙氡M辦法組裝出最后需要的對(duì)象。

到這里,我們可以認(rèn)為buildSingle就走完了,然后回到func (a *API) build(...),把生成的對(duì)象返回給前端。

那么,這個(gè)接口/build-transaction到底是做什么的呢?通過(guò)上面我分析,我們可以知道它有兩個(gè)作用:

一是檢查各參數(shù)是否正確。因?yàn)橛脩籼顚?xiě)的數(shù)據(jù)很多,而且里面的數(shù)據(jù)看起來(lái)專業(yè)性很強(qiáng),容易出錯(cuò),早點(diǎn)發(fā)現(xiàn)早點(diǎn)提醒

二是補(bǔ)全一些信息,如id,公鑰等等,方便前端進(jìn)行后面的操作

在這個(gè)接口的分析過(guò)程中,我們還是忽略了很多內(nèi)容,比如返回給客戶端的那一大段JSON代碼中的數(shù)據(jù)。我想這些東西還是留著我們研究到比原的核心的時(shí)候,再一起學(xué)習(xí)吧。

/sign-transaction

在前一步/build-transaction成功完成以后,會(huì)進(jìn)行下一步操作/sign-transaction

下面是通過(guò)Chrome的開(kāi)發(fā)工具捕獲的內(nèi)容:

提交的數(shù)據(jù):
{
    "password": "my-password",
    "transaction": {
        "raw_transaction": "070100010161015f643bef0936443042ccb1e94213ed52af72488088702d88e7fc3580359a19a522ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d599010001160014108c5ba0934951a12755523f8a1fe42a6c24342f010002013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8ebaabf4201160014b111c8114dc7ee02050598022b46855fd482d27300013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d4fe955701160014cd451eb3b1ab9949c9a8cdb086fc78b60b444ff200",
        "signing_instructions": [{
            "position": 0,
            "witness_components": [{
                "type": "raw_tx_signature",
                "quorum": 1,
                "keys": [{
                    "xpub": "f98b3a39b4eef67707cac85240ef07235c990301b2e0658001545bdb7fde3a21363a23682a1dfbb727dec7565624812c314ca9f31a7f7374101e0247d05cb248",
                    "derivation_path": ["010100000000000000", "0100000000000000"]
                }],
                "signatures": null
            }, {
                "type": "data",
                "value": "b826dcccff76d19d097ca207e053e67d67e3da3a90896ae9fa2d984c6f36d16c"
            }]
        }],
        "allow_additional_actions": false
    }
}

可以看到這里提交的請(qǐng)求數(shù)據(jù),與前面/build-transaction相比,基本上是一樣的,只是多了一個(gè)password,即我們剛才在表單最后一處填寫(xiě)的密碼。從這個(gè)接口的名字中含有sign可以推測(cè),這一步應(yīng)該是與簽名有關(guān)。

得到的回應(yīng)
{
    "status": "success",
    "data": {
        "transaction": {
            "raw_transaction": "070100010161015f643bef0936443042ccb1e94213ed52af72488088702d88e7fc3580359a19a522ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d599010001160014108c5ba0934951a12755523f8a1fe42a6c24342f630240c52a057fa26322a48fdd88c842cf31a84c6aec54ae2dc62554dc3c7e0216986a0a4f4a5c935a5ae6d88b4c7a4d1ca1937205f5eb23089128cc6744fbd2b88d0520b826dcccff76d19d097ca207e053e67d67e3da3a90896ae9fa2d984c6f36d16c02013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8ebaabf4201160014b111c8114dc7ee02050598022b46855fd482d27300013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d4fe955701160014cd451eb3b1ab9949c9a8cdb086fc78b60b444ff200",
            "signing_instructions": [{
                "position": 0,
                "witness_components": [{
                    "type": "raw_tx_signature",
                    "quorum": 1,
                    "keys": [{
                        "xpub": "f98b3a39b4eef67707cac85240ef07235c990301b2e0658001545bdb7fde3a21363a23682a1dfbb727dec7565624812c314ca9f31a7f7374101e0247d05cb248",
                        "derivation_path": ["010100000000000000", "0100000000000000"]
                    }],
                    "signatures": ["c52a057fa26322a48fdd88c842cf31a84c6aec54ae2dc62554dc3c7e0216986a0a4f4a5c935a5ae6d88b4c7a4d1ca1937205f5eb23089128cc6744fbd2b88d05"]
                }, {
                    "type": "data",
                    "value": "b826dcccff76d19d097ca207e053e67d67e3da3a90896ae9fa2d984c6f36d16c"
                }]
            }],
            "allow_additional_actions": false
        },
        "sign_complete": true
    }
}

回過(guò)來(lái)的消息也基本上跟提交的差不多,只是在成功操作后,raw_transaction字段的內(nèi)容也變長(zhǎng)了,還添加上了signatures字段。

我們開(kāi)始看代碼,通過(guò)搜索/sign-transaction,我們很快定位到以下代碼:

api/api.go#L164-L244

func (a *API) buildHandler() {
    // ...
    if a.wallet != nil {
        // ...
        m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
        // ...
}

/sign-transaction對(duì)應(yīng)的handler是a.pseudohsmSignTemplates,讓我們跟進(jìn)去:

api/hsm.go#L53-L63

func (a *API) pseudohsmSignTemplates(ctx context.Context, x struct {
    Password string             `json:"password"`
    Txs      txbuilder.Template `json:"transaction"`
}) Response {
    if err := txbuilder.Sign(ctx, &x.Txs, x.Password, a.pseudohsmSignTemplate); err != nil {
        log.WithField("build err", err).Error("fail on sign transaction.")
        return NewErrorResponse(err)
    }
    log.Info("Sign Transaction complete.")
    return NewSuccessResponse(&signResp{Tx: &x.Txs, SignComplete: txbuilder.SignProgress(&x.Txs)})
}

可以看到這個(gè)方法內(nèi)容也是比較簡(jiǎn)單的。通過(guò)調(diào)用txbuilder.Sign,把前端傳來(lái)的參數(shù)傳進(jìn)去,然后把結(jié)果返回給前端即可。那我們只需要看txbuilder.Sign即可:

blockchain/txbuilder/txbuilder.go#L82-L100

func Sign(ctx context.Context, tpl *Template, auth string, signFn SignFunc) error {
    // 1. 
    for i, sigInst := range tpl.SigningInstructions {
        for j, wc := range sigInst.WitnessComponents {
            switch sw := wc.(type) {
            case *SignatureWitness:
                err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
                // ...
            case *RawTxSigWitness:
                err := sw.sign(ctx, tpl, uint32(i), auth, signFn)
            // ...
            }
        }
    }
    // 2.
    return materializeWitnesses(tpl)
}

可以看到這段代碼邏輯還是比較簡(jiǎn)單:

第1處代碼是兩個(gè)大循環(huán),基本上做了兩件事:

把用戶提交上來(lái)的數(shù)據(jù)中需要簽名的部分取出來(lái),運(yùn)行相關(guān)的簽名函數(shù)sw.sign,生成相關(guān)的簽名signatures

raw_transaction處添加了一些操作符和約束條件,把它變成了一個(gè)合約(這塊還需要以后確認(rèn))

第2處代碼如果發(fā)現(xiàn)前面簽名過(guò)程正確,就調(diào)用materializeWitnesses函數(shù)。它主要是在檢查沒(méi)有數(shù)據(jù)錯(cuò)誤之后,把第1步中生成的簽名signatures添加到tpl對(duì)象上去。

由于sw.SignmaterializeWitnesses基本上都是一些算法或者合約相關(guān)的東西,我們這里就暫時(shí)忽略,以后再研究吧。

這個(gè)接口/sign-transaction的作用應(yīng)該是對(duì)通過(guò)密碼以及公鑰對(duì)“交易”這個(gè)重要的操作進(jìn)行驗(yàn)證,不然大家都能隨便把別人的錢(qián)轉(zhuǎn)到自己帳戶里了。

/submit-transaction

當(dāng)前一步/sign-transaction簽名成功之后,終于可以進(jìn)行最后一步/submit-transaction進(jìn)行最終的提交了。

下面是通過(guò)Chrome的開(kāi)發(fā)工具捕獲的內(nèi)容。

請(qǐng)求的數(shù)據(jù)
{
    "raw_transaction": "070100010161015f643bef0936443042ccb1e94213ed52af72488088702d88e7fc3580359a19a522ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d599010001160014108c5ba0934951a12755523f8a1fe42a6c24342f630240c52a057fa26322a48fdd88c842cf31a84c6aec54ae2dc62554dc3c7e0216986a0a4f4a5c935a5ae6d88b4c7a4d1ca1937205f5eb23089128cc6744fbd2b88d0520b826dcccff76d19d097ca207e053e67d67e3da3a90896ae9fa2d984c6f36d16c02013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8ebaabf4201160014b111c8114dc7ee02050598022b46855fd482d27300013dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d4fe955701160014cd451eb3b1ab9949c9a8cdb086fc78b60b444ff200"
}

可以看到,到了這一步,提交的數(shù)據(jù)就少了,直接把前一步生成的簽名后的raw_transaction提交上去就行了。我想這里的內(nèi)容應(yīng)該已經(jīng)包含了全部需要的信息,并且經(jīng)過(guò)了驗(yàn)證,所以不需要其它數(shù)據(jù)了。

得到的回應(yīng)
{
    "status": "success",
    "data": {
        "tx_id": "6866c1ab2bfa2468ce44451ce6af2a83f3885cdb6a1673fec94b27f338acf9c5"
    }
}

可以看到成功提交后,會(huì)得到一個(gè)tx_id,即為當(dāng)前這個(gè)交易生成的唯一的id,可以用來(lái)查詢。

我們通過(guò)查找/submit-transaction,可以在代碼中找到:

api/api.go#L164-L244

func (a *API) buildHandler() {
    // ...
    if a.wallet != nil {
        // ...
        m.Handle("/submit-transaction", jsonHandler(a.submit))
        // ...
}

那么/submit-transaction所對(duì)應(yīng)的handler就是a.submit了。我們跟進(jìn)去:

api/transact.go#L182-L191

func (a *API) submit(ctx context.Context, ins struct {
    Tx types.Tx `json:"raw_transaction"`
}) Response {
    if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
        return NewErrorResponse(err)
    }

    log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
    return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
}

可以看到主要邏輯就是調(diào)用txbuilder.FinalizeTx來(lái)“終結(jié)”這個(gè)交易,然后把生成的tx_id返回給前端。

讓我們繼續(xù)看txbuilder.FinalizeTx

blockchain/txbuilder/finalize.go#L25-L47

func FinalizeTx(ctx context.Context, c *protocol.Chain, tx *types.Tx) error {
    // 1.
    if err := checkTxSighashCommitment(tx); err != nil {
        return err
    }

    // This part is use for prevent tx size  is 0
    // 2.
    data, err := tx.TxData.MarshalText()
    // ...
    
    // 3.
    tx.TxData.SerializedSize = uint64(len(data))
    tx.Tx.SerializedSize = uint64(len(data))

    // 4.
    _, err = c.ValidateTx(tx)
    // ...
}

這一個(gè)方法整體上還是各種驗(yàn)證

第1處代碼是對(duì)交易對(duì)象簽名相關(guān)的內(nèi)容進(jìn)行嚴(yán)格的檢查,比如參數(shù)個(gè)數(shù)、簽名、甚至某些對(duì)應(yīng)虛擬機(jī)的操作碼,這一塊挺復(fù)雜的,你一定不會(huì)想看blockchain/txbuilder/finalize.go#L66-L113

第2處代碼是把交易數(shù)據(jù)解碼,從看起來(lái)奇怪的16進(jìn)制字符串變成正常的內(nèi)容

第3處代碼是把解析出來(lái)的內(nèi)容的長(zhǎng)度賦值給tx中的某些字段

第4處代碼是對(duì)交易內(nèi)容再次進(jìn)行詳細(xì)的檢查,最后還包括了對(duì)gas的檢查,如果全部正常,則會(huì)把它提交到txPool(用來(lái)在內(nèi)存中保存交易的對(duì)象池),等待廣播出去以及打包到區(qū)塊中。我覺(jué)得這個(gè)名字ValidateTx有點(diǎn)問(wèn)題,因?yàn)樗窗蓑?yàn)證,還包含了提交到池子中,這是兩個(gè)不同的操作,應(yīng)該分開(kāi)

這里涉及到的更細(xì)節(jié)的代碼就不進(jìn)去了,主線我們已經(jīng)有了,感興趣的同學(xué)可以自行進(jìn)去深入研究。

那我們今天關(guān)于提交交易的這個(gè)小問(wèn)題就算是完成了,下次會(huì)繼續(xù)研究剩下的幾個(gè)小問(wèn)題。

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

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

相關(guān)文章

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

    摘要:繼續(xù)看生成地址的方法由于這個(gè)方法里傳過(guò)來(lái)的是而不是對(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中...

    oneasp 評(píng)論0 收藏0
  • 剝開(kāi)原看代碼03:比原如何監(jiān)聽(tīng)p2p端口的

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

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

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

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

    摘要:作者比原項(xiàng)目倉(cāng)庫(kù)地址地址在前一篇中,我們說(shuō)到,當(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ū),寫(xiě)入到它里面的數(shù)據(jù),會(huì)被發(fā)送出去。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https:...

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

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

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

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

0條評(píng)論

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