btcd交易流程之交易的创建(一)

btcd区块链与钱包模块分离

btcd是bitcoin的go语言实现,它严格遵循了Bitcoin Core的规则。btcd和Bitcoin Core的重要不同是,btcd不包含钱包功能,这是有意为之。这意味着不能直接用btcd付钱、收钱。这些功能在btcwallet项目中实现。
为什么要将wallet功能单独出来?原因如下:

One of the major problems with wallet and chain functionality integrated in the same process is multi-user support. For example, when using bitcoind or bitcoin-qt, two users sharing the same computer must each maintain their own block chain. This results in duplicated effort and wasted disk space, as the block chain is public data and should be sharable. Due to this, btcd is designed to provide chain services for separate wallet processes. These processes then are able to request updates to the chain and submit transactions to the network without having to deal with all the complexities of chain management.

简而言之,就是因为区块链是公共数据,应该被共享。为了共享btcd的区块链,让多个用户共用一条区块链,将钱包功能分离出来。

BTCD的架构

btcd的整体架构

各部分的作用:

addmgr :进行peer地址的管理, 主要是一些本地的工作,不涉及直接的网络连接或传输;10min保存一次IP地址到文件中
blockchain :实现比特币区块处理和链选择规则;./fullblocktests : 提供一组用于测试共识验证规则的块测试
btcec :实现对比特币脚本所需的椭圆曲线密码函数的支持
btcjson :为底层的json-rpc命令和返回值提供一个扩展api
chaincfg :比特币的参数配置,包括地址头,genesis哈希等
connmgr :进行peer之间连接的管理。发现新的peer节点,建立连接;对等节点关闭,关闭连接。连接别人的列表,被别人连接的列表。建立连接是需要对方peer的地址,这依赖于addrManager中管理的地址。
database : 为比特币区块链提供数据库接口。基于leveldb,参考boltdb,开发了上层的api
mempool : 比特币交易池
mining : PoW挖矿
netsync : 同步管理器,用于统一处理各个节点发送和接收到的数据,以便同步区块链和同步交易。
peer : 实现了P2P网络中peer之间维持连接及收发wire协议消息的机制。
rpcclient : 实现一个强大且易于使用的支持websocket的比特币json-rpc客户端
txscript : 实现比特币交易脚本语言
wire : 实现bitcoin网络协议,定义理论peers之间的协议消息,消息格式及包的封装和解析等。
btcd的启动流程

在这里插入图片描述

btcd数据传输协议

在这里插入图片描述

交易的创建

交易创建流程的概述

btcwallet从命令行接收用户命令,btcwallet随后从UTXO找出支付给该用户的交易输出,设置好付款地址、找零地址、交易费等,就将交易发送到运行btcd的服务器,由btcd服务器进一步向其对等方发送交易。

交易创建的源码分析

由btcwallet开始,对交易的流程进行分析。以sendToAddress RPC为例:

// sendToAddress处理了一个sendtoaddressRPC,它创建了一个交易花掉一个钱包对应的UTXO
// 剩下的钱将返回钱包的一个新的地址
func sendToAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
	cmd := icmd.(*btcjson.SendToAddressCmd)

	// Transaction comments are not yet supported.  Error instead of
	// pretending to save them.
	if !isNilOrEmpty(cmd.Comment) || !isNilOrEmpty(cmd.CommentTo) {
		return nil, &btcjson.RPCError{
			Code:    btcjson.ErrRPCUnimplemented,
			Message: "Transaction comments are not yet supported",
		}
	}

	amt, err := btcutil.NewAmount(cmd.Amount)
	if err != nil {
		return nil, err
	}

	// Check that signed integer parameters are positive.
	if amt < 0 {
		return nil, ErrNeedPositiveAmount
	}

	// Mock up map of address and amount pairs.
	pairs := map[string]btcutil.Amount{
		cmd.Address: amt,
	}

	// sendtoaddress always spends from the default account, this matches bitcoind
	return sendPairs(w, pairs, waddrmgr.KeyScopeBIP0044, waddrmgr.DefaultAccountNum, 1,
		txrules.DefaultRelayFeePerKb)
}

sendToAddress处理了一个sendtoaddress RPC命令,这个命令是由用户发出的。它创建了一个新的交易,花掉属于一个钱包的一些UTXOs并付钱给另一个地址。既没有给付款地址,也没有给矿工打包费的钱将被送回钱包内的一个新的地址。执行成功后将返回创建的交易的TxID。

