0%

使用比特币节点进行交易

使用比特币节点进行交易

如果读过之前的完整的一系列文章,应该已经经历了从节点源代码编译,运行,和配置 rpc 的完全过程,本文在之前工作的过程上进行总结,使用比特币节点发起一笔交易

注意:读了之前的文章也应该清楚一点,比特币节点代码和 rpc api 更新较快。本文依赖比特币 0. 18 版本,系统环境乌班图。

比特币发起交易步骤

首先列出比特币交易的最主干的过程如下

  1. 提取UTXO
  2. 发起交易
  3. 对交易进行签名
  4. 广播交易

提取UXTO

交易的最重要的部分就是一开始的提取UXTO的过程。根据官方的 RPC Api 。我们可以使用 listunspent 的api 来获取UTXO的列表。

这是交易过程中第一个会出现问题的地方,如果我们直接使用这个 api 会发现一个问题,我们可能会发现完全提取不倒信息。

这是因为我们想要发起交易,很大程度上依赖的是比特币节点的钱包的功能。(关于比特币全节点的概念,请参考《精通比特币》一书)。这个比特币地址如果和本节点上的钱包是没有关联的话,我们是无法得到UTXO的。

接下来介绍什么叫做关联

根据比特币早期的文档(文档版本比较老,可以去查阅早期的wiki文档,不再列出)。比特币早期中有账号的概念。也就是说,比特币钱包都是依赖账号来管理的 Account。比特币的地址都是存在某个账号下的。之所以直接查询对应的比特币地址没有信息。是因为我们的地址,不是比特币钱包的账号下面的,所以我们直接去过滤相应地址下的UTXO是没有信息的。

当然后期的版本比特币不再强调账号的概念,换成了如下体系。

比特币钱包使用 name 属性来管理,钱包内部使用 label 对地址进行管理(图稍后补上)

在体系途中 xxxxx yyyyy zzzzz代表地址,Alvin Mike 代表钱包的 name。我们直接查询的 zzzzz 属于和钱包没有关系的地址,属于 没有关联

如何关联

那么如何关联呢,其实很简单,我们只需要把地址对应的私钥导入钱包即可。我们可以使用比特币 RPC Api 中的 importprivkey 来导入私钥。这样这个地址就纳入了钱包中,属于地址和钱包进行了关联。我们可以参考文档

importprivkey

请仔细参阅文档,注意参数 rescan。导入私钥请开启 rescan,这样的话节点会对这个地址进行扫描,对该地址对应的 UTXO 进行索引。这样就可以查询 UTXO 了。当然他需要一个较长时间构建索引,在运行期间你可能无法得到正确的结果,你只需要等待 rescan 完成即可。

不导入私钥可以吗

不导入私钥是可以的,我们参阅文档可以得到以下两种方法,都可以在不导入私钥的情况下查询UXTO

  1. 在启动节点的时候加入 txindex = 1; 这个参数可以帮助你建立所有的交易索引,这样你就可以在节点上查询所有地址的交易信息了。大胆猜测,比特币浏览器之类的节点需要这种配置
  2. 可以只导入地址,假定只导入地址的话使用 importprivkey ,没有私钥,无法签名。作为只读钱包还是可以的。具体请参阅文档。

以上两种不是很方便,就不再详细介绍了。

再强调一下

网上搜集很多教程中,都会出现从起一个节点开始,然后使用 getnewaddress 这个 Api 。这种操作其实就是就规避了关联 这个问题,因为直接用钱包的功能生成的地址就是挂在钱包下面的,所以我们可以很容易的查询这种地址下面的 UTXO。

发起交易

确切的说,我们需要的是发起裸交易,(也有 sendtoadddress 方法,这个不属于本文的讨论范围)。这个就比较直接,这里依然可以从前文提到的链接中方便的找到说明。

发起裸交易的核心就是从之前的某交易中,支付一定数量的比特币到目标地址。至少要满足的条件是:你发的比特币数量要低于交易中的比特币总量。毕竟钱不够如何支付呢。

注意:我们使用的是 createrawtransaction 。这种交易方式不会生成找零地址。则输入和输出之差会成为交易费用 fee 将会成为矿工的奖励。这种交易会造成超高费用交易,同时会影响最后一步的交易广播。关于对广播的影响,我们稍后再谈。如果我们需要创建找零地址,请使用 fundcreaterawtransaction。(后面可能会出现专门的一篇介绍这个)

总之这一步之后 我们会得到一个未经过签名的 rawhex

交易签名

