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

本文详细解读了btcd为何将钱包功能从核心链服务中分离,强调了区块链作为公共数据的共享特性。探讨了btcd的架构、启动流程及交易创建过程,展示了如何通过wallet模块处理sendtoaddress命令,确保交易安全和效率。

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服务器。

2008年爆发全球金融危机,同年11月1日,个自称中本聪(Satoshi Nakamoto)的人在P2P foundation网站上发布了比特币白皮书《比特币:种点对点的电子现金系统》 [6]  ,陈述了他对电子货币的新设想——比特币就此面世。2009年1月3日,比特币创世区块诞生。 和法定货币相比,比特币没有个集中的发行方,而是由网络节点的计算生成,谁都有可能参与制造比特币,而且可以全世界流通,可以在任意台接入互联网的电脑上买卖,不管身处何方,任何人都可以挖掘、购买、出售或收取比特币,并且在交易过程中外人无法辨认用户身份信息。2009年1月5日,不受央行和任何金融机构控制的比特币诞生。比特币是种数字货币,由计算机生成的串串复杂代码组成,新比特币通过预设的程序制造。 每当比特币进入主流媒体的视野时,主流媒体总会请些主流经济学家分析下比特币。早先,这些分析总是集中在比特币是不是骗局。而现如今的分析总是集中在比特币能否成为未来的主流货币。而这其中争论的焦点又往往集中在比特币的通缩特性上。 [7]  不少比特币玩家是被比特币的不能随意增发所吸引的。和比特币玩家的态度截然相反,经济学家们对比特币2100万固定总量的态度两极分化。 凯恩斯学派的经济学家们认为政府应该积极调控货币总量,用货币政策的松紧来为经济适时的加油或者刹车。因此,他们认为比特币固定总量货币牺牲了可调控性,而且更糟糕的是将不可避免地导致通货紧缩,进而伤害整体经济。奥地利学派经济学家们的观点却截然相反,他们认为政府对货币的干预越少越好,货币总量的固定导致的通缩并没什么大不了的,甚至是社会进步的标志。 比特币网络通过“挖矿”来生成新的比特币。所谓“挖矿”实质上是用计算机解决项复杂的数学问题,来保证比特币网络分布式记账系统的致性。比特币网络会自动调整数学问题的难度,让整个网络约每10分钟得到个合格答案。随后比特币网络会新生成定量的比特币作为区块奖励,奖励获得答案的人。 [6]  2009年,比特币诞生的时候,区块奖励是50个比特币。诞生10分钟后,第批50个比特币生成了,而此时的货币总量就是50。随后比特币就以约每10分钟50个的速度增长。当总量达到1050万时(2100万的50%),区块奖励减半为25个。当总量达到1575万(新产出525万,即1050的50%)时,区块奖励再减半为12.5个。该货币系统曾在4年内只有不超过1050万个,之后的总数量将被永久限制在约2100万个。 [3]  [8]  比特币是种虚拟货币,数量有限,但是可以用来套现:可以兑换成大多数国家的货币。你可以使用比特币购买些虚拟的物品,比如网络游戏当中的衣服、帽子、装备等,只要有人接受,你也可以使用比特币购买现实生活当中的物品。 2014年2月25日,“比特币中国”的比特币开盘价格为3562.41元,截至下午4点40分,价格已下跌至3185元,跌幅逾10%。根据该平台的历史行情数据显示,在2014年1月27日,1比特币还能兑换5032元人民币。这意味着,该平台上不到个月,比特币价格已下跌了36.7%。 同年9月9日,美国电商巨头eBay宣布,该公司旗下支付处理子公司Braintree将开始接受比特币支付。该公司已与比特币交易平台Coinbase达成合作,开始接受这种相对较新的支付手段。 虽然eBay市场交易平台和PayPal业务还不接受比特币支付,但旅行房屋租赁社区Airbnb和租车服务Uber等Braintree客户将可开始接受这种虚拟货币。Braintree的主要业务是面向企业提供支付处理软件,该公司在2013年被eBay以大约8亿美元的价格收购。 2017年1月22日晚间,火币网、比特币中国与OKCoin币行相继在各自官网发布公告称,为进步抑制投机,防止价格剧烈波动,各平台将于1月24日中午12:00起开始收取交易服务费,服务费按成交金额的0.2%固定费率收取,且主动成交和被动成交费率致。 [9]  5月5日,OKCoin币行网的新数据显示,比特币的价格刚刚再度刷新历史,截止发稿前高触及9222元人民币高位。1月24日中午12:00起,中国三大比特币平台正式开始收取交易费。9月4日,央行等七部委发公告称中国禁止虚拟货币交易。同年12月17日,比特币达到历史高价19850美元。 2018年11月25日,比特币跌破4000美元大关,后稳定在3000多美元。 [10]  11月19日,加密货币恢复跌势,比特币自2017年10月以来首次下探5000美元大关,原因是之前BCH出现硬分叉,且监管部门对首次代币发行(ICO)加强了审查。 [10]  11月21日凌晨4点半,coinbase平台比特币报价跌破4100美元,创下了13个月以来的新低。 2019年4月,比特币再次突破5000美元大关,创年内新高。 [11]  5月12日,比特币近八个月来首次突破7000美元。 [12]  5月14日,据coinmarketcap报价显示,比特币站上8000美元,24小时内上涨14.68%。 [13]  6月22日 ,比特币价格突破10000美元大关。比特币价格在10200左右震荡,24小时涨幅近7%。 [14]  6月26日,比特币价格举突破12000美元,创下自去年1月来近17个月高点。 [15]  6月27日早间,比特币价格度接近14000美元,再创年内新高。 [16]  2020年2月10日,比特币突破了万美元。据交易数据,比特币的价格涨幅突破3% [17]  。3月12日,据加密货币交易平台Bitstamp数据显示,19点44分,比特币低价格已跌至5731美元 [18]  。5月8日,比特币突破10000美元关口,创下2月份以来的新高 [19]  。5月10日早上8点开始,比特币单价在半小时内从9500美元价位瞬间下跌了上千美元,低价格跌破8200美元,高价差超1400美元 [20]  。7月26日下午6点,比特币短时极速拉升,高触及10150.15USDT,日内大涨幅超过4%,这是2020年6月2日以来首次突破1万美元关口 [21]  。11月4日,比特币价格正式突破14000美元 [22]  。11月12日晚,比特币价格突破16000美元,刷新2018年1月以来新高,周涨超8.6%。比特币总市值突破2915亿美元 [23]  。11月18日,比特币价格突破17000美元 [24]  。12月1日,比特币价格报19455.31美元,24小时涨幅为5.05%。 [25]  12月17日,比特币价格突破23000美元整数关口,刷新历史新高,日内涨幅超7.5%。 [26]  截至12月27日19时20分,比特币报价28273.06美元。 [27]  2021年1月8日,比特币涨至4万美元关口上方,高至40402美元
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值