sendToAddress首先将icmd转换为SendToAddressCmd

cmd := icmd.(*btcjson.SendToAddressCmd)

SendToAddressCmd包含以下四个域:

// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
type SendToAddressCmd struct {
	Address   string
	Amount    float64
	Comment   *string
	CommentTo *string
}

CommentCommentTo目前不被btcwallet支持,这里略过。
继续看amt, err := btcutil.NewAmount(cmd.Amount)完成了什么工作:

// NewAmount创建了一个代表bitcoin数的数额,以Satoshi为单位
func NewAmount(f float64) (Amount, error) {
	// The amount is only considered invalid if it cannot be represented
	// as an integer type.  This may happen if f is NaN or +-Infinity.
	switch {
	case math.IsNaN(f):
		fallthrough
	case math.IsInf(f, 1):
		fallthrough
	case math.IsInf(f, -1):
		return 0, errors.New("invalid bitcoin amount")
	}

	return round(f * SatoshiPerBitcoin), nil
}

NewAmount将返回一个Amount对象,Amount实际上是int64,它表示bitcoin的数量,它以Satoshi为单位:

// Amount represents the base bitcoin monetary unit (colloquially referred
// to as a `Satoshi').  A single Amount is equal to 1e-8 of a bitcoin.
type Amount int64

NewAmount实际上将以bitcoin为单位的数额转换为以Satoshi为单位的数额。

// Mock up map of address and amount pairs.
	pairs := map[string]btcutil.Amount{
		cmd.Address: amt,
	}

pairs是一个由付款地址到付款Satoshi数量的映射。
继续查看sendPairs函数:

// sendPairs创建并发送付款交易,成功后将返回交易的哈希。
func sendPairs(w *wallet.Wallet, amounts map[string]btcutil.Amount,
	keyScope waddrmgr.KeyScope, account uint32, minconf int32,
	feeSatPerKb btcutil.Amount) (string, error) {

	outputs, err := makeOutputs(amounts, w.ChainParams())
	if err != nil {
		return "", err
	}
	tx, err := w.SendOutputs(
		outputs, &keyScope, account, minconf, feeSatPerKb,
		wallet.CoinSelectionLargest, "",
	)
	if err != nil {
		if err == txrules.ErrAmountNegative {
			return "", ErrNeedPositiveAmount
		}
		if waddrmgr.IsError(err, waddrmgr.ErrLocked) {
			return "", &ErrWalletUnlockNeeded
		}
		if _, ok := err.(btcjson.RPCError); ok {
			return "", err
		}

		return "", &btcjson.RPCError{
			Code:    btcjson.ErrRPCInternal.Code,
			Message: err.Error(),
		}
	}

	txHashStr := tx.TxHash().String()
	log.Infof("Successfully sent transaction %v", txHashStr)
	return txHashStr, nil
}

sendPairs创建、发送付款交易。操作成功后,它将返回交易的哈希。
首先看makeOutputs代码:

// makeOutputs根据地址到金额的映射创建了一个交易输出的slice。这些交易输出将被放到新创建的交易中。
// 交易的输出描述了交易的目的地和比特币的数额。
func makeOutputs(pairs map[string]btcutil.Amount, chainParams *chaincfg.Params) ([]*wire.TxOut, error) {
	outputs := make([]*wire.TxOut, 0, len(pairs))
	for addrStr, amt := range pairs {
		addr, err := btcutil.DecodeAddress(addrStr, chainParams)
		if err != nil {
			return nil, fmt.Errorf("cannot decode address: %s", err)
		}

		pkScript, err := txscript.PayToAddrScript(addr)
		if err != nil {
			return nil, fmt.Errorf("cannot create txout script: %s", err)
		}

		outputs = append(outputs, wire.NewTxOut(int64(amt), pkScript))
	}
	return outputs, nil
}

makeOutputs用一对地址到转账数额的映射,创建了一个交易输出的slice,这个slice之后将被用来创建新的交易。具体地,makeOutputs遍历每个(地址,数额)对,为他们生成脚本,进而生成交易输出。
addr, err := btcutil.DecodeAddress(addrStr, chainParams)将用字符串表示的地址封装为表示地址的Address对象。
pkScript, err := txscript.PayToAddrScript(addr)创建了一个将钱支付到地址addr的脚本,其代码如下:

