比特币应用开发杂记(一):查询、监听和广播交易

杂谈

最近对比特币(SV)中提到的MetaNet技术十分着迷,十分想写一些MetaNet应用,不过这方面中文公开内容并不多,所以开始了收集和学习,并记录一些笔记。

首先说一下对MetaNet的原理的理解。

MetaNet就是把比特币中的Transactions(TX)当作网络协议使用,并将打包的TX作为永久存储的一种比特币网络的用法。

TX的交换可以借助比特币P2P网络,也可以不借助。比特币网络的挖矿只是将TX持久化的一种手段。

当然,借助比特币网络路由TX的话,可以做到匿名,也是另一种需求了。

我收集了一些开发的资料,放在了awssome-bitcoin-sv

目前,即便是Bitcoin SV网络,也并没有真正启用MetaNet,还有很多限制要解除,这些限制是core加上的,使得BTC成为了一个纯粹转账用的网络,所以应用开发沉寂了很多。

可以看到SV将要解除的限制有:

1、用于替换请求的Sequence字段限制

2、OP_RETURN限制

3、脚本、TX字节数限制

4、非标准脚本限制

解除这些限制之后,就可以做一些链上智能合约脚本了。

说实在的,有点翘首企盼,不过在此之前,还是需要先学习基本的链上开发。

环境和工具

关于SV的库目前的确较少。

我决定使用nodejs + node-electrum-client + bsv

1
npm install electrum-client bsv

Electrum-Client的使用

连接节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ElectrumClient = require('electrum-client')
//设置连接的节点为SV节点
const ecl = new ElectrumClient(50002, 'satoshi.vision.cash', 'tls')
const connect = async() => {
console.log("开始连接节点...")
await ecl.connect()
console.log("请求服务端版本...")
const version = await ecl.server_version()
console.log("返回版本数据:"+JSON.stringify(version))
console.log("服务端实现为:"+version[0]+"\r\n服务端支持的协议版本为:"+version[1])
console.log("请求完毕,关闭连接...")
await ecl.close()
}
connect()

ElectrumClient使用ElectrumX协议,可以理解为一种SPV的协议,它与链无关,既可以连接SV链,也可以连接ABC链,还可以连接BTC链,连到哪条链上和节点在哪条链一致。

具体支持的协议可以在这里看。

查询余额、UTXO

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
const ElectrumClient = require('electrum-client')
const address = "1BHcPbcjRZ9ZJvAtr9nd4EQ4HbsUC77WDf";
const ecl = new ElectrumClient(50002, 'satoshi.vision.cash', 'tls')

const account = async () => {
console.log("开始连接节点...")
await ecl.connect()
console.log("请求服务端版本...")
const version = await ecl.server_version()
console.log("返回版本数据:"+JSON.stringify(version))
console.log("服务端实现为:"+version[0]+"\r\n服务端支持的协议版本为:"+version[1])
console.log("开始查询以下地址余额:"+address)
const balance = await ecl.blockchainAddress_getBalance(address)
console.log("余额返回数据:"+JSON.stringify(balance))
console.log("已确认余额:"+balance.confirmed+"\r\n未确认余额:"+balance.unconfirmed)
console.log("开始查询以下地址UTXO:"+address)
const utxos = await ecl.blockchainAddress_listunspent(address)
console.log("UTXO查询返回数据:")
console.log(utxos)
for(let index in utxos){
var utxo=utxos[index]
console.log("UTXO"+index)
console.log("------------------------------------------------------------------------------------------------------")
console.log(" 所在TX:"+utxo.tx_hash)
console.log(" 在TX中的位置:"+utxo.tx_pos)
console.log(" 所在块高度"+utxo.height)
console.log(" 数额:"+utxo.value)
console.log(" 所在TX详情(可使用EC->Tools->LoadTransactions解析):")
console.log(await ecl.blockchainTransaction_get(utxo.tx_hash))
console.log("------------------------------------------------------------------------------------------------------")
}
console.log("查询一些其他的")
console.log("开始查询内存池中以下地址的交易:"+address)
const mem = await ecl.blockchainAddress_getMempool(address)
console.log("内存池查询返回:")
console.log(mem)
console.log("开始查询以下地址所有历史交易:"+address)
const hist = await ecl.blockchainAddress_getHistory(address)
console.log("历史交易查询返回:")
console.log(hist)
console.log("查询完毕,关闭连接")
await ecl.close()
}
account()

