BSV Planaria框架技术总结二 Bitquery

此文是变形虫技术总结的第二篇,阅读此文之前建议先阅读关于变形虫的前两篇文章。

Bitcoin SV的开发哲学——变形虫框架

BSV Planaria框架技术总结一 节点搭建

前面的文章说过变形虫是一个持久层框架,通过planaria组件爬取区块链上的交易数据,提取并加工所需的数据后将数据存储到MongoDB中,然后由planarium对外提供接口给应用程序来调取数据。在搭建好数据库之后,我们下一步关注的重点就是如何对数据库进行读写。前文已经介绍过,变形虫与传统数据库最大的区别就是读写分离,不能直接写数据库,只能通过客户端发起链上交易来修改状态机,因此向变形虫写数据本质上就是构造交易,这将在下一篇文章中总结。本文的重点就是如何高效优雅地从变形虫中读取数据。

Bitquery简介

planaria使用一套简单高效的查询语言,称为Bitquery,类似于sql语言,可以将变形虫中的数据进行各种图灵完备的组合和处理,输出各种形态的数据。由于变形虫的开发目的是一个可以满足一切基于比特币链上数据需求的持久层框架,因此必须具备一种专为链上交易数据服务的查询语言,bitquery也就因此而产生。

bitquery查询语句本质上是一个json对象,之所以是这种形态是为了适应MongoDB的查询。这里有必要提及一下,变形虫框架为何内置数据库采用的是MongoDB而不是类似mysql的关系型数据库。照理说,链上交易数据是充分结构化的,各个交易之间,地址之间有很强的关联性,查询交易更适合使用关系型数据库,这样连表操作更为方便,性能也更好。但是对于变形虫而言,它关注的重点并不是交易本身,而是交易上携带的千变万化,各行各业的杂乱无章的非标准数据,这些数据没有统一的范式,也没有统一的关联性。在这种情形下,Nosql的优势就会凸显出来,将一个个非标准数据看做一个个”文件“,不强制要求文件内容的格式,更加符合变形虫的需求目标。我有一种设想,以后的变形虫甚至可以在内部同时集成关系型和非关系型两种数据库,关系型数据库专门处理关联性高的交易数据,提升关联连表查询效率,而非关系型数据库处理各种应用的非标准数据,提升拓展性和兼容性。两种内置的数据库组件各司其职,将变形虫的性能发挥到极致。