// PayToAddrScript创建一个将交易输出付款到特定地址的脚本
func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
	const nilAddrErrStr = "unable to generate payment script for nil address"

	switch addr := addr.(type) {
	case *btcutil.AddressPubKeyHash:
		if addr == nil {
			return nil, scriptError(ErrUnsupportedAddress,
				nilAddrErrStr)
		}
		return payToPubKeyHashScript(addr.ScriptAddress())

	case *btcutil.AddressScriptHash:
		if addr == nil {
			return nil, scriptError(ErrUnsupportedAddress,
				nilAddrErrStr)
		}
		return payToScriptHashScript(addr.ScriptAddress())

	case *btcutil.AddressPubKey:
		if addr == nil {
			return nil, scriptError(ErrUnsupportedAddress,
				nilAddrErrStr)
		}
		return payToPubKeyScript(addr.ScriptAddress())

	case *btcutil.AddressWitnessPubKeyHash:
		if addr == nil {
			return nil, scriptError(ErrUnsupportedAddress,
				nilAddrErrStr)
		}
		return payToWitnessPubKeyHashScript(addr.ScriptAddress())
	case *btcutil.AddressWitnessScriptHash:
		if addr == nil {
			return nil, scriptError(ErrUnsupportedAddress,
				nilAddrErrStr)
		}
		return payToWitnessScriptHashScript(addr.ScriptAddress())
	}

	str := fmt.Sprintf("unable to generate payment script for unsupported "+
		"address type %T", addr)
	return nil, scriptError(ErrUnsupportedAddress, str)
}

PayToAddrScript根据地址addr的类型创建了不同的脚本,它们分别如下:

// payToPubKeyHashScript创建一个脚本付款到公钥的哈希
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
	return NewScriptBuilder().AddOp(OP_DUP).AddOp(OP_HASH160).
		AddData(pubKeyHash).AddOp(OP_EQUALVERIFY).AddOp(OP_CHECKSIG).
		Script()
}

// payToWitnessPubKeyHashScript creates a new script to pay to a version 0
// pubkey hash witness program. The passed hash is expected to be valid.
func payToWitnessPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
	return NewScriptBuilder().AddOp(OP_0).AddData(pubKeyHash).Script()
}

// payToScriptHashScript creates a new script to pay a transaction output to a
// script hash. It is expected that the input is a valid hash.
func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
	return NewScriptBuilder().AddOp(OP_HASH160).AddData(scriptHash).
		AddOp(OP_EQUAL).Script()
}

// payToWitnessPubKeyHashScript creates a new script to pay to a version 0
// script hash witness program. The passed hash is expected to be valid.
func payToWitnessScriptHashScript(scriptHash []byte) ([]byte, error) {
	return NewScriptBuilder().AddOp(OP_0).AddData(scriptHash).Script()
}

// payToPubkeyScript creates a new script to pay a transaction output to a
// public key. It is expected that the input is a valid pubkey.
func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) {
	return NewScriptBuilder().AddData(serializedPubKey).
		AddOp(OP_CHECKSIG).Script()
}

这就是我们熟悉的比特币脚本语言,它以[]byte格式返回。

回到sendPairs,创建了交易输出outputs后,调用了Wallet对象的SendOutputs方法发送交易。
SendOutputs方法代码如下:

// SendOutputs创建并发送了付款交易。币的选择由钱包负责,钱包将选择属于一个给定key范围和账户的输入
// 如果key范围未指明,属于一个账户的所有输入都可能被选择。
func (w *Wallet) SendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
	account uint32, minconf int32, satPerKb btcutil.Amount,
	coinSelectionStrategy CoinSelectionStrategy, label string) (
	*wire.MsgTx, error) {

	// 确保将要创建的交易的输出遵守比特币网络的共识规则
	for _, output := range outputs {
		err := txrules.CheckOutput(
			output, txrules.DefaultRelayFeePerKb,
		)
		if err != nil {
			return nil, err
		}
	}

	// 创建交易并广播到网络。交易将被加到数据库中,这是为了确保发生重启后能够重新进行广播,直到它被确认
	createdTx, err := w.CreateSimpleTx(
		keyScope, account, outputs, minconf, satPerKb,
		coinSelectionStrategy, false,
	)
	if err != nil {
		return nil, err
	}

	// If our wallet is read-only, we'll get a transaction with coins
	// selected but no witness data. In such a case we need to inform our
	// caller that they'll actually need to go ahead and sign the TX.
	if w.Manager.WatchOnly() {
		return createdTx.Tx, ErrTxUnsigned
	}

	txHash, err := w.reliablyPublishTransaction(createdTx.Tx, label)
	if err != nil {
		return nil, err
	}

	// Sanity check on the returned tx hash.
	if *txHash != createdTx.Tx.TxHash() {
		return nil, errors.New("tx hash mismatch")
	}

	return createdTx.Tx, nil
}