Electrum最新的查询是根据scriptHash来查询,而不再是根据address查询。不过address也能查。

监听消息

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
const ElectrumClient = require('electrum-client')

const ecl = new ElectrumClient(50002, 'satoshi.vision.cash', 'tls')
const address = "1BHcPbcjRZ9ZJvAtr9nd4EQ4HbsUC77WDf";
const sleep = (ms) => new Promise((resolve,_) => setTimeout(() => resolve(), ms))

const newPeer = function (peer){
console.log("新Peer")
console.log(peer)
}
const newBlock = function (blockheader){
console.log("新块被挖出:")
console.log(blockheader)
}
const newTX = function (myaddress, status){
console.log("地址"+myaddress+"收到消息")
console.log(status)
}

const listen = async () => {
try{
console.log("准备监听")
ecl.subscribe.on('server.peers.subscribe', newPeer)
ecl.subscribe.on('blockchain.numblocks.subscribe', console.log)
ecl.subscribe.on('blockchain.headers.subscribe', newBlock)
ecl.subscribe.on('blockchain.address.subscribe', newTX)
ecl.subscribe.on('blockchain.scripthash.subscribe', console.log)
console.log("开始连接节点...")
await ecl.connect()
console.log("请求服务端版本...")
const version = await ecl.server_version()
console.log("返回版本数据:"+JSON.stringify(version))
console.log("服务端实现为:"+version[0]+"\r\n服务端支持的协议版本为:"+version[1])
console.log("开始订阅消息")
const p1 = await ecl.serverPeers_subscribe()
const p2 = await ecl.blockchainHeaders_subscribe()
const p3 = await ecl.blockchainAddress_subscribe(address)
console.log("消息订阅完成,等待中...")
while(true){
await sleep(1000)
//心跳
const ver = await ecl.server_version()
}
await ecl.close()
}catch(e){
console.log("error")
console.log(e)
}
}
listen()

这就是消息监听了。

广播TX

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
const ElectrumClient = require('electrum-client')

const ecl = new ElectrumClient(50002, 'satoshi.vision.cash', 'tls')
const TX = "01000000013216d667c54d6d3f95b054084a17bacccb7cdcc43f1caf838b8a40183b62e2eb000000006a473044022020d11e4db8bf59d221e97d167d9fbf845d9f3ed094b33c9c7e458b06341ed23e02203b91f540d794e53856fa62d0f7b097bee47350db1cd73d14e4341d7b5e6b3c2b41210374d0fca0d78a13b8eb0edff0b2c434a7bd8ee96c9efc767489f3577928e67ad8feffffff0300000000000000000f6a0d54686973206973206120747279a0860100000000001976a91470d57479627e3b77847648a873dff945bfdeb95488ac263a9500000000001976a91461f61eb7233157bad8111b96066d9e0fed87029788ac47870800";

const broadcast = async (rawTX) => {
try{
console.log("开始连接节点...")
await ecl.connect()
console.log("请求服务端版本...")
const version = await ecl.server_version()
console.log("返回版本数据:"+JSON.stringify(version))
console.log("服务端实现为:"+version[0]+"\r\n服务端支持的协议版本为:"+version[1])
console.log("开始广播TX")
const result = await ecl.blockchainTransaction_broadcast(rawTX)
console.log("结果")
console.log(result)
console.log("关闭连接")
await ecl.close()
}catch(e){
console.log("错误")
console.log(e)
console.log("关闭连接")
await ecl.close()
}
}
broadcast(TX)

BSV库

Electrum-Client功能能满足一个SPV应用的消息首发要求。

不过构造和解析TX,还是要有专门的库进行,这个就是BSV库了。

但是今天先到这里。