bitquery示例如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PipQL7QC-1594465117826)(https://docs.planaria.network/bitquery.png)]

bitquery基于两种非常强大的技术,MongoDB Query Language(mongoDB 查询语言)和JQ(一个基于栈操作的图灵完备的json处理语句)。查询语句如上图所示分为3部分,第一部分是协议的版本号,目前是3,第二部分是基于MongoDB的查询语句,筛选出数据,第三部分是返回结果处理语句,对第二部筛选出来的结果进行整理和包装,处理成应用程序需要的样式。

第二部分查询语句主要基于MongoDB的查询语句,但是与原生的MongoDB查询略有不同,针对Planaria做了一定的适配,选取了部分查询功能,使用json对象的各个成员来标记搜索条件。

第三部分处理搜索结果,第二步返回的结果是一个json,可能包含我们不需要的数据,或者不是我们所希望的数据格式,需要将其整理成我们需要的格式,这时候就需要用到JQ,变形虫内置JQ的处理组件,可以直接帮我们把数据在服务器端就处理好,不需要客户端自行进行数据的处理。

query字段(q)可以包含下列成员(后文会依次详细说明):

  • find:同MongoDB的find条件语句。
  • aggregate:对应MongoDB的聚合语句。
  • project:对应mongoDB的project操作符
  • sort:对应MongoDB的排序操作符
  • limit:对应MongoDB的个数限制操作
  • skip:对应跳过操作,通过limit和skip可以实现翻页
  • db:选择db,目前有c(已确认交易库)和u(未确认交易库)

response字段(r)包含一个f成员,就是后处理function,里面是一个JQ表达式,用于处理query返回的结果。

Genesis数据储存格式

目前我运行的是一个全量的Genesis数据库,因此在这里详细说明一下返回值的字段含义,如果不知道这些字段的含义,也就无从写query语句和Jq表达式了。没有节点的同学可以直接访问Unwriter的公开endpoint:

genesis公开Endpoint

打开后会看到默认的查询语句如下:

{"v": 3,"q": {"find": {},"limit": 10}
}

这语句的意思是取最近的十条记录,没有其他条件。然后我们会看到下方有一个巨大的表格,里面填充了各种数据,并且有一些奇怪的表头名称。为了方便描述,我们修改一下查询语句,因为默认查询语句经常会查出一些巨大的交易数据体(二进制图片等数据),在本文有限的空间下没办法展示出来,我们采用如下的查询:

{"v": 3,"q": {"find": { "out.h1": "6d02" },"limit": 1}
}

转换成get请求如下:

GET /q/1FnauZ9aUH2Bex6JzdcV4eNX7oLSSEbxtN/ewogICJ2IjogMywKICAicSI6IHsKICAgICJmaW5kIjogeyAib3V0LmgxIjogIjZkMDIiIH0sCiAgICAibGltaXQiOiAxCiAgfQp9 HTTP/1.1
Host: genesis.bitdb.network
key: 1KWqy2WbNpEPC7hwvfJbvXy2vekS2LwGim
Cache-Control: no-cache
Postman-Token: 6445346e-1444-8450-4cb5-088197fe921e

这个语句的意思是取出最近的一条memo.sv(一个bsv链上微博)的交易数据,原理我们后文介绍。

可以看到这个查询语句返回了一系列数据,填充在表格中,我们将其json源码(从postman直接请求)贴下来,如下:

{"u": [],"c": [{"_id": "5ccfe65cd7faee16774712b0","tx": {"h": "15ec948360c54b0864fed10addc9370e7acf70624bae3e46f0db16ba33728e60"},"in": [{"i": 0,"b0": "MEQCIGbAWT8cYm+sHj+/kJI6Ir7HznAZl+uI/Op1w119nyBWAiAmaWlVHAw7F1lPjkremq5YfDyuGr2bMQC7Ft2/uUwor0E=","b1": "A+8emT7UM3oOjjqstBg4sjn2I4wnIPPwY7ARQ6VkrGue","str": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f20560220266969551c0c3b17594f8e4ade9aae587c3cae1abd9b3100bb16ddbfb94c28af41 03ef1e993ed4337a0e8e3aacb41838b239f6238c2720f3f063b01143a564ac6b9e","e": {"h": "175553b769f70d93b6c3961e8bdefe20ab274f6bbb8bbbcbfbbe205fb347a610","i": 0,"a": "1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"},"h0": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f20560220266969551c0c3b17594f8e4ade9aae587c3cae1abd9b3100bb16ddbfb94c28af41","h1": "03ef1e993ed4337a0e8e3aacb41838b239f6238c2720f3f063b01143a564ac6b9e"}],"out": [{"i": 0,"b0": {"op": 118},"b1": {"op": 169},"b2": "rjVo4QocscdXU/oca7cRtfhv+8E=","s2": "\ufffd5h\ufffd\n\u001c\ufffd\ufffdWS\ufffd\u001ck\ufffd\u0011\ufffd\ufffdo\ufffd\ufffd","b3": {"op": 136},"b4": {"op": 172},"str": "OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG","e": {"v": 26870,"i": 0,"a": "1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"},"h2": "ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1"},{"i": 1,"b0": {"op": 106},"b1": "bQI=","s1": "m\u0002","b2": "QSBCaXRjb2luZXIgaW4gdGhlIHN3YXJtIG9mIHNwZWN1bGF0b3JzLi4uCgpodHRwczovL2kuaW1ndXIuY29tLzRNU0xZVW8ubXA0CgojQml0Y29pbg==","s2": "A Bitcoiner in the swarm of speculators...\n\nhttps://i.imgur.com/4MSLYUo.mp4\n\n#Bitcoin","str": "OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e","e": {"v": 0,"i": 1,"a": "false"},"h1": "6d02","h2": "4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e"}],"blk": {"i": 581188,"h": "000000000000000005cf5c75cdd7f55b5e772e0d751dd0f4a60076702af5768f","t": 1557128792}}]
}

然后我们根据上面的返回值,介绍一下返回值的各个字段含义。原版的文档请参考如下链接:

Bitdb Indexer

根路径的u和c代表两个不同的数据库collection,u是unconfirmed tx,未确认交易,存在于内存池中。c是confirmed tx,已经确认的交易,存在于区块链中。

然后列表中的每一个对象都是一个交易,里面记载了交易的详情。

对于交易层面而言,其结构如下:

{"tx": {"h": [交易 HASH]},"blk" {"i": [区块高度],"h": [区块HASH],"t": [区块时间戳]},"in": [INPUT1,INPUT2,INPUT3,...],"out": [OUTPUT1,OUTPUT2,OUTPUT3,...]
}

对多输入多输出的交易,in和out分别是这笔交易输入input和输出output的列表。

然后我们说输入输出脚本的层面,也就是上面input和output的每一个具体的对象,建议看这部分之前先了解比特币脚本的工作原理。

这是从上面的output中摘取出来的一个输出脚本

{  "i":0,"b0":{  "op":118},"b1":{  "op":169},"b2":"rjVo4QocscdXU/oca7cRtfhv+8E=","s2":"\ufffd5h\ufffd\n\u001c\ufffd\ufffdWS\ufffd\u001ck\ufffd\u0011\ufffd\ufffdo\ufffd\ufffd","b3":{  "op":136},"b4":{  "op":172},"str":"OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG","e":{  "v":26870,"i":0,"a":"1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"},"h2":"ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1"
}

我们知道,一个标准的P2PKH(Pay To Pubkey Hash)交易的输出脚本(也称为锁定脚本)如下所示:

OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG

这个脚本可以根据空格拆成5个数据或者操作码块,在校验比特币交易时,5个块依次进栈参与运算。我们按照顺序将其依次标记为0,1,2,3,4。

然后我们介绍上面的json的字段:

  • i:指这个输出(或输入)在交易中的标号index,是第几个输出,0是第一个
  • b0 b1 b2 b3 b4:是上述5个块的base64编码,如果是操作码,比如OP_DUP或者OP_HASH160,则储存其操作码编号,比如b0对应OP_DUP,其操作码编号是118,因此b0对应的就是op:118。如果不是操作码,是数据,则储存数据的Base64编码。(比特币操作码编号一栏)
  • s0 s1 s2 s3 s4:是上述5个脚本块的UTF8字符串,由于标准脚本只有s2是数据,其他都是操作码,因此此处只返回了s2,操作码不储存成字符串
  • h0 h1 h2 h3 h4:是上述5个脚本串的十六进制(hex)编码,同样由于标准脚本只有h2是数据,是会变化的,因此此处只返回h2,操作码不储存hex
  • str:脚本原文
  • e:edge,边界,对于输入脚本,edge储存了输入脚本的来源交易,对于输出脚本,edge储存了这个utxo将花向何处
  • e(输入脚本中):h表示这个输入的来源交易的交易hash。i是作为来源的utxo在上一笔交易中的index。a是来源交易的发起人地址。
  • e(输出脚本中):v是输出的金额satoshis,i是位于输出的位置index,a是输出花出去的目标地址(如果该输出已经被花费)

根据上面的解释,我们可以套用相同的模式来解析第二个输出:

{  "i":1,"b0":{  "op":106},"b1":"bQI=","s1":"m\u0002","b2":"QSBCaXRjb2luZXIgaW4gdGhlIHN3YXJtIG9mIHNwZWN1bGF0b3JzLi4uCgpodHRwczovL2kuaW1ndXIuY29tLzRNU0xZVW8ubXA0CgojQml0Y29pbg==","s2":"A Bitcoiner in the swarm of speculators...\n\nhttps://i.imgur.com/4MSLYUo.mp4\n\n#Bitcoin","str":"OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e","e":{  "v":0,"i":1,"a":"false"},"h1":"6d02","h2":"4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e"
}

上面的输出是一个op return输出,也是变形虫关注的重点。根据上面对字段的解释,我们可以看出,这个输出脚本有三个块,OP_RETURN,6d02,以及后面的数据。

OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e

这其实是一个标准的memo.sv协议的op return格式。开头的OP_RETURN是操作码,所以s0,h0没有储存,h1是6d02的hex,6d02声明了后面的内容属于memo.sv协议,再后面的内容就是hex化的数据体,关于bit协议的声明(6d02的来源和改良方案),可以参考bitcom协议规范,Ifwallet的bibodeng对此专门有讲解:

Unwritter发布的Bitcom协议是什么?

至此,我们详细介绍了变形虫中储存交易的格式,之后就是通过操作MongoDB以及JQ来查询和处理我们所需要的数据。

查询语句

然后我们研究Bitquery的query部分,查询体。

query部分的本质是查询MongoDB,而且由于Planaria只可读,在使用它的时候我们只需要关注读取语句(当然如果你要自定制planaria.js,需要研究MongoDB的写入操作,这不在本文的讨论范畴内,以后专门写)。因此我们需要先对mongoDB的find操作的一些基本概念进行介绍,官方文档如下:

MongoDB的Find操作

find语句传入一个json对象,json对象中的每一个字段名代表要指定的字段名,字段的值代表搜索条件,多个字段可以并列,相当于sql语句中的and条件。

这里由于篇幅不够,详细的操作无法全部说明,就挑一些官网的典型示例来说明一下。

1.匹配相等,index为5,姓是Hopper

db.bios.find( { _id: 5 } )db.bios.find( { "name.last": "Hopper" } )

2.匹配范围或正则表达式

db.bios.find({ _id: { $in: [ 5, ObjectId("507c35dd8fada716c89d0013") ] } }
)db.bios.find( { birth: { $gt: new Date('1950-01-01') } } )db.bios.find({ "name.last": { $regex: /^N/ } }
)

3.匹配多个条件(相当于and)

db.bios.find( {birth: { $gt: new Date('1920-01-01') },death: { $exists: false }
} )

4.匹配多个条件之一(相当于or)

db.col.find({$or: [{key1: value1}, {key2:value2}]}
)

5.匹配非(相当于!)

db.col.find({"likes":{$ne:50}})

6.aggregate聚合函数(相当于count)
聚合需要一些聚合操作,这些操作又称为管道pipeline,MongoDB将这些管道内的操作(称为stage)处理完成后,将结果聚合在一起。管道的操作列表及其说明参照下文:

Aggregation Pipeline Stages

例如,数据源如下

{ _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 }
{ _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 }
{ _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 }
{ _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 }
{ _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }

使用聚合操作将它们聚合并算出个数

db.orders.aggregate([{ $match: { status: "A" } },{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } },{ $sort: { total: -1 } }])