SendOutputs创建、发送了付款交易。选择哪些UTXO作为输入由wallet决定。
SendOutputs首先检查了每个输出脚本是否遵守网络的共识规则。具体地,包括输出值是否小于零、输出值是否大于btcutil.MaxSatoshi、这个交易是否是dust。

如果网络花费硬币的成本超过最低交易中继费的1/3,则视为dust。

w.CreateSimpleTx创建了一个交易并将它广播到网络中。这个交易会被添加到数据库中,这是为了保证即使机器重启后仍能继续广播交易,直到该交易收到了确认。
CreateSimpleTx代码如下:

// CreateSimpleTx创建一个已经签名的交易,花掉至少有minconf个确认的UTXOs。
// 只有属于给定key范围及账户的UTXO会被选择,除非key范围未指定。找零和适合的交易费将自动包含。
func (w *Wallet) CreateSimpleTx(keyScope *waddrmgr.KeyScope, account uint32,
	outputs []*wire.TxOut, minconf int32, satPerKb btcutil.Amount,
	coinSelectionStrategy CoinSelectionStrategy, dryRun bool) (
	*txauthor.AuthoredTx, error) {

	req := createTxRequest{
		keyScope:              keyScope,
		account:               account,
		outputs:               outputs,
		minconf:               minconf,
		feeSatPerKB:           satPerKb,
		coinSelectionStrategy: coinSelectionStrategy,
		dryRun:                dryRun,
		resp:                  make(chan createTxResponse),
	}
	w.createTxRequests <- req
	resp := <-req.resp
	return resp.tx, resp.err
}

它完成的工作是创建一个createTxRequest结构体req,将它发送到Wallet对象的createTxRequests通道中。req实际上由Wallet对象的txCreator方法接收,其代码如下:

// txCreator负责交易输入的选择以及交易的创建。输入选择需要被序列化,
// 否则有可能选择了已经花费了的UTXO,进而创建一个双花交易。除了选择UTXO,
// 这个方法也负责对交易进行签名,这是因为我们不希望陷入创建交易导致而输入被用完的情况。
// 在这种情况下,由于没有足够的可用的输入,可以让多个请求都失败,而不是其中一个请求失败。
func (w *Wallet) txCreator() {
	quit := w.quitChan()
out:
	for {
		select {
		case txr := <-w.createTxRequests:
			// If the wallet can be locked because it contains
			// private key material, we need to prevent it from
			// doing so while we are assembling the transaction.
			release := func() {}
			if !w.Manager.WatchOnly() {
				heldUnlock, err := w.holdUnlock()
				if err != nil {
					txr.resp <- createTxResponse{nil, err}
					continue
				}

				release = heldUnlock.release
			}

			tx, err := w.txToOutputs(
				txr.outputs, txr.keyScope, txr.account,
				txr.minconf, txr.feeSatPerKB,
				txr.coinSelectionStrategy, txr.dryRun,
			)

			release()
			txr.resp <- createTxResponse{tx, err}
		case <-quit:
			break out
		}
	}
	w.wg.Done()
}

txToOutputs根据outputs []*wire.TxOut创建一个经过签名的交易。其完成的工作如下:

  1. requireChainClient用于标志一个方法只有在wallet对象的RPC服务器设置后才能完成,调用它确保设置了RPC服务器;
  2. 开启了数据库中的一个事务,其完成的工作如下:
  3. 获取地址管理器桶ReadWriteBucket(一个数据库内等级制的结构,它可以执行读写操作)和找零源ChangeSource(为交易创建提供找零输出脚本)
  4. 在UTXO中找到所有合乎资格的交易(具体地,是否达到了最小确认数conf要求,UTXO是否被锁定,是否与给定的账户关联)
  5. 根据传入的coinSelectionStrategy对UTXOs进行选择(包括CoinSelectionLargestCoinSelectionRandom策略),以选定的UTXOs作为输入源;
  6. 调用NewUnsignedTransaction根据确定的交易输出outputs,每kb交易费用feeSatPerKb,输入源inputSource,找零源changeSource创建一个未签名的交易;
  7. 调用AddAllInputScripts对生成的交易进行签名;
  8. 如果有找零,请求后端在确认该交易后告知找零交易;