得到一个裸交易的 hexstring 之后,需要我们对交易进行签名,在当前 0.18 版本下我们有两条签名路径

  1. 使用钱包的签名功能 [https://bitcoincore.org/en/doc/0.18.0/rpc/wallet/signrawtransactionwithwallet/]

    使用钱包的签名功能需要我们事先导入私钥,也就是说地址对应的私钥和钱包是有关联

  2. 使用私钥进行签名 [https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/signrawtransactionwithkey/]

    使用私钥进行签名,需要我们有发起地址的所有权,也就是有私钥

无论使用哪种签名,都可以得到签名之后的 hexstring 。

这个也是交易中最关键的步骤步骤,如果我们签名正常结束,我们会得到签名的结果

“complete”: true

同时得到一个更长的经过签名的 hexstring

需要注意的是:出现complete: true 这种结果并不代表签名就是万无一失的,这种签名可能会有别的错误,我们只是提供了合适的参数进行签名而已。如果这里的签名不正常,这次交易是无法广播的。

如果需要耍个小聪明,稍微调整参数,你会发现得到的签名后 hex 是有细微差别的(这个很容易理解,因为这一串字符串本身就是拼接而来的)

所以无法广播,请优先考虑签名错误。请仔细对照文档中的PrevTX参数。

广播交易

如果我们前面三个步骤正确,到这一步直接广播交易即可

我们使用 sendrawtransaction 方法即可,参数就是上一步中得到的 $SIGNED_RAW_TRANSACTION_STRING

这里提到的一个超高交易费的问题,如果我们使用之前的 createrawtaranstion 是没有设置找零地址的,所以这里直接广播是不行的。需要加上参数 allowhignfee 。参考文档。

在允许高额交易费用这个错误之外,我们还可能遇到其他错误,比如我在 Testnet 环境下遇到了 Missing Input 的错误。或者其他类型的错误。排错原则为:优先检查交易签名

之后等待挖矿即可。后文将列出我直接使用 bitcoin-cli 进行交易的过程,为了保持文档的完整性,中间出错的步骤并没有删除,是完整保留的。下文的命令行操作过程是百分百完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

启动私链 发起一个交易

1. bitcoind -printtoconsole -regtest // 其余参数全部参照原来的设计,其余几个项目都去 .bitcoin/bitcoin.conf 下去掉

//先测试私链起来了没有
2. bitcoin-cli -regtest -getinfo

//在查询一下
3. bitcoin-cli -regtest getbestblockhash

//查询当前用户信息 不出所料的话都应该没有币的
4. bitcoin-cli -regtest getbalance

//现在生成一个地址 得到一个地址 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e 这个地址作为初始的地址
5. bitcoin-cli -regtest getnewaddress

//验证一下这个地址
6. bitcoin-cli -regtest validateaddress 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e

//尝试导出这个私钥 cS3c2UNjTjhREK59T7YK93NrvF9vUTXdP9d6vgR9bqYVJTizQtuj
7. bitcoin-cli -regtest dumpprivkey 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e

//开始挖矿 注意这里的 api 发生了变化 以前是直接generate 现在推荐下面的方法 直接100个区块到之前生成的地址上
8. bitcoin-cli -regtest generatetoaddress 101 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e

//接下来 获取当前账户下的信息 确认一百个块 得到50个比特币
9. bitcoin-cli -regtest getbalance

//再生成第二个地址 用于接受转账 2N9KXa3WdbbGRYM4XgNWqnQemDRSj5fqgDh
10. bitcoin-cli -regtest getnewaddress

//接下来 提取utxo
11. bitcoin-cli -regtest listunspent
[
{
"txid": "bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4",
"vout": 0,
"address": "2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e",
"label": "",
"redeemScript": "0014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292",
"scriptPubKey": "a9147cbeaeb1f3919c7e0963c635287ce3385dfa91dd87",
"amount": 50.00000000,
"confirmations": 101,
"spendable": true,
"solvable": true,
"desc": "sh(wpkh([bec83093/0'/0'/0']0295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c3012180))#pv6ux7wl",
"safe": true
}
]

//创建一个裸交易 给新生成的地址转20个比特币 得到一个交易id 0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8700000000
12. bitcoin-cli -regtest createrawtransaction '[{"txid" :"bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4","vout" : 0}]' '{"2N9KXa3WdbbGRYM4XgNWqnQemDRSj5fqgDh": 20}'

//使用私钥进行签名
13. bitcoin-cli -regtest signrawtransactionwithwallet 0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8700000000 '[{"txid":"bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4", "vout":0, "scriptPubKey":"a9147cbeaeb1f3919c7e0963c635287ce3385dfa91dd87","amount":20, "redeemScript":"0014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292"}]'

得到

{
"hex": "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8702473044022013597740ee733abe79849008462e3e87cc96c6e7e195fbcb74a7519c9df37e04022039ee7076ad688fa68f4a4d4497b5e0b2504682137e0359c100c49310ebdaef9401210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000",
"complete": true
}

// 广播交易 出错
14. bitcoin-cli -regtest sendrawtransaction 02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8702473044022013597740ee733abe79849008462e3e87cc96c6e7e195fbcb74a7519c9df37e04022039ee7076ad688fa68f4a4d4497b5e0b2504682137e0359c100c49310ebdaef9401210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000

//只使用;裸交易id进行签名
15. bitcoin-cli -regtest signrawtransactionwithwallet "0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8700000000"
{
"hex": "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef870247304402205388b5674d8874a8fc9ae7acbd39f14a0658163c22684dcd8bc32073ec295d1702206cbda91f9bbfc5479e380077ff2ba5e480a402f883fc638b8c82ad71e01977bc01210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000",
"complete": true
}

// 广播
16. bitcoin-cli -regtest sendrawtransaction "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef870247304402205388b5674d8874a8fc9ae7acbd39f14a0658163c22684dcd8bc32073ec295d1702206cbda91f9bbfc5479e380077ff2ba5e480a402f883fc638b8c82ad71e01977bc01210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000" true

得到 3b3c8646fb03764aae2b2d9c8ef81704576f94c356e2d38f76bcd98ea4d9a5ae 已完成交易 等待挖矿即可

额外提醒

在完成以上操作后,就实现了利用比特币节点进行交易,此外再提供两点温馨提示

  1. 如果使用私链,使用命令行启动和使用bitcoin.conf启动,这两个配置一定不能冲突,否则比特币节点是无法启动的。(当然别的方式这两个配置也不能冲突,算是比特币一个比较人性化的地方?)
  2. Bitcoin-cli 操作本质上是使用 json-rpc 使用 curl 一样可以达到这个效果。当然也不是为了使用代码进行交互。这里使用的认证方式是 Basic。如果需要写代码,注意这一点。之后可能会列出我自己 封装的 bitcoin-json rpc 代码。
  3. 实在想测试,建议使用私链。环境比较纯净。你不会遇到:比特币过少,网络差等问题。最主要的是你可以自己挖矿,适合急性子。