管道的操作是,首先选取status为A的,然后将它们按照cust_id字段进行分组,并统计出每组的amout总和,最后按照总和倒序排列。最后的返回结果如下:

{ "_id" : "xyz1", "total" : 100 }
{ "_id" : "abc1", "total" : 75 }

7.project,或projection操作,指定返回的字段,指定返回则设置为1,指定不返回则设置为0

例如:

const cursor = db.collection('inventory').find({status: 'A'}).project({ item: 1, status: 1 });

在inventory表中选取status为A的记录,并返回_id,item,和status,3个字段。

8.sort操作,指定排序方式,按照哪个字段排序,正序为1,倒序为-1

例如:

db.orders.find().sort( { amount: -1 } )

在orders表中选取全部,并按照amount倒序排列。

9.limit操作,指定返回的限制个数

按照query查找,并限制返回number个

db.collection.find(<query>).limit(<number>)

10.skip操作,指定跳过的个数offset

function printStudents(pageNumber, nPerPage) {print( "Page: " + pageNumber );db.students.find().skip( pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0 ).limit( nPerPage ).forEach( student => {print( student.name );} );
}

上面是一个配合limit使用的翻页逻辑,跳过前offset个记录

至此,我们已经简单介绍了query部分可能涉及到的一些查询操作,更多高级用法请参阅MongoDB的使用规范和文档,本文不赘述。