至此,txToOutputs创建了完整的交易,返回给其调用者txCreatortxCreator通过CreateSimpleTx在请求中附带的响应通道进行回复。CreateSimpleTx得到回复后,将该交易返回给其调用者SendOutputsSendOutputs随后调用reliablyPublishTransaction发布交易。
reliablyPublishTransaction代码如下:

// reliablyPublishTransaction是publishTransaction的一个超集,
// 它包含了发布一个交易、更新相关的数据库状态、如果该交易被后端拒绝从数据库移除该交易需要的原语逻辑。
func (w *Wallet) reliablyPublishTransaction(tx *wire.MsgTx,
	label string) (*chainhash.Hash, error) {

	chainClient, err := w.requireChainClient()
	if err != nil {
		return nil, err
	}

	// As we aim for this to be general reliable transaction broadcast API,
	// we'll write this tx to disk as an unconfirmed transaction. This way,
	// upon restarts, we'll always rebroadcast it, and also add it to our
	// set of records.
	txRec, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
	if err != nil {
		return nil, err
	}

	// Along the way, we'll extract our relevant destination addresses from
	// the transaction.
	var ourAddrs []btcutil.Address
	err = walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error {
		addrmgrNs := dbTx.ReadWriteBucket(waddrmgrNamespaceKey)
		for _, txOut := range tx.TxOut {
			_, addrs, _, err := txscript.ExtractPkScriptAddrs(
				txOut.PkScript, w.chainParams,
			)
			if err != nil {
				// Non-standard outputs can safely be skipped because
				// they're not supported by the wallet.
				continue
			}
			for _, addr := range addrs {
				// Skip any addresses which are not relevant to
				// us.
				_, err := w.Manager.Address(addrmgrNs, addr)
				if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
					continue
				}
				if err != nil {
					return err
				}
				ourAddrs = append(ourAddrs, addr)
			}
		}

		if err := w.addRelevantTx(dbTx, txRec, nil); err != nil {
			return err
		}

		// If the tx label is empty, we can return early.
		if len(label) == 0 {
			return nil
		}

		// If there is a label we should write, get the namespace key
		// and record it in the tx store.
		txmgrNs := dbTx.ReadWriteBucket(wtxmgrNamespaceKey)
		return w.TxStore.PutTxLabel(txmgrNs, tx.TxHash(), label)
	})
	if err != nil {
		return nil, err
	}

	// We'll also ask to be notified of the transaction once it confirms
	// on-chain. This is done outside of the database transaction to prevent
	// backend interaction within it.
	if err := chainClient.NotifyReceived(ourAddrs); err != nil {
		return nil, err
	}

	return w.publishTransaction(tx)
}

它包含发布事务、更新相关数据库状态以及最终可能从数据库中删除事务(以及清除所有使用的输入和创建的输出)所需的主要逻辑(如果事务被后端拒绝)。

  1. 调用requireChainClient检查共识RPC服务器是否被设置;
  2. 将未被确认的交易写到磁盘,以确保重启后被重复广播;
  3. 请求当交易上链后被通知;
  4. 调用publishTransaction将未被确认的交易发到钱包目前对应的运行btcd的服务器。

publishTransaction的代码逻辑如下:
调用SendRawTransaction将交易发出,如果返回没有错误,则返回交易的哈希;如果有错误,则枚举错误类型,如“交易已经存在”、“交易已经在内存池”…,并对其进行相应处理。

SendRawTransaction是一个接口,根据后端服务器的不同,其实现不同,以BitcoindClient为例,其SendRawTransaction代码如下:

// SendRawTransaction sends a raw transaction via bitcoind.
func (c *BitcoindClient) SendRawTransaction(tx *wire.MsgTx,
	allowHighFees bool) (*chainhash.Hash, error) {

	return c.chainConn.client.SendRawTransaction(tx, allowHighFees)
}

继续查看SendRawTransaction方法:

// SendRawTransaction submits the encoded transaction to the server which will
// then relay it to the network.
func (c *Client) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) {
	return c.SendRawTransactionAsync(tx, allowHighFees).Receive()
}

继续看异步发送未经处理交易的SendRawTransactionAsync方法:

