Quorum 核心的是 fullnode(全节点),大量的 fullnode 在少量 bootstrap node 的协助下构建为分布式网络。
每个 fullnode 都能自主选择是否开放 api 给 lightnode(轻节点)调用来收发数据。
在 quorum 这个分布式网络中,数据以 trx 的形式被归集在指定的 group 中。
每个 fullnode 可以创建或加入多个 groups,每个 group 以顺序区块的方式(区块链)保存这些 trxs,也就是说,每个 rum group 都是一条独立的区块链。用户可以选择创建或加入(订阅)指定的 groups。
fullnode 与 lightnode 的最大区别在于,是否需要在本地同步和保存区块链的完整数据。—— 如果用户创建/加入多个 groups,那么就有多条相应的 group 级别的区块链。fullnode 在本地同步和保管指定区块链的完整数据,lightnode 通常只需要通过 fullnode 提供的 api 筛选和保存自己所需的数据。
Quorum 生态内的 apps/servers 可以是基于 fullnode 的,也可以是基于 lightnode 的。一般用户想要快速体验,推荐前往官网选择感兴趣的应用:https://rumsystem.net/apps
任选一个方式,都可启动一个 fullnode:
1、采用 rum app(全节点应用),包含 quorum 主程序和图形界面的桌面应用。
2、开发者友好:只需要 quorum 主程序,通过 curl 或相应语言的 sdk 使用 api 来与主程序交互。
在 main 分支通过 make linux
编译,在 README.md
查看启动指令,或直接 ./quorum help
查看完整的指令和参数帮助。
还可以通过 quorum jwt
命令或 api: CreateToken 来生成 role 为 chain 级的 jwt token。这类 token 掌握对 fullnode 的所有权限。本地可通过 ip:host 和 jwt 使用 api 访问和管理 fullnode。—— 连接远程节点的功能,也已经被 rum app(全节点应用) 所支持。
通过 quorum jwt
命令或 api: CreateToken 来生成 role 为 node 级的 jwt token。这类 token 掌握对相应 group 的部分权限,比如获取 group 的所有数据,以及往链上推送数据。
所有完整 api 可查阅:https://github.com/rumsystem/quorum-api
也许你需要封装好的 sdk。
Start:
You can try:
- Node
- Group
- Network and Sync
- Content
- View group content:
- Post content to group
- Content with text only
- Content with inreplyto
- Content with images
- Like/Dislike
- Block
- Trx
- Producers
- App Config
- Private Group
- Chain Config: allow/deny auth mode and list
- SnapShot
- Pubqueue
Common params:
- 安装 RUM
-
启用 RUM 服务
-
采用你擅长的语言,与 RUM 服务建立连接,
"""
:param PORT: int,本地已经启动的 Rum 服务的 端口号,该端口号基本上是固定的,不会变动
:param HOST: str,本地已经启动的 Rum 服务的 host,通常是 127.0.0.1
:param CACERT: str,本地 Rum 的 server.crt 文件的绝对路径
"""
import requests
url = f"https://{HOST}:{PORT}/api/v1"
session = requests.Session()
session.verify = CACERT
session.headers.update({
"USER-AGENT": "asiagirls-py-bot",
"Content-Type": "application/json"})
session.get(f"{url}/node")
请在您自行创建的种子网络中测试,不要往公开的种子网络中发布测试信息。
go test cmd/main* -v
# only the first time you build need to run above.
export PATH=$(go env GOPATH)/bin:$PATH
# check GOPATH in PATH,
# if not in run the command above.
make gen-doc
# due to project continue developing sometime it may build failure
# when this happen you can check for a older head or read the deploy version below
make serve-doc
Open url http://localhost:1323/index.html
in the browser.
or you can read deploy version https://rumsystem.github.io/quorum-api/
-
Download and install go (ver 1.15.2)
-
Clone quorum project from
https://github.com/rumsystem/quorum.git
-
3 local nodes will be created
bootstrap node
(bootstrap)owner node
(group owner node)user node
(group user node)
follow steps below.
- cd to quorum source code path and create
config/
dir
mkdir -p config
- start
bootstrap node
go run main.go fullnode --bootstrap --listen /ip4/0.0.0.0/tcp/10666
output:
I0420 14:58:47.719592 332 keys.go:47] Load keys from config
I0420 14:58:47.781916 332 main.go:64] Host created, ID:<QmR1VFquywCnakSThwWQY6euj9sRBn3586LDUm5vsfCDJR>, Address:<[/ip4/172.28.230.210/tcp/10666 /ip4/127.0.0.1/tcp/10666]>
Record <HOST_ID>
, for example:
QmR1VFquywCnakSThwWQY6euj9sRBn3586LDUm5vsfCDJR
- Start
owner node
go run main.go fullnode --peername owner --listen /ip4/127.0.0.1/tcp/7002 --apiport 8002 --peer /ip4/127.0.0.1/tcp/10666/p2p/<QmR1VFquywCnakSThwWQY6euj9sRBn3586LDUm5vsfCDJR> --configdir config --datadir data --keystoredir ownerkeystore --jsontracer ownertracer.json --loglevel debug
- For the first time, user will be asked to input a password for the node, if not given, a password will be created for the node
- After a password is created, next time user will be asked to input the password to open node.
- env RUM_KSPASSWD can be used to input node password, like:
RUM_KSPASSWD=<node_password> go run main.go fullnode ...
- Start
user node
go run main.go fullnode --peername user --listen /ip4/127.0.0.1/tcp/7003 --apiport 8003 --peer /ip4/127.0.0.1/tcp/10666/p2p/<QmR1VFquywCnakSThwWQY6euj9sRBn3586LDUm5vsfCDJR> --configdir config --datadir data --keystoredir userkeystore --jsontracer usertracer.json --loglevel debug
- Backup/Restore
backup
RUM_KSPASSWD=secret go run main.go backup --peername mypeer --file /tmp/quorum-backup
# note: backup file name ends with `.zip.enc`
restore
restoreDir=/tmp/restore; RUM_KSPASSWD=secret go run main.go restore --peername mypeer --file /tmp/quorum-backup.zip.enc --configdir $restoreDir/config --datadir $restoreDir/data --keystoredir $restoreDir/keystore --seeddir $restoreDir/seeds
API: */api/v1/node
- Method: GET
- Usage : get node info
- Params : none
Example:
curl -X GET -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8003/api/v1/node
API return value:
{
"node_id": "16Uiu2HAkytdk8dhP8Z1JWvsM7qYPSLpHxLCfEWkSomqn7Tj6iC2d",
"node_publickey": "CAISIQJCVubdxsT/FKvnBT9r68W4Nmh0/2it7KY+dA7x25NtYg==",
"node_status": "NODE_ONLINE",
"node_type": "peer",
"node_version": "1.0.0 - 99bbd8e65105c72b5ca57e94ae5be117eaf05f0d",
"peers": {
"/quorum/nevis/meshsub/1.1.0": [
"16Uiu2HAmM4jFjs5EjakvGgJkHS6Lg9jS6miNYPgJ3pMUvXGWXeTc"
]
}
}
Param | Description |
---|---|
"node_publickey" | 组创建者的 pubkey |
"node_status" | "NODE_ONLINE" or "NODE_OFFLINE" |
"node_version" | 节点的协议版本号 |
"peers" | dict |
API: */api/v1/group
- Method: POST
- Usage : Owner create a group
- Params:
- group_name
- consensus_type
- encryption_type
- app_key
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_name":"my_test_group", "consensus_type":"poa", "encryption_type":"public", "app_key":"test_app"}' http://127.0.0.1:8002/api/v1/group
{
"group_name": "my_test_group",
"consensus_type": "poa",
"encryption_type": "public",
"app_key": "test_app"
}
API return value:
{
"genesis_block": {
"BlockId": "80e3dbd6-24de-46cd-9290-ed2ae93ec3ac",
"GroupId": "c0020941-e648-40c9-92dc-682645acd17e",
"ProducerPubKey": "CAISIQLW2nWw+IhoJbTUmoq2ioT5plvvw/QmSeK2uBy090/3hg==",
"Hash": "LOZa0CLITIpuQqpvXb6LyXV9z+2rSoU4JwBq0BCXttc=",
"Signature": "MEQCICAXCicQ6f4hRNSoJR89DF3a6AKpe6ZgLXsjXqH9H3jxAiA8dpukcriwEu8amouh2ZEKA2peXr3ctKQwxI3R6+nrfg==",
"Timestamp": 1632503907836381400
},
"group_id": "c0020941-e648-40c9-92dc-682645acd17e",
"group_name": "my_test_group",
"owner_pubkey": "CAISIQLW2nWw+IhoJbTUmoq2ioT5plvvw/QmSeK2uBy090/3hg==",
"owner_encryptpubkey": "age18kngxt6lkxqulldvxu8xs2ey77rrzwjhqpdey527ad4gkn3euu9sj3ah5j",
"consensus_type": "poa",
"encryption_type": "public",
"cipher_key": "8e9bd83f84cf1408484d24f486861947a1db3fbe6eb3c61e31af55a4803aedc1",
"app_key": "test_app",
"signature": "304502206897c3c67247cba2e8d5991501b3fd471fcca06f15915efdcd814b9e99c9a48a022100aa3024eb5663da6cbbde150132a4ff52c6c6aeeb49e0c039b4c28e72b071382f"
}
Params:
- genesis_block //genesis block, the first block of the group
- owner_encryptpubkey //owner encryption key(age)
- consensus_type
- encryption_type
- cipher_key //aes key [1]
- signature //owner signature
[1] neglect group encryption type (public
or private
), all trx except "POST" will be encrypted by cipher_key
returned json string from API call is the "seed
" of the newly created group
.
other nodes can use the seed to join the group.
API: */api/v1/group/join
- Method: GET
- Usage : User node join a group
- Params: the
seed
ofgroup
above
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"genesis_block":{"BlockId":"36ac6e22-80a1-4d54-abbb-8bd2c55ef8cf","GroupId":"eae3f0db-a034-4c5f-a25f-b1177390ec4d","ProducerPubKey":"CAISIQMJIG4do9g8PBixH432YXVQmD7Ilqp7DzbGxgLJHbRoFA==","Hash":"fDGwAPJbHHG0GpKLQZnRolK9FUO5nSIod/iprwQQn8g=","Signature":"MEYCIQDo5uge+saujb0WR6ZreISDYWpRzY6PQ3f5ly7vtHHgkQIhAKcuwDT2fIpBDx/7lQU6mIBQKJuQeI0Zbw3W7kHfBO28","Timestamp":1631804384241781200},"group_id":"eae3f0db-a034-4c5f-a25f-b1177390ec4d","group_name":"my_test_group","owner_pubkey":"CAISIQMJIG4do9g8PBixH432YXVQmD7Ilqp7DzbGxgLJHbRoFA==","owner_encryptpubkey":"age1lx3zh5sc5cureh484t5tm2036lhrzdnh96rfaft6echs9cqsefss4yn886","consensus_type":"poa","encryption_type":"public","cipher_key":"3994c4224da17ad50504c78458f37249149477c7bc643f3fe78e44033c17874a","signature":"30450220591361918948140c8ad1736cde3831f326470f2d3c5105a0b63867c7b216857c0221008921422c6e1974834d5610d4c6ad1a9dd0394ac464dfc12659cde41d75172d14"}' http://127.0.0.1:8003/api/v1/group/join
API return value:
{
"group_id": "ac0eea7c-2f3c-4c67-80b3-136e46b924a8",
"group_name": "my_test_group",
"owner_pubkey": "CAISIQOeAkTcYYWVTSH80dl2edMA4kI27g9/C6WAnTR01Ae+Pw==",
"user_pubkey": "CAISIQO7ury6x7aWpwUVn6mj2dZFqme3BAY5xDkYjqW/EbFFcA==",
"user_encryptpubkey": "age1774tul0j5wy5y39saeg6enyst4gru2dwp7sjwgd4w9ahl6fkusxq3f8dcm",
"consensus_type": "poa",
"encryption_type": "public",
"cipher_key": "076a3cee50f3951744fbe6d973a853171139689fb48554b89f7765c0c6cbf15a",
"signature": "3045022100a819a627237e0bb0de1e69e3b29119efbf8677173f7e4d3a20830fc366c5bfd702200ad71e34b53da3ac5bcf3f8a46f1964b058ef36c2687d3b8effe4baec2acd2a6"
}
API return value:
- "user_encryptpubkey" 本节点在组内的加密公钥 **
- consensus_type
- encryption_type
- "cipher_key" 组内协议对称加密密钥(aes)
- "signature" signature by group owner
节点 B 加入组后,开始自动同步(SYNCING),同步完成后状态变为(IDLE)
API: */api/v1/groups
- Method: GET
- Usage : List all groups
- Params : none
Example:
curl -X GET -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8002/api/v1/groups
- Method: GET
- Params: none
API return value:
{
"groups": [
{
"group_id": "90387012-431e-495e-b0a1-8d8060f6a296",
"group_name": "my_test_group",
"owner_pubkey": "CAISIQP67zriZHvC+OWv1X8QzFIwm8CKIM+5KRx1FsUSHQoKxg==",
"user_pubkey": "CAISIQP67zriZHvC+OWv1X8QzFIwm8CKIM+5KRx1FsUSHQoKxg==",
"user_eth_addr": "0x721d91555b2Ecf34aa87E3566E34cDcDbaf125DF",
"consensus_type": "POA",
"encryption_type": "PUBLIC",
"cipher_key": "f4ee312ef7331a2897b547da0387d56a7fe3ea5796e0b628f892786d1e7ec15d",
"app_key": "test_app",
"last_updated": 1631725187659332400,
"highest_height": 0,
"highest_block_id": "a865ae03-d8ce-40fc-abf6-ea6f6132c35a",
"group_status": "IDLE",
"snapshot_info": {
"TimeStamp": 1649096392051580700,
"HighestHeight": 0,
"HighestBlockId": "5f3a1cb7-822a-4fb8-ae84-864c496ab8de",
"Nonce": 19500,
"SnapshotPackageId": "9d381d97-1cf4-4d13-9f95-b816f01f4df5",
"SenderPubkey": "CAISIQMuaL8Y5TxbaO0ult7BTjYYhgrteewJANQGt/CYANjxQA=="
}
}
]
}
- Params:
- consensus_type
- encryption_type
- user_eth_addr [1]
- cipher_key
- last_updated
- highest_height [2]
- highest_block_id [3]
- snapshot_info
[1] The eth address of current user. 当前用户的 ETH 地址。
[2] Height of the "highest" block in this group
[3] block_id of the "highest" block in this group
API: */api/v1/group/clear
- Method: POST
- Usage : User node clear a group
- Params :
删除一个组的全部本地内容,包括如下
- block
- trx
- announced
- scheam
- denied_list
- post
- producer
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"13a25432-b791-4d17-a52f-f69266fc3f18"}' http://127.0.0.1:8002/api/v1/group/clear | jq
API return value:
{
"group_id": "13a25432-b791-4d17-a52f-f69266fc3f18",
"signature": "30450221009634af1636bf7374453cd73088ff992d9020777eb617795e3c93ea5d5008f56d022035342a852e87afa87b5e038147dedf10bb847f60808ec78a470b92dfbff91504"
}
目前前端在离开组时需一起调用该 API,清除所有组相关的数据,警告用户“如果离开组,所有数据将被删除,再加入需要重新同步”即可
API: */api/v1/group/leave
- Method: POST
- Usage : User node leave a group
- Params :
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"846011a8-1c58-4a35-b70f-83195c3bc2e8"}' http://127.0.0.1:8002/api/v1/group/leave
Params:
{
"group_id": "846011a8-1c58-4a35-b70f-83195c3bc2e8"
}
API return value:
{
"group_id": "846011a8-1c58-4a35-b70f-83195c3bc2e8",
"signature": "304402201818acb8f1358b65aecd0343a48f0fe79c89c3f2852fa809dd6b9315a20740e4022026d0ca3b981ee2a3701930b62d7f5ddcf959a3ba50d926c31f6c143ef91f024a"
}
Param | Description |
---|---|
"signature" | signature by group owner |
API: */api/v1/group
- Method: DELETE
- Usage : Owner node del a group
- Params :
Example:
curl -X DELETE -H 'Content-Type: application/json' -d '{"group_id":"846011a8-1c58-4a35-b70f-83195c3bc2e8"}' http://127.0.0.1:8003/api/v1/group
API return value:
{
"group_id": "846011a8-1c58-4a35-b70f-83195c3bc2e8",
"owner_pubkey": "CAASpgQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGfeXQnKNIZpZeKDkj+FgAnVwBMa/GLjNrglsZduWWopYiPMBv9NEv59kG7JjYAaVZsWD5t2tgHPSYzHO6uc6DB9rphFprvx3dH8N/GDNh52J+FuS65Bw56sSIv/Y0e4D15WJsQV2/AQQvW90qpLTnRPY2VVUWiuETJQFDBvm1tmTRz+QOHQEqye0ogQSTtIvCdFcf3u7Bb2IAVTqFOIQlB98zwd1UNST9mkzgIYv3jIQ7lgA85/EC7v77J4Igin6/76aUgFbz++4f05Lns1nzvnGcorXSB7Dz//L+94Pyi00Y5ekN/YE3dk01PEr5Ucvi7bF1YfX2S+B4ruliZeTab05kysO5eKJF5Fd17YaEsIJb1d5kTXWL93/TJ5DkajLYmv9JGPjz78OUSMkz2FgS25hy4wIQpg0pP2We+mUoYK5B22FYdOuIropKq0VAzQeG/dFMAt7rFGNP8GLmQF0qV/KEE4xO3+kJdcWMDykMLdzOGwJzG9NHksIZPj4yxJP+jFdffZ9hHR0AuQlyCTg4Us13PTAYn6pTtwkvy0aS7J2Q8+IwNLuMJrfwjZYxTkdqJcvlck6+2IbLHYyBVi5TxT2zERB4Eg0iuJYq2VFWEkEWsUMtDda5G3jEI9yL/afjhVn6xmyo1D7aoeYqXqIx9Y/8jpRC4nN1wMfpsO+qdQIDAQAB",
"signature": "owner_signature"
}
API: */api/v1/group/{group_id}/seed
- Method: Get
- Usage : Get the seed of a group by group_id.
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8003/api/v1/group/c0c8dc7d-4b61-4366-9ac3-fd1c6df0bf55/seed
API: */api/v1/network
- Method: GET
- Usage : Get network info
- Params : none
Example:
curl http://localhost:8002/api/v1/network
API return value:
{
"groups": [
{
"GroupId": "997ce496-661b-457b-8c6a-f57f6d9862d0",
"GroupName": "pb_group_1",
"Peers": [
"16Uiu2HAkuXLC2hZTRbWToCNztyWB39KDi8g66ou3YrSzeTbsWsFG"
]
}
],
"node": {
"addrs": [
"/ip4/192.168.20.17/tcp/7002",
"/ip4/127.0.0.1/tcp/7002",
"/ip4/107.159.4.35/tcp/65185"
],
"ethaddr": "0x4daD72e78c3537a8852ca7b3d1742Dd42c30441A",
"nat_enabled": true,
"nat_type": "Public",
"peerid": "16Uiu2HAm8XVpfQrJYaeL7XtrHC3FvfKt2QW7P8R3MBenYyHxu8Kk"
}
}
这里需要注意, nat_type 和 addrs 都会改变,开始的时候没有公网地址,类型是 Unknown 之后会变成 Private,再过一段时间反向链接成功的话,就变成 Public,同时 Addrs 里面出现公网地址。
API: */api/v1/group/{group_id}/startsync
- Method: POST
- Usage : Start sync for a group
- Params :
客户端可以手动触发某个组和组内其他节点同步块
Example:
curl -X POST -H 'Content-Type: application/json' -d '' http://<IP_ADDR>/api/v1/group/<GROUP_ID>/startsync
API return value:
status_code | result | Description |
---|---|---|
200 | {"GroupId":<GROUP_ID>,"Error":""} |
GROUP_ID 的组正常开始同步,同时组的状态会变为 SYNCING |
400 | {"error":"GROUP_ALREADY_IN_SYNCING"} |
GROUP_ID 的组当前正在同步中 |
这个 api 将会废弃,目前保留仅供调试用。
API: */api/v1/group/{group_id}/content
- Method: GET
- Usage : Get content of a group, all or with senders filter
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8003/api/v1/group/c0c8dc7d-4b61-4366-9ac3-fd1c6df0bf55/content
API: */app/api/v1/group/{group_id}/content?starttrx={trx_id}&num={n}
TIPS: 这个 API 比其它常见 API 多了一个 /app
- Method: POST
- Usage : Request content trxs of a group with senders filter
- Params :
- group_id
- trx_id: start trx_id to fetch
- n: int,num of trxs to fetch
Example:
curl -v -X POST -H 'Content-Type: application/json' -d '{"senders":[ "CAISIQP8dKlMcBXzqKrnQSDLiSGWH+bRsUCmzX42D9F41CPzag=="]}' "http://localhost:8002/app/api/v1/group/5a3224cc-40b0-4491-bfc7-9b76b85b5dd8/content?starttrx=95f74d77-b15a-4cf5-a964-1c367c1b1909&num=20"
API return value: a list of trxs.
Note Trx:
- "type" of "Content": "Note"
- "TypeUrl": "quorum.pb.Object"
{
"TrxId": "da2aaf30-39a8-4fe4-a0a0-44ceb71ac013",
"Publisher": "CAISIQOlA37+ghb05D5ZAKExjsto/H7eeCmkagcZ+BY/pjSOKw==",
"Content": {
"type": "Note",
"content": "simple note by aa",
"name": "A simple Node id1"
},
"TypeUrl": "quorum.pb.Object",
"TimeStamp": 1629748212762123400
}
Person Profile Trx:
- "type" of "Content": any of "name"(string),"image"(dict) or "wallet"(list)
- "TypeUrl": "quorum.pb.Person"
{
"TrxId": "7d5e4f23-42c5-4466-9ae3-ce701dfff2ec",
"Publisher": "CAISIQNK024r4gdSjIK3HoQlPbmIhDNqElIoL/6nQiYFv3rTtw==",
"Content": {
"name": "Lucy",
"image": {
"mediaType": "image/png",
"content": "there will be bytes content of images、"
},
"wallet": [
{
"id": "bae95683-eabb-212f-9588-12dadffd0323",
"type": "mixin",
"name": "mixin messenger"
}
]
},
"TypeUrl": "quorum.pb.Person",
"TimeStamp": 1637574058426424900
}
Like/Dislike Trx:
- "type" of "Content": "Like" or "Dislike"
- "TypeUrl": "quorum.pb.Object"
{
"TrxId": "65de2397-2f35-4a07-9af2-35a920b79882",
"Publisher": "CAISIQMbTGdEDACml0BOcBXpWM6FOLDgH7u9VapHJ+wDMZSObw==",
"Content": {
"id": "02c23edc-be7d-4a32-bbae-fb8e179e9c5b",
"type": "Like"
},
"TypeUrl": "quorum.pb.Object",
"TimeStamp": 1639980884426949600
}
Params
- "TrxId",trx_id
- "Publisher" ,发布者
- "Content", dict, 内容
- "TypeURL", string, Type
- "TimeStamp" ,int64
nodeA can be owner node
or user node
.
API: */api/v1/group/content
- Method: POST
- Usage : Post content to group
- Params :
type
: requested, string, should be one of these:- "Note"
- "Like" or "Dislike"
object
:requested, should stay the same astype
- type "Note":
name
:optional- for note only:
content
:requested, string, the text of content
- for image only:
image
:requested, list, each image should have:mediaType
:requested, string, type of imagecontent
:requested, string, base64encode image bytesname
: name of image
- for note + image:
content
: the same as above (for note only)image
: the same as above(for image only)
inreplyto
: optionaltrxid
: the trx_id to reply
- type "Like" or "Dislike"
id
: requested, string, trx_id to like/dislike
- type "Note":
target
:requestedid
: requested, string, group_id of the grouptype
: requested, sting, "Group"
- API return value:
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"type":"Add","object":{"type":"Note","content":"simple note by aa","name":"A simple Node id1"},"target":{"id":"c0c8dc7d-4b61-4366-9ac3-fd1c6df0bf55","type":"Group"}}' http://127.0.0.1:8002/api/v1/group/content
when object.content
is not null, object.image
is optional.
{
"type": "Add",
"object": {
"type": "Note",
"content": "Good Morning!\nHave a nice day."
},
"target": {
"id": "c60ed78e-df15-4408-9b5b-f87158cf0bda",
"type": "Group"
}
}
rum app bbs use object.name
as title which should not be ""
:
{
"type": "Add",
"object": {
"type": "Note",
"content": "Good Morning!\nHave a nice day.",
"name": "my first forum!"
},
"target": {
"id": "c60ed78e-df15-4408-9b5b-f87158cf0bda",
"type": "Group"
}
}
object.content
or object.image
is requested. one of them, or both.
{
"type": "Add",
"object": {
"type": "Note",
"content": "can’t agree more! thx.",
"inreplyto": {
"trxid": "08c6ee4d-0310-47cf-988e-3879321ef274"
}
},
"target": {
"id": "d87b93a3-a537-473c-8445-609157f8dab0",
"type": "Group"
}
}
when object.image
is not null, object.content
is optional.
1~4 images , total size less than 200 kb
{
"type": "Add",
"object": {
"type": "Note",
"content": "Good Morning!\nHave a nice day.",
"name": "",
"image": [
{
"mediaType": "image/png",
"content": "this is pic content by base64.b64encode(pic-content-bytes).decode(\"utf-8\")",
"name": "pic-name init by uuid like f\"{uuid.uuid4()}-{datetime.now().isoformat()}\""
}
]
},
"target": {
"id": "d87b93a3-a537-473c-8445-609157f8dab0",
"type": "Group"
}
}
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"type":"Like","object":{"id":"578e65d0-9b61-4937-8e7c-f00e2b262753"}, "target":{"id":"c0c8dc7d-4b61-4366-9ac3-fd1c6df0bf55","type":"Group"}}' http://127.0.0.1:8002/api/v1/group/content
Params:
- "id" in "object" is trx_id
{
"type": "Like",
"object": {
"id": "578e65d0-9b61-4937-8e7c-f00e2b262753"
},
"target": {
"id": "c0c8dc7d-4b61-4366-9ac3-fd1c6df0bf55",
"type": "Group"
}
}
API: */api/v1/block/{group_id}/{block_id}
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8003/api/v1/block/<GROUP_ID>/<BLOCK_ID>
API return value:
{
"BlockId": "aa75447f-b621-4424-a723-9d4bf1d9fff9",
"GroupId": "a4b634c2-ceb7-4e60-9584-a221aa7b6855",
"PrevBlockId": "78bffd23-2dba-408b-b88e-ed3f5f005411",
"PreviousHash": "ZXh7C2Fnp4J8ny96Udo2Nr3Z50zu+KdA4BcEiw7cF4s=",
"Trxs": [
{
"TrxId": "820d6b65-99b8-4b96-afb1-0b639a76e1f3",
"GroupId": "a4b634c2-ceb7-4e60-9584-a221aa7b6855",
"Data": "CiR0eXBlLmdvb2dsZWFwaXMuY29tL3F1b3J1bS5wYi5PYmplY3QSKxIETm90ZTIRc2ltcGxlIG5vdGUgYnkgYWFCEEEgc2ltcGxlIE5vZGUgaWQ=",
"TimeStamp": 1631817240704625000,
"Version": "ver 0.01",
"Expired": 1631817540704625200,
"SenderPubkey": "CAISIQJHvBByFpoeT6SBvE+w3FTs5zRTq19hi7GP0fTVkj00hw==",
"SenderSign": "MEUCIBwTg4UzSub5IUl4NVEZmMmkG8Kx2XMZCHIThoLdAtBoAiEAoCM5f/vYbUVIqdgS40vVueb954duzIjrzMDzHmE8h6s="
}
],
"ProducerPubKey": "CAISIQJHvBByFpoeT6SBvE+w3FTs5zRTq19hi7GP0fTVkj00hw==",
"Hash": "RnChfYe3rBsO5swKoSDV5K8spV+NL5kaJ3aH1w/73lU=",
"Signature": "MEYCIQC9Rnj381tjLmo8XwW0kpOCQb5o62QN78L4a6QsXIA37gIhALVClUs9UB32f7wQTUmoVg58uLr6r3apGkNyKh1uek4i",
"Timestamp": 1631817245705639200
}
"Trxs" is a list. one block can have a number of trxs.
Trx 生命周期,加密和出块过程
Trx 种类
所有链上操作均是 Trx,客户端相关的 Trx 有 5 种
Type | Description | More about |
---|---|---|
POST | user 发送组内信息(POST Object) | Post content |
ANNOUNCE | user 宣布自己在组内的公钥 | Anounce user |
AUTH | Owner 调整 chain 权限 | chainconfig |
SCHEMA | Owner 管理组内数据 schema | Group config |
PRODUCER | Owner 管理组内 producer | Producers |
Trx 加密类型
为了确保后加入的用户能正确使用组内功能,根据 trx 类型,进行如下形式的加密:
-
POST trx
- 强加密组: 每个发送节点都要根据自己手中的组内成员名单(公钥名单),对 POST 的内容进行非对称加密,然后发送,收到 trx 的节点使用自己的公钥对 trx 进行解密 private group
- 弱加密组: 每个发送节点都用 seed 中的对称加密字串对收发的 trx 数据进行对称加密
-
other trx
- 所有其他的链相关的协议均使用弱加密组策略(用 seed 中的对称加密字串进行收发)
出块流程/共识策略
一个 Trx 被 push 到链上后,根据 group 的不同共识类型,将被采取不同形式对待。
Trx 状态判断
同其他链相似,Trx 的发送没有重试机制,客户端应自己保存并判断一个 Trx 的状态,具体过程如下
-
将这个 trx 标记为“发送中”
-
设置一个超时,目前建议是 30 秒,不断查询,直到相同 trx_id 的内容出现在返回结果中,即可认为 trx 发送成功(被包含在块中)
-
如果超时被触发,没有查到结果,即认为发送 trx 失败,客户端可以自行处理重发
- AUTH 相关的 trx 处理方式相同(白名单/黑名单)
API: */api/v1/trx/{group_id}/{trx_id}
Example:
curl -X GET -H 'Content-Type: application/json' -d http://127.0.0.1:8003/api/v1/trx/<GROUP_ID>/<TRX_ID>
API return value:
{
"TrxId": "c63d7c8e-d56d-432c-aae3-7d0d9dc34c31",
"GroupId": "3bb7a3be-d145-44af-94cf-e64b992ff8f0",
"Data": "rx5hmlGgIgnQSm5tT75KY96UaIauDALPvPLjRRe2qiwJhc8VI3wwpsm2M3Y4bYCXGhpjWVDc3D5pHr+cnhuUqWZWQUZJ8FkGYG+bHnz0t4z2//6xo+3+GrCogphT+vJHPCld3womShSLEo4G3VTBbBzaPOnSg1T31OuI8wRsKoslI1owKiWC4r5VwhXHmLq8RW+HFpIy7PqZXxr+8Hsojawrs0B9CbJ3wf7TWubUlw5JhpAXGbbBBw6nLyGM7MnL0+Q3nUi1mX9dgGWOEwwxvO66SYhB",
"TimeStamp": "1639570707554262200",
"Version": "1.0.0",
"Expired": 1639571007554262200,
"SenderPubkey": "CAISIQKwLxW1uBoZHMbss9QTdVLb8lfBhvMQ3ucnm9afGnVmpQ==",
"SenderSign": "MEQCIGKc0MyiusNFWZEc+ZMXzk/eev7Sdouii4zAeSIGCqnMAiAz+LMXWck1NIJLB8U7mGmetzYGuTYPKxifH7sF1cMwZg=="
}
- "Data" 是加密的,(encryption type由组类型决定)
- 客户端应通过获取 Content 的 API 来获取解密之后的内容
Producer 作为组内“生产者”存在,可以代替 Owner 出块,组内有其他 Producer 之后,Owenr 可以不用保持随时在线,在 Owner 下线的时间内,Producer 将代替 Owner 执行收集 Trx 并出块的任务
关于 Producer 的内容,如具体的共识算法、Producer 的收益等内容,请参考 RUM 设计文档
Owner 作为组内第一个 Producer 存在,有其它 Producer 存在时,如果 Owner 在线,也将作为一个 Producer 存在
有 Producer 存在的流程如下:
-
Owner 作为 Producer 存在,负责出块
-
其他 Producer 获得组的 seed,加入组,完成同步
-
Producer 用Announce API将自己作为 Producer 的意愿告知 Owner
-
其他节点(包括 Owner 节点)查看所有 Announce 过的 Producer
- 请注意,Owner 只可以选择在组内Announce 过自己的 Producer,并且 producer 的状态应该为“ADD”,没有 Announce 过的 producer 是不可以添加的
-
查看 Announce Producer 状态,可以看出,经过 Owner 批准,该 Producer 的状态(result)变为 APPROVED
-
- Owner 可以随时删除一个 Producer, 不管 Producer 是否 Announce 离开
- 在实际环境中,Producer 完全可以不 Announce Remove 而直接离开,Owner 需要注意到并及时将该 Producer 从 Producer 列表中删除
API: */api/v1/group/announce
- Method: POST
- Usage : Announce producer
- Params :
- group_id
- action
- type
- memo
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"5ed3f9fe-81e2-450d-9146-7a329aac2b62", "action":"add", "type":"producer", "memo":"producer p1, realiable and cheap, online 24hr"}' http://127.0.0.1:8005/api/v1/group/announce | jq
Params:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"action": "add",
"type": "producer",
"memo": "producer p1, realiable and cheap, online 24hr"
}
Param | Description |
---|---|
"action" | add or remove |
"type" | string |
"memo" | memo |
API return value:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"sign_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"encrypt_pubkey": "",
"type": "AS_PRODUCER",
"action": "ADD",
"sign": "3046022100a853ca31f6f6719be213231b6428cecf64de5b1042dd8af1e140499507c85c40022100abd6828478f56da213ec10d361be8709333ff44cd0fa037409af9c0b67e6d0f5",
"trx_id": "2e86c7fb-908e-4528-8f87-d3548e0137ab"
}
Param | Description |
---|---|
"sign_pubkey" | producer 在本组的签名 pubkey |
"encrypt_pubkey" | 没有使用 |
"type" | AS_PRODUCER |
"action" | ADD |
"sign" | producer 的签名 |
API: */api/v1/group/{group_id}/announced/producers
- Method: GET
- Usage : Get announced producers
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8002/api/v1/group/5ed3f9fe-81e2-450d-9146-7a329aac2b62/announced/producers
API return value:
[
{
"AnnouncedPubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"AnnouncerSign": "3046022100a853ca31f6f6719be213231b6428cecf64de5b1042dd8af1e140499507c85c40022100abd6828478f56da213ec10d361be8709333ff44cd0fa037409af9c0b67e6d0f5",
"Result": "ANNOUCNED",
"Action": "Add",
"TimeStamp": 1634756064250457600
}
]
- ACTION 可以有 2 种状态,“ADD”表示 Producer 正常,“REMOVE”表示 Producer 已经 announce 自己离开改组
[
{
"AnnouncedPubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"AnnouncerSign": "3046022100a853ca31f6f6719be213231b6428cecf64de5b1042dd8af1e140499507c85c40022100abd6828478f56da213ec10d361be8709333ff44cd0fa037409af9c0b67e6d0f5",
"Result": "APPROVED",
"Action": "ADD",
"TimeStamp": 1634756064250457600
}
]
Params:
Param | Description |
---|---|
"AnnouncedPubkey" | producer pubkey |
"AnnouncerSign" | producer 的签名 |
"Result" | ANNOUNCED or APPROVED,producer 刚 Announce 完毕的状态是 ANNOUNCED |
"Action" | "ADD" or "REMOVE" |
"TimeStamp" | timestamp |
API: */api/v1/group/producer
- Method: POST
- Usage : Add producer
- Params :
- producer_pubkey
- group_id
- action
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"producer_pubkey":"CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==","group_id":"5ed3f9fe-81e2-450d-9146-7a329aac2b62", "action":"add"}' http://127.0.0.1:8002/api/v1/group/producer | jq
Params:
{
"producer_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"action": "add"
}
Param | Description |
---|---|
"action" | "add" // add or remove |
"producer_pubkey" | producer public key |
"memo" | optional |
API return value:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"producer_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"owner_pubkey": "CAISIQNVGW0jrrKvo9/40lAyz/uICsyBbk465PmDKdWfcCM4JA==",
"sign": "304402202cbca750600cd0aeb3a1076e4aa20e9d1110fe706a553df90d0cd69289628eed022042188b48fa75d0197d9f5ce03499d3b95ffcdfb0ace707cf3eda9f12473db0ea",
"trx_id": "6bff5556-4dc9-4cb6-a595-2181aaebdc26",
"memo": "",
"action": "ADD"
}
Param | Description |
---|---|
"producer_pubkey" | publikc key for producer just added |
"sign" | string |
"action" | Add or REMOVE |
"memo" | memo |
API: */api/v1/group/{group_id}/producers
- Method: GET
- Usage : Get producers
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8005/api/v1/group/5ed3f9fe-81e2-450d-9146-7a329aac2b62/producers | jq
API return value:
[
{
"ProducerPubkey": "CAISIQNVGW0jrrKvo9/40lAyz/uICsyBbk465PmDKdWfcCM4JA==",
"OwnerPubkey": "CAISIQNVGW0jrrKvo9/40lAyz/uICsyBbk465PmDKdWfcCM4JA==",
"OwnerSign": "3046022100e29a892a9e66f9a736a7d9672db7bd9e2431b8bcff6d407723303a14bc53c66e022100ecf61ce2ff95109fb6504094104afca7074a7c96ac79733cab98cef0e5f85baf",
"TimeStamp": 1634755122424178000,
"BlockProduced": 3
},
{
"ProducerPubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"OwnerPubkey": "CAISIQNVGW0jrrKvo9/40lAyz/uICsyBbk465PmDKdWfcCM4JA==",
"OwnerSign": "304402202cbca750600cd0aeb3a1076e4aa20e9d1110fe706a553df90d0cd69289628eed022042188b48fa75d0197d9f5ce03499d3b95ffcdfb0ace707cf3eda9f12473db0ea",
"TimeStamp": 1634756661280204800,
"BlockProduced": 0
}
]
Param | Description |
---|---|
"ProducerPubkey" | Producer Pubkey |
"OwnerPubkey" | Owner Pubkey |
"OwnerSign" | Owner 签名 |
"TimeStamp" | Timestamp |
"BlockProduced" | 该 Producer 目前实际生产的区块数 |
- 注意,如果 ProducerPubkey 和 OwnerPubkey 相同,则说明这是 Owner,上例可以看出,Owner 目前共生产了 3 个区块,Producer
<CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ>
还没有生产区块
API: */api/v1/group/producer
- Method: POST
- Usage : Owner remove producer
- Params :
- producer_pubkey
- group_id
- action
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"producer_pubkey":"CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==","group_id":"5ed3f9fe-81e2-450d-9146-7a329aac2b62", "action":"remove"}' http://127.0.0.1:8002/api/v1/group/producer | jq
{
"producer_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"action": "remove"
}
API: */api/v1/group/appconfig
- Method: POST
- Usage : Add app config item
- Params :
- group_id
- action
- name
- type
- value
- memo
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"action":"add", "group_id":"c8795b55-90bf-4b58-aaa0-86d11fe4e16a", "name":"test_bool", "type":"bool", "value":"false", "memo":"add test_bool to group"}' http://127.0.0.1:8002/api/v1/group/appconfig | jq
Params:
{
"action": "add",
"group_id": "c8795b55-90bf-4b58-aaa0-86d11fe4e16a",
"name": "test_bool",
"type": "bool",
"value": "false",
"memo": "add test_bool to group"
}
Param | Description |
---|---|
"action" | add or del |
"name" | 配置项的名称 |
"type" | 配置项的类型,可选值为 "int", "bool", "string" |
"value" | 配置项的值,必须与 type 相对应,且转换为字符串,比如"100" ,"false" 。原因为 json: cannot unmarshal bool into Go struct field AppConfigParam.value of type string |
"memo" | memo |
权限:
只有 group owner 可以调用该 API
调用后,通过块同步,组内所有节点获得该配置
API return value:
{
"group_id": "c8795b55-90bf-4b58-aaa0-86d11fe4e16a",
"sign": "3045022100e1375e48cfbd51cb78afc413fcca084deae9eb7f8454c54832feb9ae00fada7702203ee6fe2292ea3a87d687ae3369012b7518010e555b913125b8a7bf54f211502a",
"trx_id": "9e54c173-c1dd-429d-91fa-a6b43c14da77"
}
Param | Description |
---|---|
"sign" | owner 对该 trx 的签名 |
group 的 key 是 owner 通过 app config 自行添加的。
API: */api/v1/group/{group_id}/appconfig/keylist
- Method: GET
- Usage : Get app config keylist
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8002/api/v1/group/c8795b55-90bf-4b58-aaa0-86d11fe4e16a/appconfig/keylist
API:/v1/group/<GROUP_ID>/appconfig/keylist
API return value:
[
{
"Name": "test_string",
"Type": "STRING"
}
]
Params:
Param | Description |
---|---|
"name" | 配置项的名称 |
"type" | 配置项的数据类型 |
API: */api/v1/group/{group_id}/appconfig/{KEY_NAME}
- Method: GET
- Usage : Get app config key
- Params :
- group_id
- key_name
Example:
curl -X GET -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8002/api/v1/group/c8795b55-90bf-4b58-aaa0-86d11fe4e16a/appconfig/test_string | jq
API return value:
{
"Name": "test_string",
"Type": "STRING",
"Value": "123",
"OwnerPubkey": "CAISIQJOfMIyaYuVpzdeXq5p+ku/8pSB6XEmUJfHIJ3A0wCkIg==",
"OwnerSign": "304502210091dcc8d8e167c128ef59af1b6e2b2efece499043cc149014303b932485cde3240220427f81f2d7482df0d9a4ab2c019528b33776c73daf21ba98921ee6ff4417b1bc",
"Memo": "add test_string to group",
"TimeStamp": 1639518490895535600
}
参数同添加组内配置
"Private" stands for encryption type.
强加密类型的种子网络。这类种子网络的特点是,任何人即便可以拿到 seed 并由此加入 group,但如果没有被 owner approved as user,是无法获取到 content 的,即能拿到 trxs 但 trx 的 Content 字段为空。以此保证 group 的内容隐私。
When creating a group, you need to set encryption_type
as "private", and then you'll get a private group.
The encryption_type
can not be changed after group was created.
Private group 的用户管理,本质上是管理解密权限。
在解密权限管理上,owner 与 user 的行为一致,即 owner 应该 announce 并 approve 自己,以获得内容解密权限。其它 user 也需要 announce 自己,然后由 owner 决定是否通过该申请。
workflow:
- Announce user's encrypt pubkey to a group
- view announced users or a user
- Owner approve a user or remove
API: */api/v1/group/announce
- Method: POST
- Usage : Announce user
- Params :
- group_id
- action, "add" or "remove"
- type, "user"
- memo
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"5ed3f9fe-81e2-450d-9146-7a329aac2b62", "action":"add", "type":"user", "memo":"invitation code:a423b3"}' http://127.0.0.1:8003/api/v1/group/announce | jq
Params:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"action": "add",
"type": "user",
"memo": "invitation code:a423b3"
}
API return value:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"sign_pubkey": "CAISIQJwgOXjCltm1ijvB26u3DDroKqdw1xjYF/w1fBRVdScYQ==",
"encrypt_pubkey": "age1fx3ju9a2f3kpdh76375dect95wmvk084p8wxczeqdw8q2m0jtfks2k8pm9",
"type": "AS_USER",
"action": "ADD",
"sign": "304402206a68e3393f4382c9978a19751496e730de94136a15ab77e30bab2f184bcb5646022041a9898bb5ff563a6efeea29b30bac4bebf0d3464eb326fd84322d98919b3715",
"trx_id": "8a4ae55d-d576-490a-9b9a-80a21c761cef"
}
Param | Description |
---|---|
"sign_pubkey" | user's sign pubkey |
"encrypt_pubkey" | user's encrypt pubkey |
"type" | "AS_USER" |
"action" | "ADD" |
"sign" | user's signature |
API: */api/v1/group/{group_id}/announced/users
API: */api/v1/group/{group_id}/announced/user/{pubkey}
- Method: GET
- Usage : get announced users or a user
- Params :
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8002/api/v1/group/5ed3f9fe-81e2-450d-9146-7a329aac2b62/announced/users
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8002/api/v1/group/5ed3f9fe-81e2-450d-9146-7a329aac2b62/announced/user/CAISIQMaZWI95T0kxDNcB3DxO0T/BraC1gEKxZRVihcxevtUgg==
API return value:
[
{
"AnnouncedSignPubkey": "CAISIQIWQX/5Nmy2/YoBbdO9jn4tDgn22prqOWMYusBR6axenw==",
"AnnouncedEncryptPubkey": "age1a68u5gafkt3yfsz7pr45j5ku3tyyk4xh9ydp3xwpaphksz54kgns99me0g",
"AnnouncerSign": "30450221009974a5e0f3ea114de8469a806894410d12b5dc5d6d7ee21e49b5482cb062f1740220168185ad84777675ba29773942596f2db0fa5dd810185d2b8113ac0eaf4d7603",
"Result": "ANNOUNCED"
}
]
Params:
Param | Description |
---|---|
"AnnouncedPubkey" | user's pubkey |
"AnnouncerSign" | string |
"Result" | ANNOUNCED or APPROVED |
API: */api/v1/group/user
- Method: POST
- Usage : owner approve a user
- Params :
- user_pubkey
- group_id
- action, "add" or "remove"
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"user_pubkey":"CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==","group_id":"5ed3f9fe-81e2-450d-9146-7a329aac2b62", "action":"add"}' http://127.0.0.1:8002/api/v1/group/user | jq
Params:
{
"user_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"action": "add"
}
API return value:
{
"group_id": "5ed3f9fe-81e2-450d-9146-7a329aac2b62",
"user_pubkey": "CAISIQOxCH2yVZPR8t6gVvZapxcIPBwMh9jB80pDLNeuA5s8hQ==",
"owner_pubkey": "CAISIQNVGW0jrrKvo9/40lAyz/uICsyBbk465PmDKdWfcCM4JA==",
"sign": "304402202cbca750600cd0aeb3a1076e4aa20e9d1110fe706a553df90d0cd69289628eed022042188b48fa75d0197d9f5ce03499d3b95ffcdfb0ace707cf3eda9f12473db0ea",
"trx_id": "6bff5556-4dc9-4cb6-a595-2181aaebdc26",
"memo": "",
"action": "ADD"
}
Param | Description |
---|---|
"sign" | signature |
由于 private group 管理解密权限,chainconfig 管理出块权限,这两者在底层实现上是解耦的,而当 private group 创建后, auth mode 被默认设为 FOLLOW_DNY_LIST 模式,其效果为:任何加入 group 的人都可以发布内容并上链,只不过没有获得解密权限的人无法解密内容。
如果 private group 的创建者想要杜绝非目标用户制造链上数据,只想让那些被 approved 的 user (或者只有少量指定用户)才可以发布内容并上链,那么可这样实现:
- 创建 private group 后,立即更新 POST auth mode 设置为 FOLLOW_ALW_LIST 模式。这样所有加入 group 的人,都默认不可以发布内容上链。
- owner 在 approve 每条 announce as user 的申请(该操作赋予该 user 内容解密权限)后:
- 如果希望该用户可以发布内容上链,那就把该用户也更新到 allow list;
- 如果不希望该用户拥有发布内容的权限,则无需任何操作。
关于 auth mode 与 allow list 等 chainconfig 的具体说明,请移步:Chain Config: allow/deny auth mode and list。
请您留意,每种 trx type 都可以单独设置 auth mode,较常见的是只修改 POST 类型的 auth mode,通常无需改动其它 trx type 的默认设置。
For a group, the owner can grant/deny privilege of individual trxType or trxTypes to different group user.
The following trxType are configurable:
"POST",
"ANNOUNCE",
"REQ_BLOCK_FORWARD",
"REQ_BLOCK_BACKWARD",
"BLOCK_SYNCED"
"BLOCK_PRODUCED"
"ASK_PEERID"
Each trx type has their own allow/deny list and "follow" rule for the user(pubkey) NOT in allow/deny list, the authentication mode following the rules below:
- If a pubkey is in ALLOW list, send this type of trx is ALLOWED for this pubkey.
- If a pubkey is in DENY list, producer/owner will REJECT all trx with this trxtype sent from this pubkey.
- If a pubkey is not in allow/deny list, the following rules apply:
- a. if trxtype is set to "FOLLOW_ALW_LIST", since the pubkey IS NOT in allow list, access will NOT be granted.
- b. if trxtype is set to "FOLLOW_DNY_LIST", since the pubkey IS NOT in deny list, access will be granted.
For a pubkey is denied, it CAN STILL send a trx with certain trxtype and get the trx_id, BUT owner/producer will reject this trx, that means the trx will NOT be packaged in to a block and broadcast to the group.
Rule 1 has the highest priority and Rule 3 has the lowest.
For example, a trxtype (such as "POST") of a group is set to "FOLLOW_DNY_LIST" or "FOLLOW_ALW_LIST" , whatever, and a pubkey was both added to the denylist/allowlist. In this case, rule 1 is in effect, so the pubkey can send trxs of the trxtype ("POST") to the group and these trxs will be packaged in to blocks.
为了让权限管理具备最好的灵活性,以上 3 条规则的优先级,由高到低生效,即第 1 条最高,第 3 条最低。
设想一种特殊情况,某 pubkey 被同时加入 allow 名单和 deny 名单时,不管 authtype 是如何设置的,第 1 条已满足,于是第 1 条会生效。
产品实现时,最好避免把同一个 pubkey 同时加入 allow 名单和 deny 名单。以保持 authtype 和 allow/deny 名单简约、一致、明了。
Therefor the CLIENT APP should check the group authentication rules to give error message back to user.
API: */api/v1/group/<group_id>/trx/auth/<trx_type>
- Method: GET
- Usage : get the following rules (rules applied to user not allow/deny list) for certain trxType
- Params :
- group_id
- trx_type
Example:
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8003/api/v1/group/b3e1800a-af6e-4c67-af89-4ddcf831b6f7/trx/auth/post | jq
Result:
{
"TrxType": "POST",
"AuthType": "FOLLOW_ALW_LIST"
}
Params:
TrxType
: trx typeAuthtype
: auth type,FOLLOW_ALW_LIST
FOLLOW_DNY_LIST
API: v1/group/chainconfig
- Method: POST
- Usage : set the following rules (rules applied to user not allow/deny list) for certain trxType
- Params :
group_id
, group idtype
, chain config type, must be "set_trx_auth_mode"config
, config content, includes the following itemstrx_type
, type of trxtrx_auth_mode
, must be one of "follow_alw_list"/"follow_dny_list"
memo
, memo
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"set_trx_auth_mode", "config":"{\"trx_type\":\"POST\", \"trx_auth_mode\":\"follow_dny_list\"}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
{
"group_id": "b3e1800a-af6e-4c67-af89-4ddcf831b6f7",
"type": "set_trx_auth_mode",
"config": "{\"trx_type\":\"POST\", \"trx_auth_mode\":\"follow_dny_list\"}",
"Memo": "Memo"
}
Result:
{
"group_id": "b3e1800a-af6e-4c67-af89-4ddcf831b6f7",
"owner_pubkey": "CAISIQPLW/J9xgdMWoJxFttChoGOOld8TpChnGFFyPADGL+0JA==",
"sign": "30440220089276796ceeef3a2c413bd89249475c2ecd8be4f2cb0ee3d19903fc45a7386b02206561bfdfb0338a9d022619dd8064e9a3496c1ea768f344e3c3850f8a907cdc73",
"trx_id": "90e9818a-2e23-4248-93e3-d4ba1b100f4f"
}
Params:
group_id
, group idowner_pubkey
, group owner pubkeysign
: owner signaturetrx_id
, trx id
API: v1/group/chainconfig
- Method: POST
- Usage : Update allow/deny list
- Params :
group_id
, group idtype
, chain config type, must be "upd_alw_list"/"upd_dny_list"config
, config content, includes the following itemsaction
, type of operation, must be "add"/"remove"pubkey
, pubkey to add/removetrx_type
, type of trx (can be array)
memo
, memo
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"upd_alw_list", "config":"{\"action\":\"add\", \"pubkey\":\"CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==\", \"trx_type\":[\"post\", \"announce\", \"req_block_forward\", \"req_block_backward\", \"ask_peerid\"]}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
{
"group_id": "b3e1800a-af6e-4c67-af89-4ddcf831b6f7",
"type": "upd_alw_list",
"config": "{\"action\":\"add\", \"pubkey\":\"CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==\", \"trx_type\":[\"post\", \"announce\", \"req_block_forward\", \"req_block_backward\", \"ask_peerid\"]}",
"Memo": "Memo"
}
Result:
{
"group_id": "b3e1800a-af6e-4c67-af89-4ddcf831b6f7",
"owner_pubkey": "CAISIQPLW/J9xgdMWoJxFttChoGOOld8TpChnGFFyPADGL+0JA==",
"sign": "30440220089276796ceeef3a2c413bd89249475c2ecd8be4f2cb0ee3d19903fc45a7386b02206561bfdfb0338a9d022619dd8064e9a3496c1ea768f344e3c3850f8a907cdc73",
"trx_id": "90e9818a-2e23-4248-93e3-d4ba1b100f4f"
}
Params:
group_id
, group idowner_pubkey
, group owner pubkeysign
: owner signaturetrx_id
, trx id
API: v1/group/<group_id>/trx/allowlist
API: v1/group/<group_id>/trx/denylist
- Method: Get
- Usage : Get allow/deny list
- Params :
- group_id, group id
Example:
Get group allow list
curl -X GET -H 'Content-Type: application/json' -d '' http://127.0.0.1:8003/api/v1/group/b3e1800a-af6e-4c67-af89-4ddcf831b6f7/trx/allowlist
Result:
[
{
"Pubkey": "CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==",
"TrxType": [
"POST",
"ANNOUNCE",
"REQ_BLOCK_FORWARD",
"REQ_BLOCK_BACKWARD",
"ASK_PEERID"
],
"GroupOwnerPubkey": "CAISIQPLW/J9xgdMWoJxFttChoGOOld8TpChnGFFyPADGL+0JA==",
"GroupOwnerSign": "304502210084bc833278dc98be6f279540b571ad5402f5c2d1e978c4c2298cddb079ca312002205f9374b9d27c628815aecff4ffe11329b17b8be12687223a072afa58e9f15f2c",
"TimeStamp": 1642609852758917000,
"Memo": "Memo"
}
]
params:
- Pubkey: pubkey
- TrxType: trxType allowd(denied) for this pubkey
- GroupOwnerPubkey
- GroupOwnerSign
- TimeStmap
- Memo
- DenyList API is no longer available
- All trxType is set to "FOLLOW_DNY_LIST" by default, that means if the FOLLOW rules are not changed, all user can send certain type of trx unless been put to DENY list
- no need to change FOLLOW rules for ANY trxType
- Add the following trxType of the pubkey to DENY list
- "POST"
- "ANNOUNCE"
- "REQ_BLOCK_FORWARD"
- "REQ_BLOCK_BACKWARD"
- "ASK_PEERID"
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"upd_dny_list", "config":"{\"action\":\"add\", \"pubkey\":\"CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==\", \"trx_type\":[\"post\", \"announce\", \"req_block_forward\", \"req_block_backward\", \"ask_peerid\"]}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
- Remove pubkey for all certain trxTypes from DENY list
- "POST"
- "ANNOUNCE"
- "REQ_BLOCK_FORWARD"
- "REQ_BLOCK_BACKWARD"
- "ASK_PEERID"
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"upd_dny_list", "config":"{\"action\":\"add\", \"pubkey\":\"CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==\", \"trx_type\":[]}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"set_trx_auth_mode", "config":"{\"trx_type\":\"POST\", \"trx_auth_mode\":\"follow_alw_list\"}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
NOW ONLY PUBKEY IN ALLOW LIST CAN POST.
Example:
curl -X POST -H 'Content-Type: application/json' -d '{"group_id":"b3e1800a-af6e-4c67-af89-4ddcf831b6f7", "type":"upd_alw_list", "config":"{\"action\":\"add\", \"pubkey\":\"CAISIQNGAO67UTFSuWzySHKdy4IjBI/Q5XDMELPUSxHpBwQDcQ==\", \"trx_type\":[\"post\"]}", "Memo":"Memo"}' http://127.0.0.1:8003/api/v1/group/chainconfig | jq
NOW owner can send POST, all other pubkeys are denied.
Same trxType for a pubkey can be added to both allow list and deny list, that is with reason and by design. Allow list alway has "higher" privilege than deny list, that means, if same trxType appears on both list, ACCESS WILL BE GRANTED. To make less confuse, client should manage authenticate rules carefully.
To speed up new node initial synchorize, group owner will "boardcast" a snapshot every 1 minute to all node connected. The snapshot includes the latest configuration infomation of a group, includes:
- all added user(s)
- all added producer(s)
- all chain config
- all app config
- all announced user or producer
After a node received snapshot from owner(wether in syncing or not), it will apply all configuration info to local db.That means the node have all up-to-date configuration and no need to wait block syncing finished. Client app should check snapshot "tag" by using getGroups API. A new section is added to group info.
example:
curl -X GET -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8004/api/v1/groups | jq
{
"groups": [
{
"group_id": "0b0c5c0c-26a1-44e5-9edb-ace5ea6e0e47",
"group_name": "my_test_group",
"owner_pubkey": "CAISIQMuaL8Y5TxbaO0ult7BTjYYhgrteewJANQGt/CYANjxQA==",
"user_pubkey": "CAISIQM2e3Pdt1wBeoDhYaOW+QocCZwTk37HBaGhuJsiRjdK+A==",
"consensus_type": "POA",
"encryption_type": "PUBLIC",
"cipher_key": "578aa2d4f97be40dc254a25b621836d248c027ae2fd3bf86aeaa9fed462b4f02",
"app_key": "test_app",
"last_updated": 1649096357747287600,
"highest_height": 0,
"highest_block_id": "5f3a1cb7-822a-4fb8-ae84-864c496ab8de",
"group_status": "IDLE",
"snapshot_info": {
"TimeStamp": 1649096392051580700,
"HighestHeight": 0,
"HighestBlockId": "5f3a1cb7-822a-4fb8-ae84-864c496ab8de",
"Nonce": 19500,
"SnapshotPackageId": "9d381d97-1cf4-4d13-9f95-b816f01f4df5",
"SenderPubkey": "CAISIQMuaL8Y5TxbaO0ult7BTjYYhgrteewJANQGt/CYANjxQA=="
}
}
]
}
parameters:
- "TimeStamp": owner time stamp
- "HighestHeight": current group highest height from owner
- "HighestBlockId": current group highest block id
- "Nonce": nonce, increase continuly
- "SnapshotPackageId": snapshot id
- "SenderPubkey": group owner pubkcy
After join a new group, syncing block will start automatically and the snapshot_info section will return 'nil' before a valided snapshot is received and applied. For app development, we suggest wait till a valid snapshot is received before allow user use any function provided by the app.
Owner will send snapshot every 1 minute regardless if there is anything changed, client node will check the snapshot and APPLY IT ONLY IF SOMETHING CHANGED, but the snapshot tag will be UPDATED ACCORDING TO LATEST SNAPSHOT RECEIVED.
Snapshot rule doesn't apply to a producer node. A producer node still need wait till block syncing finished to make new block.
pubqueue keeps track of the status of user-sent TRXs, TRXs that succeed will be marked, and failed TRXs will be retried until the 10th failure.
By default, after a client sending TRXs, the client needs to query the corresponding TRXs' follow-up status via pubqueue, and for TRXs that succeed or failed 10 times, the client should ACK these TRXs with specific API (otherwise they will consume your system resources).
If you don't care about the state of TRXs, quorum provides an autoack feature by adding the command line argument -autoack=true
when starting quorum.
There are two interfaces involved here.
- GET
/v1/group/:group_id/pubqueue
example
curl "http://localhost:8004/api/v1/group/6bd70de8-addc-4b03-8271-a5a5b02d1ebd/pubqueue" | jq
{
"GroupId": "6bd70de8-addc-4b03-8271-a5a5b02d1ebd",
"Data": [
{
"GroupId": "6bd70de8-addc-4b03-8271-a5a5b02d1ebd",
"State": "SUCCESS",
"RetryCount": 0,
"UpdateAt": "1650786473293614500",
"Trx": {
"TrxId": "b5433111-f3a1-41e2-a03f-648e47a04dad",
"GroupId": "6bd70de8-addc-4b03-8271-a5a5b02d1ebd",
"Data": "jvFEEhBuwRpu7or2IUt8NdTZ1R/qzlXeJeseU7csZi+XYC28Fufj3aORoKVCAXyxBxZCuHe7kp6tKAScNxClqEX82+As+fKsBK6zTpB9gyO+fn2y",
"TimeStamp": "1650532131665550100",
"Version": "1.0.0",
"Expired": 1650532161665550000,
"Nonce": 24000,
"SenderPubkey": "CAISIQMrNsVK8/ZrJylBFJZEe6BnslK7B5wAygbxde+RG9Hafg==",
"SenderSign": "MEQCIDZlG/ILNC89z/OYEuADqYpHfx81pqA3RnOlLSCeypP3AiAFKLSD8M8TyNr6quYFCnuL1nzMwUlHWiEiVimDFCHlmQ=="
},
"StorageType": "CHAIN"
}
]
}
- POST
POST /v1/trx/ack
It will return the TRXs that are succeefully acked.
example
curl -X POST -H 'Content-Type: application/json' -d '{"trx_ids": ["b5433111-f3a1-41e2-a03f-648e47a04dad"]}' "http://localhost:8004/api/v1/trx/ack"
["b5433111-f3a1-41e2-a03f-648e47a04dad"]
string
可以通过 API: List all groups 查询 node 所加入的所有 groups (包括自己创建的) 信息
string, group name
create a group 时的必填字段
可以通过 API: List all groups 查询 node 所加入的所有 groups (包括自己创建的) 信息
可以通过 gettrx API 获取具体内容
one block can have a number of trxs.
节点的 node_id
- 之前的 user_id 取消了(实际上是 peer_id)
- 前端可以用 pubkey 来当 user_id(唯一标识)
peer_id (可以通过节点信息 API 获得)
owner_pubkey: public key of group owner (ecdsa)
user_pubkey: public key of group user *
When join a new group, a user public key will be created for this group, for group owner, user_pubkey is as same as owner_pubkey
user_pubkey 是用户在组内的唯一身份标识,也用来进行签名
status of group, a group can have 3 different status:
- SYNCING
- SYNC_FAILED
- IDLE
for detail please check RUM design document.
string, group app key, requested, length should between 5 to 20
string, group consensus type, must be "poa", requested
"poa" or "pos" or "pow", "poa" only for now
链上共识方式,参见 RUM 设计文档
string, requested, encryption type of group, must be "public" or "private"
string, can be one of these:
- "POST"
- "ANNOUNCE"
- "REQ_BLOCK_FORWARD"
- "REQ_BLOCK_BACKWARD"
- "BLOCK_SYNCED"
- "BLOCK_PRODUCED"
- "ASK_PEERID"
auth type, string, can be one of these:
- "FOLLOW_ALW_LIST"
- "FOLLOW_DNY_LIST"