query部分示例与详解:

{"v": 3,"q": {"find": {"$text": {"$search": "hello"},"out.h1": "6d02","out.b2": "hello"},"skip": 5,"limit": 10,"sort": { "blk.i": 1 }}
}

上述的查询条件简单说明:搜索字段中是文本类型且带有hello的,而且out.h1字段等于6d02,而且out.b2字段等于hello的所有记录,跳过前5个,限制取10条记录,并根据blk.i(区块高度)正序排列。

JQ json后处理语句

在上一步获取到查询结果后,mongoDB返回的是一个json,如果我们希望对这个json进行一些后处理,就可以使用bitquery提供的另一个强大的工具,JQ后处理。

JQ是一个轻量级,图灵完备的堆栈式命令行Json处理工具。官方网站如下:

JQ官网

bitquery集成了JQ,并将其写在请求json的第三个部分,r.f字段。如下所示

{"v": 3,"q": {"find": { "out.h1": "534c5000", "out.s3": "GENESIS" },"limit": 20,"project": { "out.$": 1, "_id": 0 }},"r": {"f": "[.[] | .out[0] | { token_symbol: .s4, token_name: .s5, document_url: .s6} ]"}
}

f的一长串值就是JQ表达式,上述示例的含义就是取出所有query返回值中的out[0],然后将其中的s4拆出来作为字段token_symbol的值,将s5拆出来作为字段document_url的值,并返回一个新的列表。