// SendRawTransactionAsync returns an instance of a type that can be used to get
// the result of the RPC at some future time by invoking the Receive function on
// the returned instance.
//
// See SendRawTransaction for the blocking version and more details.
func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) FutureSendRawTransactionResult {
	txHex := ""
	if tx != nil {
		// Serialize the transaction and convert to hex string.
		buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
		if err := tx.Serialize(buf); err != nil {
			return newFutureError(err)
		}
		txHex = hex.EncodeToString(buf.Bytes())
	}

	// Due to differences in the sendrawtransaction API for different
	// backends, we'll need to inspect our version and construct the
	// appropriate request.
	version, err := c.BackendVersion()
	if err != nil {
		return newFutureError(err)
	}

	var cmd *btcjson.SendRawTransactionCmd
	switch version {
	// Starting from bitcoind v0.19.0, the MaxFeeRate field should be used.
	case BitcoindPost19:
		// Using a 0 MaxFeeRate is interpreted as a maximum fee rate not
		// being enforced by bitcoind.
		var maxFeeRate int32
		if !allowHighFees {
			maxFeeRate = defaultMaxFeeRate
		}
		cmd = btcjson.NewBitcoindSendRawTransactionCmd(txHex, maxFeeRate)

	// Otherwise, use the AllowHighFees field.
	default:
		cmd = btcjson.NewSendRawTransactionCmd(txHex, &allowHighFees)
	}

	return c.sendCmd(cmd)
}

该方法首先将交易序列化为[]byte,然后编码为一个string,最后再将其封装为SendRawTransactionCmd,调用c.sendCmd(cmd)将命令发出。sendCmd的代码如下:

// sendCmd发送一个给出的命令到相关联的服务器,它将返回一个回复通道,回复将在某个时间点发送到该通道上。
// 它可以处理websocket和http post方法两种,具体取决于客户的配置。
func (c *Client) sendCmd(cmd interface{}) chan *response {
	rpcVersion := btcjson.RpcVersion1
	if c.batch {
		rpcVersion = btcjson.RpcVersion2
	}
	// Get the method associated with the command.
	method, err := btcjson.CmdMethod(cmd)
	if err != nil {
		return newFutureError(err)
	}

	// Marshal the command.
	id := c.NextID()
	marshalledJSON, err := btcjson.MarshalCmd(rpcVersion, id, cmd)
	if err != nil {
		return newFutureError(err)
	}

	// Generate the request and send it along with a channel to respond on.
	responseChan := make(chan *response, 1)
	jReq := &jsonRequest{
		id:             id,
		method:         method,
		cmd:            cmd,
		marshalledJSON: marshalledJSON,
		responseChan:   responseChan,
	}

	c.sendRequest(jReq)

	return responseChan
}

该函数逻辑比较简单,将cmd连同rpcVersionJSON-RPC消息的id封装为一个json,又进一步封装为jsonRequest结构体,继续调用c.sendRequest(jReq)发送请求。值得一提的是,jsonRequest中包括了一个相应通道。
sendRequest的代码如下:

// sendRequest将给定的json请求发送到相关联的服务器,并且用提供的回复通道进行回复。
func (c *Client) sendRequest(jReq *jsonRequest) {
	// Choose which marshal and send function to use depending on whether
	// the client running in HTTP POST mode or not.  When running in HTTP
	// POST mode, the command is issued via an HTTP client.  Otherwise,
	// the command is issued via the asynchronous websocket channels.
	if c.config.HTTPPostMode {
		if c.batch {
			if err := c.addRequest(jReq); err != nil {
				log.Warn(err)
			}
		} else {
			c.sendPost(jReq)
		}
		return
	}

	// Check whether the websocket connection has never been established,
	// in which case the handler goroutines are not running.
	select {
	case <-c.connEstablished:
	default:
		jReq.responseChan <- &response{err: ErrClientNotConnected}
		return
	}

	// Add the request to the internal tracking map so the response from the
	// remote server can be properly detected and routed to the response
	// channel.  Then send the marshalled request via the websocket
	// connection.
	if err := c.addRequest(jReq); err != nil {
		jReq.responseChan <- &response{err: err}
		return
	}
	log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
	c.sendMessage(jReq.marshalledJSON)
}

sendRequest根据client的配置决定发送命令的方式:

  • 如果是HTTPPostMode,(如果是批处理命令,通过调用addRequest将传递的jsonRequest与其id相关联。这允许将来自远程服务器的响应解组为适当的类型,并在收到时发送到指定的通道。)调用c.sendPost(jReq)通过HTTP POST方法将请求发到服务器。具体地,sendPost设置好HTTP请求后,将请求发送到c.sendPostChan通道中,由sendPostHandler对请求进行处理。
  • 用已经建立的websocket连接发送请求。具体地,将请求发送到clientsendChan中,由wsOutHandler进行处理。

至此,交易已经成功创建并发到btcd服务器。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值