开发
JSON-RPC 接口
Ksana 节点与索引器 JSON-RPC 接口的每一个方法,附 JSON 约定、错误,以及对公开 signet 的真实调用。
本页目录
Ksana 节点在同一个端口上通过 HTTP 和 WebSocket 应答 JSON-RPC 2.0 请求。索引器则应答它自己的两个方法。钱包、应用、ksana-cli 和 TypeScript SDK 用的都是这套接口。
本页的每个示例,都是 2026 年 10 月 14 日对公开 signet 发出的真实调用。主网尚未上线。
端点
| 服务 | 默认地址 | 公开 signet |
|---|---|---|
| 节点,HTTP | 127.0.0.1:8545(ksana-node --rpc-addr) |
https://signet.ksana.network/rpc |
| 节点,WebSocket | 同一个端口 | wss://signet.ksana.network/rpc |
| 索引器,HTTP | 127.0.0.1:8546(ksana-indexer --rpc-addr) |
https://signet.ksana.network/indexer |
调用的参数按位置传递:params 是一个 JSON 数组,参数的顺序就是下文各节列出的顺序。请求头要带上 content-type: application/json。
从网页调用
浏览器只在服务器允许时才会调用另一个来源(CORS)。节点默认不允许任何来源;启动时加上 --rpc-cors '*' 就允许所有来源,也可以给它一个以逗号分隔的来源列表。命令行工具和服务器不受影响。
在公开 signet 上,节点的 RPC 允许所有来源,索引器则一个也不允许。发一个预检请求就能看出区别:
curl -sS -i -X OPTIONS https://signet.ksana.network/rpc -H 'origin: https://example.org' \
-H 'access-control-request-method: POST' -H 'access-control-request-headers: content-type' \
| grep -i -E '^(HTTP/2|access-control)'HTTP/2 200
access-control-allow-headers: content-type
access-control-allow-methods: POST,GET,OPTIONS
access-control-allow-origin: *同样的请求发给 https://signet.ksana.network/indexer,得到的是 HTTP/2 405。请从服务器或命令行调用索引器。
第一次调用
向节点查询它的链顶:
curl -sS https://signet.ksana.network/rpc -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"ksana_getChainInfo","params":[]}' | jq{
"jsonrpc": "2.0",
"id": 1,
"result": {
"chain_id": 1002,
"tip": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
},
"daa_score": "12452",
"protocol_version": 2,
"next_upgrade": null,
"max_known_version": 2,
"finalized": {
"height": "246",
"hash": "c45e4b58173b529c164e66c61219befe4bf7b3e93c319f9c80dbd98982793071"
}
}
}其余示例都用一个简短的 shell 函数,只打印 result。这个函数需要 curl 和 jq:
NODE=https://signet.ksana.network/rpc
INDEXER=https://signet.ksana.network/indexer
rpc() { # rpc <url> <method> [JSON 数组形式的参数]
curl -sS "$1" -H 'content-type: application/json' \
--data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$2\",\"params\":${3:-[]}}"
}
ACCOUNT=d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf
KSA=0000000000000000000000000000000000000000000000000000000000000000ACCOUNT 是代币与资金池教程里用的账户,KSA 是 KSA 的资产 ID。
JSON 约定
- 字节串(哈希、账户、资产 ID 和合约 ID、Borsh 编码)写成小写十六进制字符串,不带
0x。 u64和u128值(高度、nonce、金额、gas)是十进制字符串,因为 JavaScript 的数字只有 53 位精度。u8和u32值(小数位数、BTC 高度、计数)是 JSON 数字。- 区块标识是
"latest"、一个十进制高度(例如"268"),或者一个 64 位十六进制的区块哈希。高度指的是主链上的区块;哈希可以指节点知道的任何区块。 - 比特币的 ID(
txid、BTC 区块哈希)按比特币的内部字节序书写,与区块浏览器显示的顺序相反。以_rpc结尾的字段按区块浏览器的顺序给出。 - 协议类型(区块头、条目、VmOp)采用仓库测试向量中的 JSON 形式:枚举是只有一个键的对象,这个键就是它的变体,例如
{"Token": {"amount": "1"}}。
方法一览
| 方法 | 参数 | 结果 |
|---|---|---|
ksana_getChainInfo |
无 | 链 ID、链顶、DAA 分数、版本、最终链顶 |
ksana_estimateFee |
无 | 建议的路径 B 手续费,以及节点的 BTC 费率 |
ksana_getAccount |
账户 | nonce、余额、锁定的手续费 |
ksana_getAsset |
资产 ID | 名称、小数位数、总量、发行者 |
ksana_submitOp |
VmOp(Borsh 编码,十六进制) | 操作 ID |
ksana_getOpStatus |
操作 ID | 状态、区块、回执 |
ksana_getAccountOps |
账户、before、limit |
一个账户的一页操作 |
ksana_getLatestOps |
before、limit |
全链的一页操作 |
ksana_getBlock |
区块标识 | 区块头、条目、回执 |
ksana_getSettlement |
区块标识 | 结算及其在比特币上的状态 |
ksana_call |
调用请求 | 一次只读调用的回执 |
ksana_subscribeBlocks |
无(WebSocket) | 每个新区块一条通知 |
indexer_status |
无 | 索引器已核对的链顶 |
indexer_stateRoot |
区块标识 | 一个已核对区块的状态根 |
链与手续费
ksana_getChainInfo
没有参数。结果如第一次调用所示:
| 字段 | 含义 |
|---|---|
chain_id |
signet 上为 1002,devnet 上为 1001,主网上为 1000 |
tip |
主链的链顶,{ "height", "hash" } |
daa_score |
链顶的 DAA 分数;每个区块加上自己的权重(空块的权重更大) |
protocol_version |
链顶的区块头版本 |
next_upgrade |
下一次排定的升级,{ "version", "daa_score" },或 null |
max_known_version |
本节点软件支持的最高协议版本 |
finalized |
最终链顶:主链必须经过它,只有比特币重组才能把它撤回 |
网络的 protocol_version 一旦超过 max_known_version,节点的软件就需要升级。在任何区块成为最终之前,finalized 是创世块。
ksana_estimateFee
没有参数。它给出手续费的建议值,不改变节点接受操作的条件。
rpc $NODE ksana_estimateFee | jq .result{
"feerate_sat_vb": "2",
"path_b": {
"amount": "1",
"per_kib": "1"
},
"at": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
}
}feerate_sat_vb是本节点眼下为自己的结算支付的费率,单位 sat/vB。路径 C 交易和充值可以拿它作参考。path_b.per_kib是路径 B 的建议费率:每 KiB 成本多少个 KSA 最小单位。一笔操作的成本,是它编码后的大小与它的max_gas换算成的字节数(max_gas × 81,920 / 100,000,000)两者中较大的那个。节点在它最近的 20 个主链区块中,取每个区块最低的路径 B 费率,再取这些费率的中位数,而且从不低于它自己的--min-fee-per-kib。path_b.amount是按这个费率算出的一笔普通 KSA 转账的建议手续费,不会低于节点的--min-token-fee。对于别的操作,用它的成本乘以per_kib。at是这次估算所依据的链顶。
操作与手续费一页讲解手续费的规则。
账户与资产
ksana_getAccount
参数:账户,写成 64 位十六进制(即 Taproot 输出公钥,而不是 tb1p 地址)。
rpc $NODE ksana_getAccount "[\"$ACCOUNT\"]" | jq .result{
"account": "d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf",
"nonce": "10",
"balances": [
{
"asset": "0000000000000000000000000000000000000000000000000000000000000000",
"amount": "7475669406"
}
],
"locked_fees": [],
"at": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
}
}nonce是这个账户在主链上的下一个 nonce。还在条目池里等待的操作不计在内。balances列出每种内置资产的可用余额,以最小单位计。保存在合约存储里的代币(比如示例代币token)不会出现在这里:要向那个合约查询。locked_fees列出这个账户在尚未解锁的区块里待领的金额,每个区块一项,形如[{ "block_hash", "amount" }]:作为出块者,是该区块的奖励和路径 B 手续费;作为携带者,是携带者的那一份;作为区块分享账户,是区块分享。没有待领金额时,列表为空。at是这个回答所依据的区块。
ksana_getAsset
参数:资产 ID。KSA 的 ID 是 64 个零。
rpc $NODE ksana_getAsset "[\"$KSA\"]" | jq .result{
"asset": "0000000000000000000000000000000000000000000000000000000000000000",
"name": "KSA",
"decimals": 8,
"total_supply": "11011956230000000",
"issuer": "0000000000000000000000000000000000000000000000000000000000000000",
"at": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
}
}total_supply 以最小单位计:创世分配,加上到目前为止发放的出块奖励,再减去销毁的数额。issuer 是发行这种资产的合约,KSA 的则是全零。查询未知的资产,返回 -32004。
操作
ksana_submitOp
参数:一个签好名的路径 B VmOp,经 Borsh 编码,写成十六进制。结果:{ "op_id" }。
节点按共识规则(版本、链 ID、大小、签名、nonce 不低于账户的 nonce)和它自己的策略(最低手续费、容量)检查这个操作。出块的节点要等这个操作进了它的下一个出块模板才应答,最多等 5 秒。
- 路径 C 操作不走这个方法:它随一笔比特币交易传递,
ksana_submitOp会以-32010拒绝它。 - 违反共识规则的操作返回
-32010;被节点策略拒绝的操作(手续费太低、条目池已满、替换时没有提高手续费)返回-32011。 - 无法解码的字节返回
-32602:
rpc $NODE ksana_submitOp '["00"]' | jq .error{
"code": -32602,
"message": "VmOp decode failed: Borsh decode failed: Unexpected length of input"
}TypeScript SDK 和 ksana-cli 会替你构造、签名并编码操作。
ksana_getOpStatus
参数:操作 ID。示例查询的是一笔水龙头转账:
OP=cdfbd418ad44c47c8fe5a31ba885e9bf1de6c13b5a467e79f4b2b735fc54be97
rpc $NODE ksana_getOpStatus "[\"$OP\"]" | jq .result{
"status": "fast_confirmed",
"block": {
"height": "261",
"hash": "81a22b58a74d0d6223cfb8c3c53a3ae21d13f0311c5d155f84863b5ba7be8639"
},
"index": 0,
"receipt": {
"op_id": "cdfbd418ad44c47c8fe5a31ba885e9bf1de6c13b5a467e79f4b2b735fc54be97",
"success": true,
"error": null,
"gas_used": "10000",
"output": "",
"events": []
}
}status |
含义 |
|---|---|
pending |
在条目池里,还没进区块 |
fast_confirmed |
在主链的一个区块里,并且执行成功 |
failed |
在某个区块里,但执行失败:状态已回滚,手续费照扣 |
skipped |
只由一个被合并的区块收录,而且无法执行;不收手续费,操作可以回到条目池 |
btc_final |
执行成功,而且接受它的区块已经 BTC 最终 |
block.hash 是收录这个操作的区块。block.height 是接受它的主链区块的高度:被合并区块中的操作,在合并该区块的那个区块的高度生效。index 是这个条目在所在区块里的位置。未知的操作返回 -32004;确认与最终性讲解了这些状态背后的印记。
ksana_getAccountOps
参数:account、before(可选)、limit(可选)。它列出一个账户在主链上的操作,最新的在前:这个账户签署的操作、转给它的转账,以及它作为合约时收到的调用。
rpc $NODE ksana_getAccountOps "[\"$ACCOUNT\", null, 2]" | jq .result{
"ops": [
{
"op_id": "2f9117f181b5ea9b3269d91503bb564e77bd4eae54d8a6f48f4d6c77f1e1cc3d",
"status": "fast_confirmed",
"block": {
"height": "274",
"hash": "f337432055aaa71728d34ec078cc9b426654829790d8d39011e0ebd131b86a9a"
},
"index": 0,
"position": "274:0:0"
},
{
"op_id": "7e3c309278bbad5cc0f0c0dfcb55bbd3689cdced9d4410c704e2f09c89330026",
"status": "fast_confirmed",
"block": {
"height": "273",
"hash": "2b6f9218d05b361a38093e0425fdf4da8222fb0d8e93d8f8696ed8c4a192f60d"
},
"index": 0,
"position": "273:0:0"
}
],
"next": "273:0:0",
"at": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
}
}position是操作在主链上的位置,形如"height:slot:entry":接受它的主链区块的高度,它所在的区块在那个主链区块所接受的各区块中的位次,以及条目的序号。列表从最高的位置往下排。before是一个位置:这一页列出位于它之下的操作。要接着往下翻,就传入上一页的next;要最新的操作,就传null(或者什么都不传)。limit为 1 到 500,默认 50。- 列表到达创世块后,
next为null。在next不为null时,一页里的操作也可能少于limit,甚至一个都没有:要一直沿着next取下去。
下一页从 273:0:0 之下接着列出:
rpc $NODE ksana_getAccountOps "[\"$ACCOUNT\", \"273:0:0\", 2]" \
| jq -c '.result.ops[].position, .result.next'"272:0:0"
"271:0:0"
"271:0:0"列表包括路径 B 和路径 C 的操作,不包括解锁条目。版本早于这些方法的节点,或者操作索引还没建好的节点,返回 -32601。账户或位置的格式不对,或者 limit 超出范围,返回 -32602。
ksana_getLatestOps
参数:before(可选)、limit(可选)。列表与 ksana_getAccountOps 的相同,只是涵盖所有账户:
rpc $NODE ksana_getLatestOps '[null, 1]' | jq .result{
"ops": [
{
"op_id": "2f9117f181b5ea9b3269d91503bb564e77bd4eae54d8a6f48f4d6c77f1e1cc3d",
"status": "fast_confirmed",
"block": {
"height": "274",
"hash": "f337432055aaa71728d34ec078cc9b426654829790d8d39011e0ebd131b86a9a"
},
"index": 0,
"position": "274:0:0"
}
],
"next": "274:0:0",
"at": {
"height": "286",
"hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
}
}区块与结算
ksana_getBlock
参数:一个区块标识。结果包含 hash、header、entries、settlement、receipts、accepted_height 和 merged。为了简短,示例省略了 entries 和 receipts:
rpc $NODE ksana_getBlock '["268"]' | jq '.result | del(.entries, .receipts)'{
"hash": "571cef33ce79209d2064448531fb7759be191e5357039859445fef9154682502",
"header": {
"version": 2,
"height": "268",
"parents": [
"5f2117f25c23819c29a9be702b45c72d2d48a71d6d72094d4f58d6ee20fd3816"
],
"btc_anchor": {
"height": 326320,
"hash": "eae6f2a86febdd8ae21bee2da3902a07cb324648485c58944c708ddb0f000000"
},
"entries_root": "e4087b3dca5eef67e42073d133ab3969fb5c4306046870811059a0972567b793",
"state_root": "bd06368cf82d7ed80c172f1b4c283993941eaeaf2a10f20b05113564c567f77f",
"producer": "ccc06c3ce8d176b3c10138dd4e70873939a9c6dc05597f45aace147ef2c07d3d",
"timestamp": "1791980287",
"bits": 520436435,
"nonce": "638"
},
"settlement": null,
"accepted_height": "268",
"merged": []
}header.parents列出这个区块在区块 DAG 中的父块;btc_anchor是它锚定的比特币区块;producer是出这个区块的账户。entries列出区块的条目,每个条目都是只有一个键的对象:Sponsored(路径 B 操作)、Forced(路径 C 操作,附带携带它的btc_txid)或Unlock。receipts为每个条目给出一张回执,取自主链接受这个区块之时。error以skipped开头的条目没有执行。settlement是这个区块自带的提交交易和揭示交易,{ "commit", "reveal" },写成十六进制;区块不带结算交易时为null。accepted_height是接受这个区块的主链区块的高度:主链上的区块就是它自己的高度,被合并的区块是合并它的那个区块的高度,还没有主链区块接受它时为null。merged列出它合并的区块,顺序就是这些区块的条目执行的顺序。
区块 268 的条目,去掉了签名:
rpc $NODE ksana_getBlock '["268"]' | jq '.result.entries[0].Sponsored.op | del(.sig)'{
"version": 2,
"chain_id": 1002,
"account": "d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf",
"nonce": "3",
"fee": {
"Token": {
"amount": "1"
}
},
"max_gas": "1000000",
"action": {
"Call": {
"contract": "2d6e5edc46352b2931657479fce65fdbf01aafc5acc3de7c27660ce19dda8171",
"method": "add_liquidity",
"args": "00a0724e18090000000000000000000000f2052a010000000000000000000000",
"funds": [
{
"asset": "0000000000000000000000000000000000000000000000000000000000000000",
"amount": "5000000000"
}
]
}
}
}用密钥签名时,sig 是 {"Bip340": "<64 bytes>"};用钱包签名时,它是 {"Bip322Simple": "<bytes>"}。路径 B 的 fee 是 {"Token": {"amount"}},路径 C 的是 "BtcNative"。action 是 Deploy、Call 或 AssetTransfer。
ksana_getSettlement
参数:一个区块标识。它告诉你这个区块的数据是否已在比特币上。示例省略了 txs,即那两笔原始交易(十六进制的 { "commit", "reveal" }):
rpc $NODE ksana_getSettlement '["264"]' | jq '.result | del(.txs)'{
"block": {
"height": "264",
"hash": "429f785a8e71515aed13acad24285fa441a9975f929a98c89ea30b0939e25bd2"
},
"txid": "a7cae8b07c1c5c8fe1258dd89205d75b5d4c81219d9843bfb4f35508b9bc4ae8",
"txid_rpc": "e84abcb90855f3b4bf43989d21814c5d5bd70592d88d25e18f5c1c7cb0e8caa7",
"commit_txid": "ce91d3090919756230a46bcaf9195d375890e82b9229f8fb01f4014b7ea5c8ee",
"commit_txid_rpc": "eec8a57e4b01f401fbf829922be89058375d19f9ca6ba4306275190909d391ce",
"carrier_account": "ccc06c3ce8d176b3c10138dd4e70873939a9c6dc05597f45aace147ef2c07d3d",
"carrier": null,
"status": "confirmed",
"confirmed": {
"txid": "a7cae8b07c1c5c8fe1258dd89205d75b5d4c81219d9843bfb4f35508b9bc4ae8",
"height": 326322,
"block": "c48a4f4f1f7ce1e696becf01bbc5f15fdbf8ba7145b9badff31c7e7711000000",
"confirmations": 3
},
"settled": true,
"btc_final": false
}txid是揭示交易,它在 Taproot 见证里携带载荷;commit_txid是它所花费的提交交易。以_rpc结尾的字段按区块浏览器的顺序给出这两个 ID。status为pending(还没广播:区块有了 2 个快轨确认之后,结算才会发出)、broadcast或confirmed。confirmed描述第一笔把这个区块的数据写进比特币、并且已经确认的比特币交易,{ "txid", "height", "block", "confirmations" }。它可能是这个区块自己的结算、后来某个区块的结算,也可能是一次补发。- 接受这个区块的主链区块,连同它之前的每一个主链区块,数据都已在比特币上时,
settled为true;接受它的区块已经最终时,btc_final为true。 carrier_account是作为这个区块的携带者而获得报酬的账户;出块者自己留下这份奖励时为null。它按当前的比特币链和主链作答。
一个区块的区块体可以由后来某个区块的结算携带(批量结算)。这时它的 carrier 指明那个区块,各交易字段描述的是那个区块的结算;还没有区块携带它时,这些字段为 null,status 为 pending。空块或只含路径 C 操作的区块没有区块体要上链,所以它自己不带结算时,返回 -32004。
只读调用
ksana_call
参数:一个调用请求对象。它在最新的状态上运行一个合约方法,不收手续费,不用 nonce,也不写入任何东西:
| 字段 | 必填 | 含义 |
|---|---|---|
contract |
是 | 合约 ID |
method |
是 | 方法名 |
args |
是 | 参数,Borsh 编码,写成十六进制(没有参数时为 "") |
funds |
否 | 附带的资产,[{ "asset", "amount" }],模拟时从 caller 转出 |
caller |
否 | 模拟的调用者,默认是 32 个零字节 |
max_gas |
否 | 十进制字符串,至多 BLOCK_GAS_LIMIT(100,000,000,也是默认值) |
读取一个资金池的储备(教程里的资金池,(u128, u128),小端):
POOL=2d6e5edc46352b2931657479fce65fdbf01aafc5acc3de7c27660ce19dda8171
rpc $NODE ksana_call "[{\"contract\": \"$POOL\", \"method\": \"reserves\", \"args\": \"\"}]" \
| jq .result{
"op_id": null,
"success": true,
"error": null,
"gas_used": "45885",
"output": "be310d09810400000000000000000000583a7696000000000000000000000000",
"events": []
}附带的资产从 caller 的余额中转出,所以附带资产的模拟需要一个有余额的 caller。下面在这个资金池里报价兑换 1 KSA,不带 caller 就会失败,而失败的调用会把 max_gas 全部记为已用:
ARGS=${KSA}00e1f50500000000000000000000000001000000000000000000000000000000
rpc $NODE ksana_call "[{\"contract\": \"$POOL\", \"method\": \"swap\", \"args\": \"$ARGS\",
\"funds\": [{\"asset\": \"$KSA\", \"amount\": \"100000000\"}], \"max_gas\": \"500000\"}]" \
| jq .result{
"op_id": null,
"success": false,
"error": "attached funds transfer failed: insufficient balance",
"gas_used": "500000",
"output": "",
"events": []
}加上 "caller": "$ACCOUNT" 后,调用成功,并返回报价:
{
"op_id": null,
"success": true,
"error": null,
"gas_used": "144458",
"output": "f4513fcf2b0000000000000000000000"
}(这段输出省略了 events。)回执是 { "op_id", "success", "error", "gas_used", "output", "events" },与操作回执的结构相同,只是 op_id 为 null。output 是方法返回的字节,写成十六进制;每个事件是 { "contract", "data" }。
调用看到的是下一个区块的环境:高度是链顶的高度加一,时间戳和比特币锚定块则与链顶的相同。native_tx() 什么也不返回。max_gas 超过上限时,返回 -32602。一个节点同一时间最多运行 4 个调用;在其中一个结束之前,再来的调用都返回 -32009。
订阅
ksana_subscribeBlocks
只能通过 WebSocket 使用。应答是一个订阅 ID。此后,每个加入主链的区块,包括重组时接入主链的区块,都会作为一条 ksana_block 通知送达。要停止,就用这个订阅 ID 调用 ksana_unsubscribeBlocks。
下面这段 JavaScript 可以在浏览器里运行,也可以在 Node.js 22 及更高版本中运行(node subscribe.mjs)。它订阅区块,打印第一条通知,然后取消订阅:
const ws = new WebSocket("wss://signet.ksana.network/rpc");
const send = (id, method, params) =>
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
ws.onopen = () => send(1, "ksana_subscribeBlocks", []);
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(JSON.stringify(message, null, 2));
if (message.method === "ksana_block") {
// 这里一个区块就够了:取消订阅,等节点确认后再关闭连接
send(2, "ksana_unsubscribeBlocks", [message.params.subscription]);
}
if (message.id === 2) ws.close();
};
ws.onclose = (event) => console.log("closed", event.code);{
"jsonrpc": "2.0",
"id": 1,
"result": 2924681207070251
}
{
"jsonrpc": "2.0",
"method": "ksana_block",
"params": {
"subscription": 2924681207070251,
"result": {
"height": "287",
"hash": "881c3cbbe76d3f2f9ac4f35a8400a38e0d3bb9cded775b4d1e4acf844be386e0",
"entries": 1,
"timestamp": "1791982019"
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": true
}
closed 1005每条通知都是 { "height", "hash", "entries", "timestamp" }:entries 是区块的条目数,timestamp 以秒为单位。
跟不上的订阅者会跳过它错过的通知,接着接收后面的区块。不会自动重连:连接关闭后,要新开一个连接,重新订阅,再用 ksana_getBlock 读取错过的区块。在空闲的 signet 上,相邻两个区块可能相隔几分钟。
索引器的方法
索引器只凭 bitcoind 重建这条链,并核对每一个状态根。它的两个方法遵循同样的约定。
indexer_status
没有参数。
rpc $INDEXER indexer_status | jq .result{
"height": "276",
"hash": "e23875efc23a5c7807a8ebc424e9f74a292fbfdfbe5c6b0bc505ddb8719a373c",
"state_root": "7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d",
"btc_height": 326324,
"finalized": {
"height": "246",
"hash": "c45e4b58173b529c164e66c61219befe4bf7b3e93c319f9c80dbd98982793071"
},
"halted": null
}height、hash和state_root描述索引器已执行并核对过的最高区块。一个区块的数据已在比特币上、并且在索引器的主链上有了 2 个确认,或者它已经 BTC 最终,索引器才会核对它,所以索引器落后于节点。btc_height是它已扫描到的比特币链顶;与 bitcoind 同步之前为null。finalized是最终链顶,按与节点相同的规则判定。halted在正常运行时为null。只有当一个被比特币判为最终的区块结果却是无效的时候,索引器才会停止,并由halted给出原因;尚未最终的无效区块会被排除在外,索引器重新选择它的主链(告警)。
indexer_stateRoot
参数:一个区块标识。它只对索引器已核对的区块作答;其他区块返回 -32004,格式不对的标识返回 -32602。
rpc $INDEXER indexer_stateRoot '["276"]' | jq .result
rpc $NODE ksana_getBlock '["276"]' | jq -r .result.header.state_root{
"height": "276",
"hash": "e23875efc23a5c7807a8ebc424e9f74a292fbfdfbe5c6b0bc505ddb8719a373c",
"state_root": "7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d"
}
7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d两个状态根一致:节点的区块 276,正是索引器只凭比特币重建出来的那个区块。
错误
出错的应答里没有 result,而是一个 error 对象,包含 code 和 message,有时还有 data:
rpc $NODE ksana_getOpStatus "[\"$KSA\"]" | jq .error
rpc $NODE ksana_getBlock '["0x10"]' | jq .error
rpc $NODE ksana_getBlockCount | jq .error{
"code": -32004,
"message": "unknown op"
}
{
"code": -32602,
"message": "block identifier must be \"latest\", a decimal height or a 64-digit hex hash"
}
{
"code": -32601,
"message": "Method not found"
}| 错误码 | 含义 |
|---|---|
-32601 |
没有这个方法,或者本节点不提供它(没有操作索引时的操作列表方法) |
-32602 |
参数格式错误:十六进制、解码、长度、范围 |
-32010 |
操作违反共识规则,或者是路径 C 操作 |
-32011 |
节点的策略拒绝了这个操作:手续费太低、条目池已满、替换时手续费没有提高 |
-32004 |
对象不存在:操作、区块、资产、结算、未核对的区块 |
-32009 |
节点繁忙:同时有太多 ksana_call 请求;请稍后再试 |
-32603 |
节点的内部错误 |