查询结果如下所示:

{"u": [{...}],"c": [{"token_symbol": "TEST","token_name": "TEST","document_url": "bitcoinfiles:b86b4bcbab7cd787b1c893ca101250c8c467dbba4df229b118218bd8a9e85a92"}, {"token_symbol": "VOTE","token_name": "An Election","document_url": "bitcoinfiles:a90e59ef7ca66b25b6ba98d028198ae222a8229804c4b0b3bc0b1bafe104738a"}, {"token_symbol": "WuCash","token_name": "Wu Tang Cash","document_url": "http://wu.cash"}, {"token_symbol": "bb23n","token_name": "bb23","document_url": "bb23n.com"}, {"token_symbol": "DBOOK01","token_name": "Digital Book Example","document_url": "https://digitalbookexampletokenurl.com"}, {"token_symbol": """token_name": """document_url": ""}, {"token_symbol": "MTT","token_name": "MyTestToken","document_url": ""}]
}

jq的使用方法unwriter简单概括如下:

  • 所有jq表达式都是基于堆栈的,因此从左向右阅读
  • 所有的表达式都假设传入一个json对象,并最后输出一个对象
  • 管道表达式类似于unix的管道,它将处理结果从左到右依次传递下去

我们这里受篇幅的限制,简单描述一下一些常用的jq语法,英文好的朋友可以直接参照JQ官网提供的手册来学习语法,当然也可以参考下面的中文博客,介绍的很详细,并且可以使用里面的jq play 在线校验表达式。

jq 常用操作

我们这里列举几个比较常用的语法:

1.获取object下某个字段的值,也可以在其后增加?,表示如果不存在也不会报错

.key, .foo.bar, .["key"].key?, .foo.bar?, .["key"]?

2.获取所有的值

.[]$ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq .[]
"mozillazg.com"
"mozillazg"

3.构造新数组,比如将上面所有的值组成新的数组

[.[]]$ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq [.[]]
["mozillazg.com","mozillazg"
]

4.切分数组,选取数组的某一项

.[1]  .[0:2]$ echo '[{"name": "tom"}, {"name": "mozillazg"}, {"name": "jim"}]' |jq .[1]
{"name": "mozillazg"
}
$ echo '[{"name": "tom"}, {"name": "mozillazg"}, {"name": "jim"}]' |jq .[0:2]
[{"name": "tom"},{"name": "mozillazg"}
]

5.使用多个filter,依次执行

如下面依次执行选取url的值,选取name的值,所以打印了两个值出来

,$ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq '.url, .name'
"mozillazg.com"
"mozillazg"

6.管道操作符,类似于unix的管道操作,熟悉linux操作的一定不陌生

Pipeline (Unix)

相当于对管道前后的条件依次过滤,最后输出结果,如下面的语句,就是先选取json原文,然后再从中过滤出url的值

|$ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq '.|.url'
"mozillazg.com"

我们这里做一个简单的bitquery操作示例(unwriter的网站给出的),并稍微解释一下,更高级和深入的用法还需要多加研究jq的文档。

{"v": 3,"q": {"find": { "out.h1": "6d02" },"limit": 10},"r": {"f": "[{ block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]"}
}

上述的jq表达式的意思是将每个结果的以下3个数据提取出来,构成一个新的对象列表。细心的朋友可能运行上述bitquery表达式会报错,我们仔细研究一下,发现确实有问题(unwriter大神也会犯错哈)。query部分返回的直接是个数组,在数组中选择.blk就会直接报错。这点我们通过变形虫执行上述命令的日志可以看出来错误原因:

Query =  { find: { 'out.b1': 'bQI=' }, limit: 2 }
before transform =  [ { _id: 5cd041b74171e816cb23c05b,tx: { h: 'a1d58e3053bb0aea6c208eca40ba6299fc9e770fb74cfacac4cf90dcc92efbaa' },in: [ [Object] ],out: [ [Object], [Object] ],blk: { i: 581226,h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',t: 1557152175 } },{ _id: 5cd041b74171e816cb23c17a,tx: { h: '0982091b7923b82bf3c64fe17a9e72bcb099d72388e30ce1ef1a43efff405628' },in: [ [Object] ],out: [ [Object], [Object] ],blk: { i: 581226,h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',t: 1557152175 } } ]
after transform =  [ { _id: 5cd041b74171e816cb23c05b,tx: { h: 'a1d58e3053bb0aea6c208eca40ba6299fc9e770fb74cfacac4cf90dcc92efbaa' },in: [ [Object] ],out: [ [Object], [Object] ],blk: { i: 581226,h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',t: 1557152175 } },{ _id: 5cd041b74171e816cb23c17a,tx: { h: '0982091b7923b82bf3c64fe17a9e72bcb099d72388e30ce1ef1a43efff405628' },in: [ [Object] ],out: [ [Object], [Object] ],blk: { i: 581226,h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',t: 1557152175 } } ]
running jq
STR =
error jq SyntaxError: Unexpected end of JSON inputat JSON.parse (<anonymous>)at Socket.child.stdout.on (/app/node_modules/bigjq/index.js:17:29)at emitNone (events.js:111:20)at Socket.emit (events.js:208:7)at endReadableNT (_stream_readable.js:1064:12)at _combinedTickCallback (internal/process/next_tick.js:139:11)at process._tickCallback (internal/process/next_tick.js:181:9)
query =  { v: 3,q: { find: { 'out.b1': 'bQI=' }, limit: 2 },r: { f: '[{ block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]' } }
response =  { errors: [ 'SyntaxError: Unexpected end of JSON input' ] }

可以从日志中看到,这段命令错误的原因在于对列表[]进行值选择.blk,那么根据jq的命令,正确的写法应该如下所示:

{"v": 3,"q": {"find": { "out.h1": "6d02" },"limit": 10},"r": {"f": "[.[] | { block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]"}
}

我把此处修改为首先获取列表中的每一个对象,对每个对象进行管道操作,提取其中的blk.i,blk.t 和s2作为3个新的字段的值,组成一个新的对象列表。这样就可以返回正确的答案了(谁认识Unwriter麻烦把这个文档中的小错误告知他)。

感兴趣的朋友可以在浏览器测试一下上面前后两个bitquery,来验证一下是否能正确执行。

正确的结果应该如下所示(篇幅限制这里limit 3)

{"u": [],"c": [{"block": 581226,"timestamp": 1557152175,"content": "capitalism has no answers when it comes to the peoples' interests."},{"block": 581226,"timestamp": 1557152175,"content": "Facebook just won't stop! They are now planning to have their own coin. http://bit.ly/2DSKT6j\nWill this be a good news or bad for the crypto market? That link is from Cointelegraph."},{"block": 581224,"timestamp": 1557150641,"content": "Time for another change of topic for my next Monday Memo. Maybe something to do with recreation. Yes, that'll do.\n\nIf you're into hiking like I am, try taking new paths or trails from time to time."}]
}

结语

这些技术文档是我自己学习变形虫协议的时候的学习总结,希望分享给各位开发者。我也希望更多开发者能够了解bsv所能实现的强大功能,多多参与进来,共同构建出更多好玩的应用,让区块链技术能够真正落地,不要仅仅停留在炒币和嘴炮上。

写这些文章是会花费很多时间,也需要查阅很多资料,甚至需要自己亲自去一次次的真机实验,但是我相信这并不是无用功,通过写这些技术文档,我对这些技术的了解更为深入,也学习到很多思想和方法,我相信比特币是proof of work,只有真正脚踏实地地work,才能让比特币的价值得以沉淀,让比特币的精神真正发扬下去。欢迎各位技术大神交流指教。

BSV Planaria框架技术总结二 Bitquery相关推荐

  1. BSV Planaria框架技术总结一 节点搭建

    BSV Planaria框架技术总结一 节点搭建 变形虫Planaria是Unwriter大神基于bitdb在bsv链上开发的一个可编程化的持久层框架,关于变形虫的特点和编程思想,可以参考我之前的文章 ...

  2. 19 Go Web 框架(二):框架技术详解

    一. net/http包够用吗? Go的net相关标准包提供web开发的大多数实现支持,如果你的项目的路径端点在十个以内,如一个简单的企业网站,这当然是够用的.但如果你的项目是构建一个大型电商网站,有 ...

  3. DL动态加载框架技术

    转载自:http://blog.csdn.net/singwhatiwanna/article/details/40283117 DL动态加载框架技术文档 1. Android apk动态加载机制的研 ...

  4. (转)MyBatis框架的学习(二)——MyBatis架构与入门

    http://blog.csdn.net/yerenyuan_pku/article/details/71699515 MyBatis框架的架构 MyBatis框架的架构如下图:  下面作简要概述: ...

  5. 苹果框架学习(二) Metal

    文章目录 苹果框架学习(二) Metal Metal简介 1. Essentials 1.1 基本任务和概念 1.2 将OpenGL代码迁移到Metal 1.3 将您的Metal代码移植到苹果Arm芯 ...

  6. 大数据开源框架技术汇总

    主要基于对现阶段一些常用的大数据开源框架技术的整理,只是一些简单的介绍,并不是详细技术梳理.可能会有疏漏,发现再整理.参考得太多,就不一一列出来了.这只是作为一个梳理,对以后选型或者扩展的做个参考. ...

  7. Android MVVM框架搭建(二)OKHttp + Retrofit + RxJava

    Android MVVM框架搭建(二)Retrofit + RxJava 前言 正文 一.引入依赖 二.工具类 三.构建网络框架 1. Base 2. 异常处理 3. 拦截器 4. 网络请求服务 四. ...

  8. 安卓刷量技术揭秘(二) 高级攻防技巧

    标 题: [原创]安卓刷量技术揭秘(二) 高级攻防技巧 作 者: ckis 时 间: 2016-10-02,00:28:27 链 接: http://bbs.pediy.com/showthread. ...

  9. DL动态加载框架技术文档

    DL动态加载框架技术文档 DL技术交流群:29969245 1. Android apk动态加载机制的研究 2. Android apk动态加载机制的研究(二):资源加载和activity生命周期管理 ...

最新文章

  1. JVM运行时数据区---方法区(演变和垃圾回收)
  2. Lua脚本语言快速入门手册
  3. 基于matlab的智能天线波束方向图仿真,基于MATLAB的智能天线波束方向图仿真
  4. [20161031]rman备份与数据文件OS块.txt
  5. 为什么要用python不用origin_Python告诉你为什么百度已死
  6. Introduction to Real-Time Kernels
  7. Ergo生态:首个算法稳定币SigmaUSD正式启动
  8. html出现滚动条页面闪动,CSS3 calc实现滚动条出现页面不跳动闪动
  9. 【C++】快速幂与矩阵快速幂
  10. linux 创建mbr分区,MBR分区类型简介
  11. 平方米的计算机公式,计算平方的公式 各类计算公式
  12. Mr.Go 会客厅第二期,B站+斗鱼“后浪” Gopher 火花四溅!
  13. php 设置数字键盘,window_win10启动时如何设置默认打开小键盘数字输入切换键?,win10作为微软的一次重大变革 - phpStudy...
  14. 计算机主机组成成分,手机电脑芯片主要由什么物质组成
  15. (一)框架搭建,前端路由设置,自定义寻找指定路径(Django+Vue+Mysql,数据库管理数据分析网站)
  16. 早该把B站打回原形了
  17. Qt Charts 动态实时折线图绘制
  18. 蚂蚁金服原副总裁漆远加盟复旦大学,任人工智能院长
  19. 楚纳-解决电动车进电梯方案
  20. 一篇文章全面了解Shopify开店

热门文章

  1. Processing 案例 | 字母在宠物小精灵上翩翩起舞
  2. 黑盒和白盒测试方法详解
  3. 嵌入式Linux(一):东山派壹号 ubuntu开发环境配置
  4. 科恩兄弟《谋杀绿脚趾》
  5. 说一下减少dom的办法?一次性给你大量的dom怎么优化?
  6. 03-HTML-图片标签
  7. 低版本Mac OS安装合适xcode的方法
  8. 因素负荷量(factor loadings)的理解
  9. 元宇宙概念的一次现实投影,北京冬奥圆满谢幕
  10. 空冥决 页游 一键端 架设 自玩