VIZ 开发手册

VIZ 开发手册为开发者提供了 VIZ 区块链中 API/对象/结构的描述、常用场景的代码示例以及低级交易构建指南。


目录:

节点类型

VIZ节点是区块链的核心,是逐块处理、执行所有操作并存储系统状态的软件。所有者配置节点,选择使用的插件。包含的插件及其设置取决于节点能够提供哪些功能。


见证节点(委托节点)

委托者负责区块的生成,因此最重要的节点是委托节点。它们与其他节点交换数据、收集交易,当轮到委托者(服务器所有者)生成区块时,将收集到的区块进行签名并传输给其他节点。区块必须在分配给委托者的3秒内完成签名和传递。错过区块会导致交易执行延迟,并在一段时间内对委托者进行惩罚(惩罚会影响其他用户为该委托者投票的总权重)。这使得系统能够暂时将服务器或数据中心出现问题的委托者在队列中降级,从而保护网络的可靠性。

通常,委托者会运行两个节点,一个主节点和一个备用节点。它们通常位于不同的数据中心,彼此独立。如果主节点出现问题,委托者会将区块签名密钥更改为备用密钥,备用节点将负责生成区块。

关键插件:chain p2p json_rpc webserver witness network_broadcast_api database_api witness_api

种子节点

任何区块链系统运行的基础都是点对点(p2p)连接和数据交换。种子节点的特殊之处在于它们承担着重要角色——接收和分发区块,从而减轻整个网络的带宽负担,并降低地理位置上邻近连接的延迟。运行种子节点没有经济利益,因为服务器需要成本,但不会给所有者带来任何收益。因此,如果财务上允许,大多数种子节点由委托者(见证人)运行。正是通过节点的API来请求最新信息、账户状态、操作历史或委员会中的申请。

关键插件:chain p2p

API节点

我们提供的一部分插件是面向开发者及其用户的API。如果节点启用了所有可用插件并存储从第一个区块开始的历史记录,则通常称为完整节点。由于插件不仅提供对系统状态的访问,还会形成自己的数据结构,因此对服务器资源(尤其是RAM,因为节点将ChainBase数据库存储在RAM中)有更高要求。正是通过节点的API来请求最新信息、账户状态、操作历史或委员会中的申请。

此类插件的示例:

  • network_broadcast_api - 向网络发送交易;
  • database_api — 提供对系统状态访问的主要插件,获取账户数据、区块信息、网络参数;
  • custom_protocol_api - 用于记录账户最近自定义操作的计数器和区块高度的插件(为账户存储的自定义操作ID数量由配置文件中的custom-protocol-store-size参数设置);
  • account_history - 获取与账户相关的操作列表;
  • committee_api - 按状态获取申请列表,获取申请信息,获取申请投票列表;
  • invite_api - 按状态获取邀请列表,通过ID或密钥请求邀请信息;
  • operation_history - 获取区块中的操作信息;
  • paid_subscription_api - 获取付费订阅信息,账户间达成的协议;
  • witness_api - 获取委托者列表、委托者队列、特定委托者信息及其投票的网络参数。

API节点可以是私有的(在设置中指定了登录名和密码访问参数),也可以是公共的(当API访问对所有人开放且节点地址公开已知时)。通常,公共API节点简称为公共节点。更多信息请参阅插件及其API

经济模型

VIZ区块链创建了所谓的可预测经济条件。如果其他系统采用递减通胀的原则,这使得在不同时间加入系统的参与者处于不平等条件,那么VIZ则采用固定通胀和1年为一轮的模式(10512000个区块)。


通胀模型

在链启动时,分配了5000万viz。每轮基于10%的固定通胀率。启动一年后,网络中有5500万viz,这意味着下一轮的通胀已经从5500万viz计算,导致第二年发行550万viz。

由于固定通胀,预测每个区块的viz发行量成为可能。例如在第二年,每个区块发行 5500000 / 10512000 = 0.523 viz。这个数字直到下一轮开始都不会改变。

2023年经济模型发生了变化:改为每个区块固定发行1 viz。

代币发行分配

现有的VIZ模型规定由网络委托者管理经济。由网络参与者选举的委托者对网络参数进行投票,其中包括代币发行分配参数:

  • inflation_witness_percent — 经济参数,设定用于奖励委托者维护区块链系统基础设施的发行百分比;
  • inflation_ratio_committee_vs_reward_fund — 经济参数,决定发送到DAO基金和奖励基金的发行比例。

您可以通过向database_api插件执行API请求get_chain_properties通过control.viz.world网站的网络浏览器了解参数的当前值。

DAO基金

DAO基金收集viz代币,为生态系统发展倡议提供资金。如果网络参与者决定举办竞赛、开发应用程序、为整个网络谋福利——他可以申请从基金获得资金。任何网络成员都可以投票反对或支持申请的全部或部分执行。当申请审议时间到来时,将计算所有参与投票的网络成员的份额并做出决定。

奖励基金

每个网络参与者都有一个随时间可再生的资源——能量。消耗的能量以每5天100个百分点的速度补充(每天20个百分点,每小时0.83个百分点等)。账户的能量不能超过100%。

当VIZ参与者决定奖励某人时,他指明想要用于奖励的账户能量百分比。区块链会考虑参与者的社会资本规模和用于奖励的能量,并将它们与最近5天内所有参与者的奖励总额相关联。最终,目标账户按接收者的社会资本和消耗能量比例从发行中获得社会资本奖励。这确保了平等竞争地访问奖励基金。网络参与者自行决定奖励谁以及为何奖励,开启了自由选择的可能性并激励任何行动和倡议。

例如:

  • 故事作者及其读者可以奖励发布角色插画的插画师;
  • 在问答网站上,参与者可以奖励帮助解决问题和理解问题的人;
  • 观众可以奖励视频博主的有趣主题或教学课程。

自治

整个VIZ经济由社会资本所有者管理。正是他们,如果同意委托者的愿景和系统管理模型,就会为委托者投票(如果没有可投票的人,任何人都可以自己成为委托者并获得其他参与者的投票)。奖励基金和DAO基金按照公平、共享的管理模式运作。

节点类型

VIZ节点是区块链的核心,是逐块处理、执行所有操作并存储系统状态的软件。所有者配置节点,选择使用的插件。包含的插件及其设置取决于节点能够提供哪些功能。


见证节点(委托节点)

委托者负责区块的生成,因此最重要的节点是委托节点。它们与其他节点交换数据、收集交易,当轮到委托者(服务器所有者)生成区块时,将收集到的区块进行签名并传输给其他节点。区块必须在分配给委托者的3秒内完成签名和传递。错过区块会导致交易执行延迟,并在一段时间内对委托者进行惩罚(惩罚会影响其他用户为该委托者投票的总权重)。这使得系统能够暂时将服务器或数据中心出现问题的委托者在队列中降级,从而保护网络的可靠性。

通常,委托者会运行两个节点,一个主节点和一个备用节点。它们通常位于不同的数据中心,彼此独立。如果主节点出现问题,委托者会将区块签名密钥更改为备用密钥,备用节点将负责生成区块。

关键插件:chain p2p json_rpc webserver witness network_broadcast_api database_api witness_api

种子节点

任何区块链系统运行的基础都是点对点(p2p)连接和数据交换。种子节点的特殊之处在于它们承担着重要角色——接收和分发区块,从而减轻整个网络的带宽负担,并降低地理位置上邻近连接的延迟。运行种子节点没有经济利益,因为服务器需要成本,但不会给所有者带来任何收益。因此,如果财务上允许,大多数种子节点由委托者(见证人)运行。正是通过节点的API来请求最新信息、账户状态、操作历史或委员会中的申请。

关键插件:chain p2p

API节点

我们提供的一部分插件是面向开发者及其用户的API。如果节点启用了所有可用插件并存储从第一个区块开始的历史记录,则通常称为完整节点。由于插件不仅提供对系统状态的访问,还会形成自己的数据结构,因此对服务器资源(尤其是RAM,因为节点将ChainBase数据库存储在RAM中)有更高要求。正是通过节点的API来请求最新信息、账户状态、操作历史或委员会中的申请。

此类插件的示例:

  • network_broadcast_api - 向网络发送交易;
  • database_api — 提供对系统状态访问的主要插件,获取账户数据、区块信息、网络参数;
  • custom_protocol_api - 用于记录账户最近自定义操作的计数器和区块高度的插件(为账户存储的自定义操作ID数量由配置文件中的custom-protocol-store-size参数设置);
  • account_history - 获取与账户相关的操作列表;
  • committee_api - 按状态获取申请列表,获取申请信息,获取申请投票列表;
  • invite_api - 按状态获取邀请列表,通过ID或密钥请求邀请信息;
  • operation_history - 获取区块中的操作信息;
  • paid_subscription_api - 获取付费订阅信息,账户间达成的协议;
  • witness_api - 获取委托者列表、委托者队列、特定委托者信息及其投票的网络参数。

API节点可以是私有的(在设置中指定了登录名和密码访问参数),也可以是公共的(当API访问对所有人开放且节点地址公开已知时)。通常,公共API节点简称为公共节点。更多信息请参阅插件及其API

操作及其类型

VIZ区块链中的操作分为普通操作、已废弃操作和虚拟操作。普通操作通过网络参与者签名的交易进入区块链。已废弃操作由于网络发展和内部机制变化而被禁用。

部分操作属于重型操作(数据操作),它们会受到额外的带宽因子影响(网络参数data_operations_cost_additional_bandwidth的投票参数)。

虚拟操作在代码中特定条件触发时由节点生成,主要为网络参与者提供信息。例如,来自网络成员的奖励操作包含奖励的目的和账户消耗的能量。它不存储接收者将从奖励基金获得的奖励金额。正是针对这种情况需要虚拟操作。在奖励情况下,将生成虚拟操作receive_award,在shares字段中包含收到的奖励。


操作编号

在VIZ协议中存在操作编号(从零开始),包括普通操作和虚拟操作:

  • 0, vote, 已废弃操作;
  • 1, content, 已废弃操作;
  • 2, transfer, VIZ代币转账;
  • 3, transfer_to_vesting, 将VIZ代币转换为SHARES网络份额;
  • 4, withdraw_vesting, 将SHARES转换为VIZ(在28天内按总额的1/28等额提取);
  • 5, account_update, 更新账户访问权限;
  • 6, witness_update, 为委托者设置密钥(或声明成为委托者的意向);
  • 7, account_witness_vote, 为委托者投票;
  • 8, account_witness_proxy, 转让委托者投票权(含份额);
  • 9, delete_content, 已废弃操作;
  • 10, custom, 向VIZ区块链系统发送包含JSON格式内容的公共字符串;
  • 11, set_withdraw_vesting_route, 设置将SHARES转换为VIZ代币时的份额提取方向;
  • 12, request_account_recovery, 通过信任账户请求恢复访问权限;
  • 13, recover_account, 信任账户满足访问恢复请求;
  • 14, change_recovery_account, - 更改用于在丢失访问权限时恢复账户的信任账户;
  • 15, escrow_transfer, 通过中介创建交易;
  • 16, escrow_dispute, 向中介请求解决交易双方争议;
  • 17, escrow_release, 从交易中释放代币;
  • 18, escrow_approve, 确认交易完成;
  • 19, delegate_vesting_shares, 将份额委托给其他参与者;
  • 20, account_create, 创建新账户;
  • 21, account_metadata, 更新公共账户元数据;
  • 22, proposal_create, 创建签名提议;
  • 23, proposal_update, 更新提议(提供签名);
  • 24, proposal_delete, 删除提议;
  • 25, chain_properties_update, 已废弃操作,已被versioned_chain_properties_update取代;
  • 26, author_reward, 已废弃虚拟操作;
  • 27, curation_reward, 已废弃虚拟操作;
  • 28, content_reward, 已废弃虚拟操作;
  • 29, fill_vesting_withdraw, 虚拟操作,包含将SHARES转换为VIZ的信息;
  • 30, shutdown_witness, 虚拟操作,在委托者因错过大量区块而被禁用时调用;
  • 31, hardfork, 虚拟操作,在发生硬分叉(更新区块链版本)时调用;
  • 32, content_payout_update, 已废弃虚拟操作;
  • 33, content_benefactor_reward, 已废弃虚拟操作;
  • 34, return_vesting_delegation, 虚拟操作,在委托份额返还时发生;
  • 35, committee_worker_create_request, 向委员会创建申请;
  • 36, committee_worker_cancel_request, 取消委员会中的申请;
  • 37, committee_vote_request, 对委员会中的申请进行投票;
  • 38, committee_cancel_request, 虚拟操作,申请被委员会拒绝;
  • 39, committee_approve_request, 虚拟操作,申请被委员会批准;
  • 40, committee_payout_request, 虚拟操作,申请获得委员会全额支付;
  • 41, committee_pay_request, 虚拟操作,申请获得委员会支付;
  • 42, witness_reward, 虚拟操作,委托者因签名区块获得的奖励;
  • 43, create_invite, 创建邀请码;
  • 44, claim_invite_balance, 将邀请码兑换到账户余额;
  • 45, invite_registration, 使用邀请码注册;
  • 46, versioned_chain_properties_update, 由委托者设置网络投票参数;
  • 47, award, 奖励网络参与者;
  • 48, receive_award, 虚拟操作,接收奖励;
  • 49, benefactor_award, 虚拟操作,接收受益人奖励;
  • 50, set_paid_subscription, 设置定期支付协议条款;
  • 51, paid_subscribe, 签署协议条款;
  • 52, paid_subscription_action, 虚拟操作,支付定期款项;
  • 53, cancel_paid_subscription, 虚拟操作,终止定期支付;
  • 54, set_account_price, 设置账户价格;
  • 55, set_subaccount_price, 设置子账户价格;
  • 56, buy_account, 购买账户;
  • 57, account_sale, 虚拟操作,账户购买记录;
  • 58, use_invite_balance, 使用邀请码余额转换为账户的网络份额;
  • 59, expire_escrow_ratification, 虚拟操作,通过中介的交易批准过期;

操作编号用于低层级交易形成及其签名(更多信息请参阅交易形成部分)。

VIZ中的对象和结构

考虑VIZ时,需要将协议的对象和结构(操作、交易、区块、硬分叉、版本、权限)与直接存在于区块链中的对象和结构(受特定操作影响的对象)区分开来。


协议对象和结构列表

所有与协议相关的内容都位于VIZ区块链C++节点的源代码的/libraries/protocol目录中。

  • types — 协议中的数据类型
  • operations / proposal_operations / chain_operations / chain_virtual_operations — 所有与操作及其处理相关的内容;
  • transaction — 所有与交易相关的内容(id、操作列表、引用的区块);
  • block_header / block — 包含交易,引用前一个区块,包含extensions,委托者可以使用它来发起切换到新版本硬分叉的投票;
  • asset — VIZ中的代币结构(VIZ和SHARES,不同类别资产之间的比率);
  • base / version — 描述网络协议版本、投票和切换到新版本时间的结构;
  • authority — 描述特定类型账户访问的密钥集的结构;
  • sign_state — 用于验证签名(或可以生成签名的密钥的存在)的辅助工具。

区块链中的对象和结构

系统状态正是由区块链本身的对象和结构组成。每个包含操作的区块都由主数据库模块处理,该模块计算所有更改并就延迟操作做出决策。在/libraries/chain/include/graphene/chain目录中,既包含对象和数据结构,也包含区块链的内部结构(评估器、block_log、dynamic_global_property_object、对象类型)。

系统状态由以下对象组成:

  • dynamic_global_property_object — 包含关于当前经济状态和节点状态数据的主要对象(例如,不可逆区块的编号);
  • account_object — 账户记录;
  • account_authority_object — 账户权限记录;
  • witness_object — 委托者记录;
  • transaction_object — 用于队列中的交易(这允许检查新交易是否有重复项,并在expire过期前未执行时从队列中删除交易);
  • block_summary_object — 用于索引区块及其哈希,用于检查TaPoS(交易必须引用前一个区块,检查正是通过由block_summary_object对象构建的索引进行);
  • witness_schedule_object — 委托者队列状态;
  • witness_vote_object — 委托者投票记录;
  • hardfork_property_object — 关于当前网络硬分叉的记录;
  • withdraw_vesting_route_object — 关于转换份额时代币分配路由的记录;
  • master_authority_history_object — 关于主权限更改的记录;
  • account_recovery_request_object — 账户恢复请求;
  • change_recovery_account_request_object — 更改用于恢复访问的信任账户的请求;
  • escrow_object — 三方交易记录;
  • vesting_delegation_object — 关于委托份额的记录;
  • vesting_delegation_expiration_object — 关于委托取消后应返还的委托份额的记录;
  • account_metadata_object — 包含账户元数据的单独记录;
  • proposal_objectproposal操作的记录;
  • required_approval_objectproposal操作所需确认的记录;
  • committee_request_object — 向委员会申请的记录;
  • committee_vote_object — 委员会申请投票的记录;
  • invite_object — 所有邀请的记录;
  • award_shares_expire_object — 应在期限结束时降低竞争的奖励记录;
  • paid_subscription_object — 关于付费订阅的信息;
  • paid_subscribe_object — 已发出的付费订阅记录;
  • witness_penalty_expire_object — 对错过区块的委托者的惩罚记录。

API插件中的对象和结构

提供API的插件可以返回来自区块链的对象,也可以返回自己的对象。通过id获取对象的简单请求按原样返回数据,通常通过类似API的构造函数传递来自区块链的对象,以复制状态并将其提供给用户,例如:witness_api插件使用单独的witness_api_object对象。而database_api插件使用account_api_object,它通过从索引中复制当前权限来补充标准的区块链对象account。

如果插件扩展了标准的索引表和对象,它会创建一个新结构,单独保存操作记录并填充索引。例如,private_message插件通过处理custom操作(创建填充message_index索引的message_object对象)来实现这一点。

网络投票参数

委托者广播他们对网络投票参数的立场。区块链系统每经过一个委托者队列周期(21个区块)计算一次投票参数的中位数值,并在该周期内固定这些值。参数描述(括号中为撰写本节时的中位数值):

  • account_creation_fee — 创建账户时转移的手续费(1.000 VIZ);
  • create_account_delegation_ratio — 创建账户时的委托保证金系数(x10);
  • create_account_delegation_time — 创建账户时的委托时间(2592000秒);
  • bandwidth_reserve_percent — 分配给备用带宽的网络份额(0.01%);
  • bandwidth_reserve_below — 备用带宽,对网络份额达到阈值的账户有效(1.000000 SHARES);
  • committee_request_approve_min_percent — 对委员会申请做出决策所需的最低投票网络百分比(10%);
  • min_delegation — 委托的最小代币数量(1,000 VIZ);
  • vote_accounting_min_rshares — 奖励时考虑的最小投票权重(5000000 rushares);
  • maximum_block_size — 网络中的最大区块大小(65536字节);
  • inflation_witness_percent — 用于奖励委托者的通胀份额(20%);
  • inflation_ratio_committee_vs_reward_fund — 委员会和奖励基金之间通胀余额的分配比率(75%);
  • inflation_recalc_period — 通胀模型重新计算之间的区块数(806400);
  • data_operations_cost_additional_bandwidth — 交易中每个数据操作的额外带宽标记(交易大小的0%);
  • witness_miss_penalty_percent — 委托者因错过区块而受到的惩罚,占投票总权重的百分比(1%);
  • witness_miss_penalty_duration — 委托者因错过区块而受到惩罚的持续时间(秒)(86400秒);
  • create_invite_min_balance — 最小支票金额(10,000 VIZ);
  • committee_create_request_fee — 向DAO基金创建申请的费用(100.000 VIZ);
  • create_paid_subscription_fee — 创建付费订阅的费用(100.000 VIZ);
  • account_on_sale_fee — 将账户挂牌出售的费用(10.000 VIZ);
  • subaccount_on_sale_fee — 将子账户挂牌出售的费用(100.000 VIZ);
  • witness_declaration_fee — 声明账户为委托者的费用(10.000 VIZ);
  • withdraw_intervals — 资本减少的周期(天)数(28)。

服务账户

在VIZ中,存在服务账户,这些账户具有嵌入配置文件中的特定权限的访问密钥:

  • null — 一个专门用于销毁接收到的代币的账户。它具有空权限,代币销毁机制嵌入在区块链的源代码中。
  • committee — 一个专门将所有接收到的代币转移到委员会基金的账户,从而允许为生态系统发展捐赠代币。具有空权限。它也是网络的发起者,用于区块链的匿名创世(当committee账户的区块签名密钥在配置文件中对所有人可用时)。签名密钥由字符串commiteevizsign的串联形成,对应5Hw9YPABaFxa2LooiANLrhUK5TPryy8f7v9Y1rk923PuYqbYdfC。启动网络并连接其他委托者后,匿名用户可以停止使用committee账户签名,签名密钥重置为VIZ111111111111111111111111111114T1Anm值。
  • anonymous — 一个专门账户,当使用指定密钥(以及登录名,如果需要)接收代币时,创建一个匿名账户(匿名性由交易所和社交、托管服务的网关可能进行的转账提供)。具有空权限。注册匿名账户的memo备注格式为login:public_key,其中login是新账户所需的登录名,public_key是用于所有权限类型的单一公钥。如果未指定登录名,且备注中仅指定了public_key,则会创建一个格式为nX.anonymous的匿名子账户,其中X是anonymous账户json_metadata中指定的数字增量。注意! 如果未指定备注,资金将被销毁,类似于向null账户转账。
  • invite — 一个专门用于能够匿名激活邀请码的账户,无需在区块链中拥有账户。活跃密钥在配置文件中对所有人可用,由字符串invitevizactive的串联形成,对应5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW。最初,它没有任何用于进行交易的份额,可以通过委托或由委托者启用备用带宽系统来纠正。
  • viz — 创世区块中的链发起者账户,私钥5JabcrvaLnBTCkCVFX5r4rmeGGfuJuVp4NAKRNLTey6pxhRQmf4,在以10,000 VIZ预安装子账户销售(接收者为committee)后重置为空。

系统状态

系统状态是节点运行所需的最小数据量。VIZ所基于的Graphene代码库在每个符合共识的区块中执行来自交易的操作(委托队列、签名匹配)。每个区块都会处理数据和待处理操作,这导致了系统状态的迭代本质。部分数据会永久存储,这对运行节点的服务器设备提出了特定要求。

区块链中的对象和结构部分描述了构成系统状态的大部分对象。


动态全局属性对象(dgpo)

该对象存储网络的主要属性,包含流通代币信息和其他重要信息。在源代码中,它通常表示为dgp变量,随着每次迭代修改其状态,因此动态全局属性可以确信地被视为系统状态中最重要的部分。让我们看看其公共属性,可通过访问database_api插件的get_dynamic_global_properties方法获取:

  • current_witness(示例值:"solox")- 当前区块的委托者;
  • head_block_number(示例值:10829669)- 当前区块号;
  • head_block_id(示例值:00a53f658226b2a6f3f75fc8185d884d029f50bf)- 当前区块的ID(哈希);
  • time(示例值:"2019-10-11T08:49:21")- 当前区块的生成时间;
  • last_irreversible_block_num(示例值:10829651)- 最后一个不可逆区块的编号;
  • genesis_time(示例值:"2018-09-29T10:23:24")- 网络第一个区块的生成时间;
  • current_supply(示例值:"55159321.957 VIZ")- 系统中VIZ代币的总数量;
  • total_vesting_fund(示例值:"27924855.425 VIZ")- 转换为网络股份(SHARES)的VIZ代币数量;
  • total_vesting_shares(示例值:"27924847.935329 SHARES")- 网络股份的通用量化度量(以SHARES表示);
  • committee_fund(示例值:"1423813.837 VIZ")- 委员会资金的余额;
  • committee_requests(示例值:39)- 向委员会申请的总数;
  • total_reward_fund(示例值:"28216.906 VIZ")- 奖励资金的余额;
  • total_reward_shares(示例值:"6019776735774")- 竞争奖励资金的量化度量;
  • current_aslot(示例值:10855719)- 当前委托者签名槽的编号(包含网络启动以来的槽编号,包括委托者跳过的区块);
  • recent_slots_filled(示例值:340282366920938463463374607431768211455)- 用于计算参与区块签名的委托者百分比;
  • participation_count(示例值:128)- 需要除以128以获得参与区块签名的委托者百分比;
  • maximum_block_size(示例值:65536)- 最大区块大小(以字节为单位)(需投票决定的网络参数);
  • average_block_size(示例值:114)- 平均区块大小使用公式计算:average_block_size = (99 * average_block_size + new_block_size) / 100,用于更新current_reserve_ratio以维持网络带宽使用率在约50%或以下;
  • max_virtual_bandwidth(示例值:5986734968066277376)- 最大网络带宽使用公式maximum_block_size * CHAIN_BANDWIDTH_AVERAGE_WINDOW_SECONDS / CHAIN_BLOCK_INTERVAL计算,最大虚拟网络带宽根据公式max_bandwidth * current_reserve_ratio计算;
  • current_reserve_ratio(示例值:20000)- 每20个区块(1分钟)进行一次检查:average_block_size <= 25% maximum_block_size。如果条件满足,则该值增加1(线性增加,每个区块),但不超过CHAIN_MAX_RESERVE_RATIO(20000)。如果条件不满足,则current_reserve_ratio减半,这应立即减少网络负载,保护其免受使用大量交易的参与者的影响。换句话说,备用比率减半不会使网络使用量减半,但会限制那些已经尝试超过其带宽50%的用户。当备用比率从最大值减半(从20000变为10000)时,恢复总虚拟带宽大约需要7天时间;
  • bandwidth_reserve_candidates(示例值:1)- 带宽储备候选者的数量(默认加1个候选者);
  • inflation_calc_block_num(示例值:10315901)- 最后一次通胀分配计算的区块号;
  • inflation_witness_percent(示例值:2000)- 委托者因签名区块而获得的通胀百分比;
  • inflation_ratio(示例值:5000)- 发送到委员会资金的通胀与奖励资金的通胀比率百分比;
  • vote_regeneration_per_day(示例值:1)- 已弃用的属性。

交易的唯一性和TaPoS(权益交易证明)

节点在交易池中检查所有传入交易的唯一性。在expiration发生后(设置中由CHAIN_MAX_TIME_UNTIL_EXPIRATION常量限制为一小时),交易将从池中删除。

VIZ中的所有交易必须符合TaPoS概念,即引用先前的某个区块(ref_block_num为2字节表示(区块号十进制表示与十六进制ffff的二进制"与"操作)和ref_block_prefix由二进制哈希状态的第5、6、7、8字节的十进制表示按反向顺序组成),这使得交易发起者可以依赖其当前的系统状态,而无需担心不可逆区块。如果它依赖于随机小分叉中的系统状态,则交易不会进入主链。因此,网络参与者可以控制交易队列的执行并构建交互,而无需等待区块变为不可逆。这反过来又对此类操作的最终核算施加了限制,因此大多数重要交易对于验证方而言应已处于不可逆状态。

节点为block_summary_object分配了2字节维度的空间(以便通过二进制"与"操作与十六进制ffff处理的区块号适合0到65536的范围),并在接受新区块时循环覆盖此空间(以及block_summary_index索引)中的标识符(哈希)。65537个区块覆盖196611秒的时间间隔(约2.27天)。因此,新交易只能引用此空间中的区块,以便节点可以验证区块标识符(来自ref_block_num)与ref_block_prefix中的校验和是否对应。

可移植系统状态

如果在基于工作量证明的第一代区块链系统中需要存储所有区块和交易的标识符,那么随着数据量的增加,许多开发人员开始寻找减少存储数据成本的方法。而大部分数据恰好存储在区块及其包含的交易中。在当前分布式账本技术中,这个问题已经通过匹配不可逆状态和系统可移植状态得到解决。一些区块链系统正开始朝这个方向发展,例如,Steem提出了平台无关状态文件 – PISF的解决方案。新的区块链系统(以及一些早期的先驱,例如XRP Ledger节点 - rippled)在设计时已经考虑了系统的可移植状态,其架构允许从受信任的节点请求当前系统状态,跳过漫长的同步过程,无需下载整个区块链历史并独立处理所有交易。

VIZ未对Graphene系统状态的架构进行重大更改,因此由block_summary_object结构组成的block_summary_index索引存储了所有关于区块的信息(即特定区块的block_id_type,该类型已包含所有信息)。这使得创建可移植系统状态变得困难,因为此类状态的数据量将非常庞大。升级的唯一途径是转向受信任节点的共识。

插件及其API

插件是扩展节点及其功能的通用工具。其中一些仅提供数据、准备索引、响应带有数据过滤的复杂用户请求,另一些处理自定义操作,并能提供完全独立的服务。例如,您可以编写一个插件,在首次付费订阅后,生成关于区块链中重要操作的通知,或提供个人消息服务。公共插件并不总是意味着"免费"。而"免费"也不总是意味着开放(就源代码而言)。

如果我们逻辑性地研究节点-服务-API链,会发现一种不理想的情况:公共API可能给节点本身的运行带来问题。当有大量请求涌向服务API时,尤其当提供服务的插件恰好与区块链节点协同工作时,这种情况可能发生。来自用户请求(或攻击者意图对服务进行DDOS攻击的请求)的带宽可能会阻碍节点本身与其他节点交换数据。如果API请求占用CPU,或使用大量索引样本,或需要长时间准备响应数据——不仅服务开始变慢,节点的运行也会出现问题。

这就是为什么建议避免将公共API节点与高要求插件以及委托者的工作放在同一服务器上。更正确的服务架构是使用一个独立的插件,该插件可以访问私有节点上的区块处理,自行处理数据,将其存储在数据库中,并允许缓存请求。当负载增加时,您始终可以使用数据和响应节点(利用负载均衡)的集群技术来扩展服务。

本节描述了所有可用的VIZ插件,这些插件通过API为用户提供访问。如果您按照以下说明操作,可以自行了解特定插件的API:

  • 打开插件的主要头文件(database_api示例),检查DEFINE_API_ARGS(API方法名称,返回值类型);
  • 打开主要插件文件(database_api示例),检查DEFINE_API(检查请求参数,CHECK_ARGS_COUNT,形成特定类型的返回值);
  • 检查plugin_initialize,它可以处理boost::program_options::variables_map,以便通过节点配置文件进行更精细的插件调整。

请求协议

所有请求必须生成JSON格式并通过RPC执行。传输协议取决于节点的配置,有通过标准HTTP请求的JSON-RPC选项,也有通过WebSocket的选项。

为此,必须在节点的配置文件中连接以下插件:json_rpc、webserver。为了接受来自用户的交易,还必须启用network_broadcast_api插件。端口设置:

# The number of threads for rpc clients. Optimal value *number of cores minus 1*
webserver-thread-pool-size = 2

# IP:PORT for HTTP connections
webserver-http-endpoint = 0.0.0.0:8090

# IP:PORT for WebSocket connects
webserver-ws-endpoint = 0.0.0.0:8091

# IP:PORT for HTTP and WebSocket connections (simultaneous processing of two types of connections)
rpc-endpoint = 0.0.0.0:8081

要处理支持SSL的请求,需要通过代理服务器(例如nginx或apache)转发所使用的端口,这样就能实现通过https/wss进行请求。

创建API请求

向公共节点生成请求的规则相当简单:

{"id":REQUEST_ID,"jsonrpc":"2.0","method":"call","params":["PLUGIN_NAME","PLUGIN_API_METHOD",[ARGS]]}
  • REQUEST_ID — 请求编号,它是可选的(您可以将所有请求编号为1),但在通过web sockets(ws)连接时,它允许您将具有相似id的请求与响应关联起来;
  • PLUGIN_NAME — 请求所发往的插件名称(例如:database_api, committee_api);
  • PLUGIN_API_METHOD — 处理请求的方法名称(例如:database_api的get_accounts);
  • ARGS — 传递给插件方法的有序参数数组。

network_broadcast_api

一个负责在网络节点之间接收和发送已签名区块及交易的插件

  • broadcast_block — 将已签名区块(signed_block)传输到其他网络节点;
  • broadcast_transaction — 将已签名交易(signed_transaction)传输到其他网络节点;
  • broadcast_transaction_synchronous — 将已签名交易(signed_transaction)传输到其他网络节点(等待区块进入并返回交易哈希、区块编号和交易在区块中的编号,如果交易过期则返回false);
  • broadcast_transaction_with_callback — 与broadcast_transaction_synchronous相同,区别在于在将交易传输到交易池之前检查交易的有效性并注册回调方法。

custom_protocol_api

  • get_account — 通过登录名返回账户,可选择请求custom_protocol_id(可选地,如果在节点历史中找到请求的自定义协议id,则覆盖custom_sequence和custom_sequence_block_num);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["custom_protocol_api","get_account",[["readdle","V"]]]}

回答:

{
  "id": 116,
  "name": "readdle",
  "master_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ7PZqJj3UvV3kymCWHnwn9PxRGgt9z6MDzyEeXCFVr6X9XmoBCY",
        1
      ]
    ]
  },
  "active_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ6CmEqBAdWu1MGrSeTPwSG9yKwLwBPEuVBhzbKBMFP2aYTomCCN",
        1
      ]
    ]
  },
  "regular_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ8PqkhFxxifidH6688oSZHYPGsXMhRVE9xhvh6N65XkYRg74pMR",
        1
      ]
    ]
  },
  "memo_key": "VIZ7Lo593wA3SwFwpiHYfm3kWw4BwgrcBsYt1UDyZW4J3dd5GWuab",
  "json_metadata": "{\"profile\":{\"nickname\":\"Readdle.me\",\"about\":\"RU: Децентрализованная Социальная Сеть, где пользователь выступает в роли Оракула, обрабатывающего социальную активность интересных ему аккаунтов. Работает на блокчейне VIZ с использованием системы награждений Социальным Капиталом VIZ (Ƶ).\",\"avatar\":\"https://readdle.me/readdle-avatar.png\",\"services\":{\"telegram\":\"readdle_me\"},\"interests\":[\"ru\",\"welcome\"],\"pinned\":\"viz://@readdle/22099872/\"}}",
  "proxy": "",
  "referrer": "",
  "last_master_update": "1970-01-01T00:00:00",
  "last_account_update": "2020-11-05T19:23:33",
  "created": "2018-09-29T18:25:27",
  "recovery_account": "in",
  "last_account_recovery": "1970-01-01T00:00:00",
  "subcontent_count": 0,
  "vote_count": 0,
  "content_count": 0,
  "awarded_rshares": 0,
  "custom_sequence": 4,
  "custom_sequence_block_num": 22897215,
  "energy": 10000,
  "last_vote_time": "2018-09-29T18:25:27",
  "balance": "0.000 VIZ",
  "vesting_shares": "24.102958 SHARES",
  "delegated_vesting_shares": "0.000000 SHARES",
  "received_vesting_shares": "10.056015 SHARES",
  "vesting_withdraw_rate": "0.000000 SHARES",
  "next_vesting_withdrawal": "1969-12-31T23:59:59",
  "withdrawn": 0,
  "to_withdraw": 0,
  "withdraw_routes": 0,
  "curation_rewards": 0,
  "posting_rewards": 0,
  "receiver_awards": 24103,
  "benefactor_awards": 0,
  "proxied_vsf_votes": [
    0,
    0,
    0,
    0
  ],
  "witnesses_voted_for": 0,
  "witnesses_vote_weight": 0,
  "last_post": "1970-01-01T00:00:00",
  "last_root_post": "1970-01-01T00:00:00",
  "average_bandwidth": "16919020937",
  "lifetime_bandwidth": "52767000000",
  "last_bandwidth_update": "2020-12-03T11:55:45",
  "witness_votes": [],
  "valid": true,
  "account_seller": "",
  "account_offer_price": "0.000 VIZ",
  "account_on_sale": false,
  "account_on_sale_start_time": "1970-01-01T00:00:00",
  "subaccount_seller": "",
  "subaccount_offer_price": "0.000 VIZ",
  "subaccount_on_sale": false
}

database_api

  • get_account_count — 返回网络中的账户数量;
  • get_accounts — 根据请求的登录名返回账户数组(与 lookup_account_names 不同,提供附加信息);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts",[["wildviz","zozo"]]]}

回答:

[
  {
    "id": 3276,
    "name": "wildviz",
    "master_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq",
          1
        ]
      ]
    },
    "active_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8dJxeKPXe5wf6bnqaMsj3EW8EbMURoKxR4RqPCQH8xA89b6RpC",
          1
        ]
      ]
    },
    "regular_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8iKb38H1JD7PZqEYpoLX3nMt1mgfoh2LwngexvywusE7fgrd5G",
          1
        ]
      ]
    },
    "memo_key": "VIZ8mvAu9beH6gDtBrdy3oh8eTGP4tKpNXHvY7wtGm7EXLuwFaRi7",
    "json_metadata": "",
    "proxy": "",
    "referrer": "",
    "last_master_update": "1970-01-01T00:00:00",
    "last_account_update": "1970-01-01T00:00:00",
    "created": "2019-03-14T08:33:21",
    "recovery_account": "xchng",
    "last_account_recovery": "1970-01-01T00:00:00",
    "subcontent_count": 0,
    "vote_count": 10,
    "content_count": 0,
    "awarded_rshares": 0,
    "custom_sequence": 2,
    "custom_sequence_block_num": 10319967,
    "energy": 9995,
    "last_vote_time": "2019-10-14T08:08:48",
    "balance": "0.000 VIZ",
    "vesting_shares": "19159.343040 SHARES",
    "delegated_vesting_shares": "250.000000 SHARES",
    "received_vesting_shares": "0.000000 SHARES",
    "vesting_withdraw_rate": "0.000000 SHARES",
    "next_vesting_withdrawal": "1969-12-31T23:59:59",
    "withdrawn": 1500000000,
    "to_withdraw": 1500000000,
    "withdraw_routes": 0,
    "curation_rewards": 0,
    "posting_rewards": 0,
    "receiver_awards": 255786,
    "benefactor_awards": 2958047,
    "proxied_vsf_votes": [
      0,
      0,
      0,
      0
    ],
    "witnesses_voted_for": 1,
    "witnesses_vote_weight": "19159343040",
    "last_post": "1970-01-01T00:00:00",
    "last_root_post": "1970-01-01T00:00:00",
    "average_bandwidth": 1089649559,
    "lifetime_bandwidth": "24196000000",
    "last_bandwidth_update": "2019-10-14T08:08:48",
    "witness_votes": [
      "wildviz"
    ],
    "valid": true,
    "account_seller": "",
    "account_offer_price": "0.000 VIZ",
    "account_on_sale": false,
    "account_on_sale_start_time": "1970-01-01T00:00:00",
    "subaccount_seller": "",
    "subaccount_offer_price": "0.000 VIZ",
    "subaccount_on_sale": false
  }
]
  • get_accounts_on_sale — 返回待售账户列表,有两个参数:from(结果列表中的偏移量)和limit(记录数量,不能超过1000);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts_on_sale",[0,1000]]}

回答:

[
  {"account":"btc","account_seller":"ae","account_offer_price":"5000.000 VIZ"},
  {"account":"press","account_seller":"on1x","account_offer_price":"10000.000 VIZ"}
]
  • get_subaccounts_on_sale — 返回待售子账户列表,有两个参数:from(结果列表中的偏移量)和limit(记录数量,不能超过1000);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_subaccounts_on_sale",[0,1000]]}

回答:

[
  {"account":"com","subaccount_seller":"ae","subaccount_offer_price":"10.000 VIZ"},
  {"account":"digital","subaccount_seller":"on1x","subaccount_offer_price":"100.000 VIZ"},
  {"account":"blog","subaccount_seller":"on1x","subaccount_offer_price":"100.000 VIZ"},
  {"account":"new.romankr","subaccount_seller":"romankr","subaccount_offer_price":"10.000 VIZ"},
  {"account":"romankr1","subaccount_seller":"romankr","subaccount_offer_price":"10.000 VIZ"},
  {"account":"viz","subaccount_seller":"committee","subaccount_offer_price":"10000.000 VIZ"}
]
  • get_block — 根据区块编号返回区块信息;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_block",["1"]]}

回答:

{
  "previous": "0000000000000000000000000000000000000000",
  "timestamp": "2018-09-29T10:23:27",
  "witness": "committee",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": [
    [
      1,
      "1.0.0"
    ]
  ],
  "witness_signature": "2003120d1f1d8bb8e8325036838e5269332fbec7c88cd3cc76e4517d27772856ce43ef6bf0a7fde9987cec9467b944e7626db100b70867e7ec82308879a021e97a",
  "transactions": []
}
  • get_block_header — 根据区块编号返回区块头信息;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_block_header",["2"]]}

回答:

{
  "previous": "000000010496d4414ddcee5b76f9a6b950da6fe9",
  "timestamp": "2018-09-29T10:23:30",
  "witness": "committee",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": []
}
  • get_chain_properties — 返回已投票网络参数的中位数值;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_chain_properties",[]]}

回答:

{
  "account_creation_fee": "1.000 VIZ",
  "maximum_block_size": 65536,
  "create_account_delegation_ratio": 10,
  "create_account_delegation_time": 2592000,
  "min_delegation": "1.000 VIZ",
  "min_curation_percent": 0,
  "max_curation_percent": 10000,
  "bandwidth_reserve_percent": 0,
  "bandwidth_reserve_below": "0.000000 SHARES",
  "flag_energy_additional_cost": 0,
  "vote_accounting_min_rshares": 50000,
  "committee_request_approve_min_percent": 1000,
  "inflation_witness_percent": 2000,
  "inflation_ratio_committee_vs_reward_fund": 7500,
  "inflation_recalc_period": 806400,
  "data_operations_cost_additional_bandwidth": 0,
  "witness_miss_penalty_percent": 100,
  "witness_miss_penalty_duration": 86400
}
  • get_config — 返回VIZ协议配置文件中的预设值;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_config",[]]}

回答:

{
  "CHAIN_100_PERCENT": 10000,
  "CHAIN_1_PERCENT": 100,
  "CHAIN_ADDRESS_PREFIX": "VIZ",
  "CHAIN_BANDWIDTH_AVERAGE_WINDOW_SECONDS": 604800,
  "CHAIN_BANDWIDTH_PRECISION": 1000000,
  "CONSENSUS_BANDWIDTH_RESERVE_PERCENT": 1000,
  "CONSENSUS_BANDWIDTH_RESERVE_BELOW": 500000000,
  "CHAIN_HARDFORK_VERSION": "2.4.0",
  "CHAIN_VERSION": "2.4.0",
  "CHAIN_BLOCK_INTERVAL": 3,
  "CHAIN_BLOCKS_PER_DAY": 28800,
  "CHAIN_BLOCKS_PER_YEAR": 10512000,
  "CHAIN_CASHOUT_WINDOW_SECONDS": 86400,
  "CHAIN_ID": "2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd",
  "CHAIN_HARDFORK_REQUIRED_WITNESSES": 17,
  "CHAIN_INITIATOR_NAME": "viz",
  "CHAIN_INITIATOR_PUBLIC_KEY_STR": "VIZ6MyX5QiXAXRZk7SYCiqpi6Mtm8UbHWDFSV8HPpt7FJyahCnc2T",
  "CHAIN_INIT_SUPPLY": "50000000000",
  "CHAIN_COMMITTEE_ACCOUNT": "committee",
  "CHAIN_COMMITTEE_PUBLIC_KEY_STR": "VIZ6Yt7d6LsngBoXQr47aLv97bJVs7jyr7esZTM4UUSpLUf3nbRKS",
  "CHAIN_IRREVERSIBLE_THRESHOLD": 7500,
  "CHAIN_IRREVERSIBLE_SUPPORT_MIN_RUN": 2,
  "CHAIN_MAX_ACCOUNT_NAME_LENGTH": 25,
  "CHAIN_MAX_ACCOUNT_WITNESS_VOTES": 100,
  "CHAIN_BLOCK_SIZE": 6291456,
  "CHAIN_MAX_COMMENT_DEPTH": 65520,
  "CHAIN_MAX_MEMO_LENGTH": 2048,
  "CHAIN_MAX_WITNESSES": 21,
  "CHAIN_MAX_PROXY_RECURSION_DEPTH": 4,
  "CHAIN_MAX_RESERVE_RATIO": 20000,
  "CHAIN_MAX_SUPPORT_WITNESSES": 10,
  "CHAIN_MAX_SHARE_SUPPLY": "1000000000000000",
  "CHAIN_MAX_SIG_CHECK_DEPTH": 2,
  "CHAIN_MAX_TIME_UNTIL_EXPIRATION": 3600,
  "CHAIN_MAX_TRANSACTION_SIZE": 65536,
  "CHAIN_MAX_UNDO_HISTORY": 10000,
  "CHAIN_MAX_VOTE_CHANGES": 5,
  "CHAIN_MAX_TOP_WITNESSES": 11,
  "CHAIN_MAX_WITHDRAW_ROUTES": 10,
  "CHAIN_MAX_WITNESS_URL_LENGTH": 2048,
  "CHAIN_MIN_ACCOUNT_CREATION_FEE": 1000,
  "CHAIN_MIN_ACCOUNT_NAME_LENGTH": 2,
  "CHAIN_MIN_BLOCK_SIZE_LIMIT": 65536,
  "CHAIN_MAX_BLOCK_SIZE_LIMIT": 2097152,
  "CHAIN_NULL_ACCOUNT": "null",
  "CHAIN_NUM_INITIATORS": 0,
  "CHAIN_PROXY_TO_SELF_ACCOUNT": "",
  "CHAIN_SECONDS_PER_YEAR": 31536000,
  "CHAIN_VESTING_WITHDRAW_INTERVALS": 28,
  "CHAIN_VESTING_WITHDRAW_INTERVAL_SECONDS": 86400,
  "CHAIN_ENERGY_REGENERATION_SECONDS": 432000,
  "TOKEN_SYMBOL": 1514755587,
  "SHARES_SYMBOL": "23438642651878150",
  "CHAIN_NAME": "VIZ",
  "CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT": 5,
  "CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT": 200000
}
  • get_database_info — 返回数据库使用情况信息(按对象类型);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_database_info",[]]}

回答:

{
  "total_size": "6442450944",
  "free_size": 1828375424,
  "reserved_size": 0,
  "used_size": "4614075520",
  "index_list": [
    {
      "name": "graphene::chain::dynamic_global_property_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::account_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::account_authority_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::witness_object",
      "record_count": 59
    },
    {
      "name": "graphene::chain::transaction_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::block_summary_object",
      "record_count": 65536
    },
    {
      "name": "graphene::chain::witness_schedule_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::content_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::content_type_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::content_vote_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::witness_vote_object",
      "record_count": 239
    },
    {
      "name": "graphene::chain::hardfork_property_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::withdraw_vesting_route_object",
      "record_count": 7
    },
    {
      "name": "graphene::chain::master_authority_history_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::account_recovery_request_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::change_recovery_account_request_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::escrow_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::vesting_delegation_object",
      "record_count": 286
    },
    {
      "name": "graphene::chain::fix_vesting_delegation_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::vesting_delegation_expiration_object",
      "record_count": 12
    },
    {
      "name": "graphene::chain::account_metadata_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::proposal_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::required_approval_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::committee_request_object",
      "record_count": 39
    },
    {
      "name": "graphene::chain::committee_vote_object",
      "record_count": 453
    },
    {
      "name": "graphene::chain::invite_object",
      "record_count": 364
    },
    {
      "name": "graphene::chain::award_shares_expire_object",
      "record_count": 2463
    },
    {
      "name": "graphene::chain::paid_subscription_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::paid_subscribe_object",
      "record_count": 26
    },
    {
      "name": "graphene::chain::witness_penalty_expire_object",
      "record_count": 4
    },
    {
      "name": "graphene::plugins::private_message::message_object",
      "record_count": 0
    },
    {
      "name": "graphene::plugins::operation_history::operation_object",
      "record_count": 11967735
    },
    {
      "name": "graphene::plugins::account_history::account_history_object",
      "record_count": 12014504
    }
  ]
}
  • get_dynamic_global_properties — 返回动态全局网络属性的数据;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}

回答:

{
  "id": 0,
  "head_block_number": 10920301,
  "head_block_id": "00a6a16d8a63db35fa727c62a49c00bf9d963819",
  "genesis_time": "2018-09-29T10:23:24",
  "time": "2019-10-14T12:22:54",
  "current_witness": "ae.witness",
  "committee_fund": "1442755.925 VIZ",
  "committee_requests": 39,
  "current_supply": "55206722.493 VIZ",
  "total_vesting_fund": "27501277.264 VIZ",
  "total_vesting_shares": "27501269.766153 SHARES",
  "total_reward_fund": "28351.836 VIZ",
  "total_reward_shares": "6610384216022",
  "average_block_size": 120,
  "maximum_block_size": 65536,
  "current_aslot": 10946390,
  "recent_slots_filled": "340282366920938463463374607431768211455",
  "participation_count": 128,
  "last_irreversible_block_num": 10920284,
  "max_virtual_bandwidth": "5986734968066277376",
  "current_reserve_ratio": 20000,
  "vote_regeneration_per_day": 1,
  "bandwidth_reserve_candidates": 1,
  "inflation_calc_block_num": 10315901,
  "inflation_witness_percent": 2000,
  "inflation_ratio": 5000
}
  • get_escrow — 通过账户登录名和交易ID返回三方交易信息;
  • get_expiring_vesting_delegations — 返回待返还的委托份额列表(按返还到期时间);
  • get_hardfork_version — 返回当前硬分叉版本;
  • get_master_history — 返回指定账户主权限更改历史列表(以master_authority_history_api_object形式);
  • get_next_scheduled_hardfork — 返回下一个计划中的硬分叉;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_next_scheduled_hardfork",[]]}

回答:

{
  "hf_version": "2.4.0",
  "live_time": "2019-04-30T05:00:00"
}
  • get_potential_signatures — 返回用于签名交易的潜在公钥;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_potential_signatures",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[]}]]}

回答:

[
  "VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"
]
  • get_proposed_transactions — 返回向账户提议的所有交易(属性:account, from — 起始交易编号, limit — 阈值);返回向账户提议的所有交易(属性:account, from - 起始交易编号, limit - 阈值)
  • get_recovery_request — 返回关于账户访问恢复请求的数据;
  • get_required_signatures — 返回建议公钥中签名交易所必需的公钥(需要发送未签名的交易并提供公钥);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_required_signatures",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[]},["VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq","VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"]]]}

回答:

[
  "VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"
]
  • get_transaction_hex — 返回原始交易的十六进制值;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_transaction_hex",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[],"signatures":["1f64cb23ba686126f9a00d904840e472de44e0e2291eca5c6b380083ee7a0b9f9934bbe9f03404a33a6df0630a020ff4750b886b65f4af8cd9877df8128d45da05"]}]]}

回答:

89a08b9f8d495a68a45d012f06736f6369616c06736f6369616c0f000000000000000000037562690000011f64cb23ba686126f9a00d904840e472de44e0e2291eca5c6b380083ee7a0b9f9934bbe9f03404a33a6df0630a020ff4750b886b65f4af8cd9877df8128d45da05
  • get_vesting_delegations — 返回账户委托份额的列表;
  • get_withdraw_routes — 返回账户份额减少方式的数组;
  • lookup_account_names — 根据请求的登录名返回账户数组;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","lookup_account_names",[["wildviz","zozo"]]]}

回答:

[
  {
    "id": 3276,
    "name": "wildviz",
    "master_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq",
          1
        ]
      ]
    },
    "active_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8dJxeKPXe5wf6bnqaMsj3EW8EbMURoKxR4RqPCQH8xA89b6RpC",
          1
        ]
      ]
    },
    "regular_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8iKb38H1JD7PZqEYpoLX3nMt1mgfoh2LwngexvywusE7fgrd5G",
          1
        ]
      ]
    },
    "memo_key": "VIZ8mvAu9beH6gDtBrdy3oh8eTGP4tKpNXHvY7wtGm7EXLuwFaRi7",
    "json_metadata": "",
    "proxy": "",
    "referrer": "",
    "last_master_update": "1970-01-01T00:00:00",
    "last_account_update": "1970-01-01T00:00:00",
    "created": "2019-03-14T08:33:21",
    "recovery_account": "xchng",
    "last_account_recovery": "1970-01-01T00:00:00",
    "subcontent_count": 0,
    "vote_count": 10,
    "content_count": 0,
    "awarded_rshares": 0,
    "custom_sequence": 2,
    "custom_sequence_block_num": 10319967,
    "energy": 9995,
    "last_vote_time": "2019-10-14T08:08:48",
    "balance": "0.000 VIZ",
    "vesting_shares": "19157.679056 SHARES",
    "delegated_vesting_shares": "250.000000 SHARES",
    "received_vesting_shares": "0.000000 SHARES",
    "vesting_withdraw_rate": "0.000000 SHARES",
    "next_vesting_withdrawal": "1969-12-31T23:59:59",
    "withdrawn": 1500000000,
    "to_withdraw": 1500000000,
    "withdraw_routes": 0,
    "curation_rewards": 0,
    "posting_rewards": 0,
    "receiver_awards": 255786,
    "benefactor_awards": 2958047,
    "proxied_vsf_votes": [
      0,
      0,
      0,
      0
    ],
    "witnesses_voted_for": 1,
    "witnesses_vote_weight": "19157679056",
    "last_post": "1970-01-01T00:00:00",
    "last_root_post": "1970-01-01T00:00:00",
    "average_bandwidth": 1089649559,
    "lifetime_bandwidth": "24196000000",
    "last_bandwidth_update": "2019-10-14T08:08:48",
    "witness_votes": [],
    "valid": true,
    "account_seller": "",
    "account_offer_price": "0.000 VIZ",
    "account_on_sale": false,
    "account_on_sale_start_time": "1970-01-01T00:00:00",
    "subaccount_seller": "",
    "subaccount_offer_price": "0.000 VIZ",
    "subaccount_on_sale": false
  },
  null
]
  • lookup_accounts — 返回沿下限的账户登录名数组,并指定返回元素的数量(不超过1000个);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","lookup_accounts",["ae","10"]]}

回答:

[
  "ae",
  "ae-witness",
  "ae.witness",
  "ae0",
  "ae1",
  "ae10",
  "ae100",
  "ae101",
  "ae102",
  "ae103"
]
  • verify_account_authority — 通过指定公钥验证单独账户对交易的签名;
  • verify_authority — 接受已签名的交易,如果交易已用所有必需的密钥签名则返回TRUE;

account_by_key

  • get_key_references — 返回包含指定公钥的账户登录名数组。

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["account_by_key","get_key_references",[["VIZ5Z2po7K5CoCXw2xLPPt8JJvJLJ3xVNANLgTy9KDfLeZH2urSSd","VIZ1111111111111111111111111111111114T1Anm"]]]}

回答:

[
  [
    "on1x"
  ],
  [
    "viz"
  ]
]

account_history

  • get_account_history — 返回与特定账户相关的操作历史(包括虚拟操作)。在配置文件中可以多次指定 track-account-range 作为json字符串:["from","to"]

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["account_history","get_account_history",["on1x","-1","5"]]}

回答:

[
  [
    3057,
    {
      "trx_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
      "block": 10913871,
      "trx_in_block": 0,
      "op_in_trx": 0,
      "virtual_op": 0,
      "timestamp": "2019-10-14T07:01:18",
      "op": [
        "transfer",
        {
          "from": "viz-social-bot",
          "to": "on1x",
          "amount": "7.725 VIZ",
          "memo": "withdraw:3353"
        }
      ]
    }
  ],
  [
    3058,
    {
      "trx_id": "ac8c4c225334dc59ec604dfa3d56b55dfc0647d3",
      "block": 10916119,
      "trx_in_block": 0,
      "op_in_trx": 0,
      "virtual_op": 1,
      "timestamp": "2019-10-14T08:53:42",
      "op": [
        "receive_award",
        {
          "initiator": "dignity",
          "receiver": "on1x",
          "custom_sequence": 0,
          "memo": "telegram:151842302",
          "shares": "58.246984 SHARES"
        }
      ]
    }
  ]
]

committee_api

  • get_committee_request — 返回关于申请的信息(可以指定为该申请返回的投票数量);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_request",["39","1"]]}

回答:

{
  "id": 38,
  "request_id": 39,
  "url": "https://control.viz.world/media/@wildviz/committee-1/",
  "creator": "wildviz",
  "worker": "wildviz",
  "required_amount_min": "0.000 VIZ",
  "required_amount_max": "10000.000 VIZ",
  "start_time": "2019-09-23T15:49:39",
  "duration": 432000,
  "end_time": "2019-09-28T15:49:39",
  "status": 5,
  "votes_count": 23,
  "conclusion_time": "2019-09-28T15:49:39",
  "conclusion_payout_amount": "10000.000 VIZ",
  "payout_amount": "10000.000 VIZ",
  "remain_payout_amount": "0.000 VIZ",
  "last_payout_time": "2019-09-28T15:56:42",
  "payout_time": "2019-09-28T15:56:42",
  "votes": [
    {
      "voter": "wildviz",
      "vote_percent": 10000,
      "last_update": "2019-09-23T15:52:09"
    }
  ]
}
  • get_committee_request_votes — 返回关于该申请的所有投票数组;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_request_votes",["39"]]}

回答:

[
  {
    "voter": "wildviz",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:52:09"
  },
  {
    "voter": "denis-golub",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:54:51"
  },
  {
    "voter": "lex",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:54:51"
  },
  ...
]
  • get_committee_requests_list — 根据状态返回委员会申请ID数组(0 - 待审核,1 - 创建者取消,2 - 票数不足,3 - 申请支付不足,4 — 申请已批准并等待支付,5 - 支付已完成);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_requests_list",["2"]]}

回答:

[
  2,
  3,
  13,
  14,
  33
]

invite_api

  • get_invite_by_id — 根据ID返回邀请信息;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["invite_api","get_invite_by_id",["0"]]}

回答:

{
  "id": 0,
  "creator": "denis-skripnik",
  "receiver": "liveblogs",
  "invite_key": "VIZ5bqU5UEig5o8gESpJCy662LLXQuZWHX49puiZtGhv3ZxmN6e3t",
  "invite_secret": "5JopL2TpywM2WKx83aTLnwLZjZ58ZEnK7aBJ3L2V2KjaVA1Neko",
  "balance": "0.000 VIZ",
  "claimed_balance": "100.000 VIZ",
  "create_time": "2018-10-05T02:12:57",
  "claim_time": "2018-10-05T11:55:36",
  "status": 2
}
  • get_invite_by_key — 使用公钥返回邀请信息;
  • get_invites_list — 根据状态返回邀请ID数组(0 - 未激活,1 - 已兑换到账户,2 - 已用于账户注册);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["invite_api","get_invites_list",["0"]]}

回答:

[
  6,
  8,
  15,
  20,
  34,
  ...
]

operation_history

  • get_ops_in_block — 根据区块编号返回操作数组(可以指定是否仅显示虚拟操作);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["operation_history","get_ops_in_block",["10913871","false"]]}

回答:

[
  {
    "trx_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
    "block": 10913871,
    "trx_in_block": 0,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "on1x",
        "amount": "7.725 VIZ",
        "memo": "withdraw:3353"
      }
    ]
  },
  {
    "trx_id": "0122149bdfdbae80a6c3f4cabe36c78e0e20c12d",
    "block": 10913871,
    "trx_in_block": 1,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "dance",
        "amount": "6.827 VIZ",
        "memo": "withdraw:3354"
      }
    ]
  },
  {
    "trx_id": "e8d8da85004234afd048979860d88cd78297ac1e",
    "block": 10913871,
    "trx_in_block": 2,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "hypno",
        "amount": "6.976 VIZ",
        "memo": "withdraw:3355"
      }
    ]
  },
  {
    "trx_id": "0000000000000000000000000000000000000000",
    "block": 10913871,
    "trx_in_block": 65535,
    "op_in_trx": 0,
    "virtual_op": 1,
    "timestamp": "2019-10-14T07:01:21",
    "op": [
      "witness_reward",
      {
        "witness": "wildviz",
        "shares": "0.103999 SHARES"
      }
    ]
  }
]
  • get_transaction — 根据交易哈希(ID)返回交易数据;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["operation_history","get_transaction",["d5f53163bc237b17c8fd6f3278d3ca1b4ae21691"]]}

回答:

{
  "ref_block_num": 34889,
  "ref_block_prefix": 453694329,
  "expiration": "2019-10-14T07:11:20",
  "operations": [
    [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "on1x",
        "amount": "7.725 VIZ",
        "memo": "withdraw:3353"
      }
    ]
  ],
  "extensions": [],
  "signatures": [
    "203e778ae54b5fe51367bef34a7001eca7257732075723e99f07fd8c4cd0cc8040021925c811fef0ff2151a28193403af6af05f05dadc1427bb0daf604df53ee0c"
  ],
  "transaction_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
  "block_num": 10913871,
  "transaction_num": 0
}
  • get_paid_subscriptions — 返回按发起者账户排序的付费订阅列表(接受2个参数:from — 从开头缩进的记录数,limit — 结果数量限制,不能超过1000);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscriptions",[0,1]]}

回答:

[
  {
    "update_time" : "2019-06-05T15:07:57",
    "creator" : "tratata",
    "id" : 3,
    "period" : 30,
    "levels" : 10,
    "url" : "",
    "amount" : 500000
  }
]
  • get_active_paid_subscriptions — 返回账户的活跃付费订阅协议数组;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_active_paid_subscriptions",["on1x"]]}

回答:

[
  "viz.world"
]
  • get_inactive_paid_subscriptions — 返回账户已取消的付费订阅协议数组;
  • get_paid_subscription_options — 返回关于账户付费订阅协议的数据;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscription_options",["viz.world"]]}

回答:

{
  "creator": "viz.world",
  "url": "https://control.viz.world/media/@viz.world/ru-service/",
  "levels": 2,
  "amount": 5000,
  "period": 30,
  "update_time": "2019-05-21T07:14:45",
  "active_subscribers": [
    "on1x",
    "muz",
    "litrbooh",
    "antonkostroma",
    "xchng",
    "liveblogs",
    "wildviz",
    "id1club"
  ],
  "active_subscribers_count": 8,
  "active_subscribers_summary_amount": 65000,
  "active_subscribers_with_auto_renewal": [
    "on1x",
    "muz",
    "litrbooh",
    "antonkostroma",
    "liveblogs"
  ],
  "active_subscribers_with_auto_renewal_count": 5,
  "active_subscribers_with_auto_renewal_summary_amount": 35000
}
  • get_paid_subscription_status — 返回关于订阅者针对特定账户的付费订阅协议的数据;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscription_status",["on1x","viz.world"]]}

回答:

{
  "subscriber": "on1x",
  "creator": "viz.world",
  "level": 2,
  "amount": 5000,
  "period": 30,
  "start_time": "2019-09-18T07:51:39",
  "next_time": "2019-10-18T07:51:39",
  "end_time": "1969-12-31T23:59:59",
  "active": true,
  "auto_renewal": true
}

witness_api

  • get_active_witnesses — 返回当前轮次中的委托者数组(共21个槽位,如果存在这么多委托者的话);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_active_witnesses",[]]}

回答:

[
  "solox",
  "lexai",
  "xchng",
  "creativity",
  "jackvote",
  "pom-vjfru0njnme",
  "retroscope",
  "wildviz",
  "lex",
  "dordoy",
  "denis-skripnik",
  "dmilash",
  "id1club",
  "lb",
  "ae.witness",
  "denis-golub",
  "litrbooh",
  "mad-max",
  "t3",
  "web3",
  "charity"
]
  • get_witness_by_account — 根据用户名返回对应的委托者;
  • get_witness_count — 返回已声明希望成为委托者的账户数量;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witness_count",[]]}

回答:

59
  • get_witness_schedule — 返回委托者队列,补充了网络参数的计算中位值和硬分叉的多数版本;

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witness_schedule",[]]}

回答:

{
  "id": 0,
  "current_virtual_time": "23461032266075892861574835409469",
  "next_shuffle_block_num": 10916661,
  "current_shuffled_witnesses": "736f6c6f780000000000000000000000000000000000000000000000000000006c657861690000000000000000000000000000000000000000000000000000007863686e6700000000000000000000000000000000000000000000000000000063726561746976697479000000000000000000000000000000000000000000006a61636b766f7465000000000000000000000000000000000000000000000000706f6d2d766a667275306e6a6e6d650000000000000000000000000000000000726574726f73636f70650000000000000000000000000000000000000000000077696c6476697a000000000000000000000000000000000000000000000000006c65780000000000000000000000000000000000000000000000000000000000646f72646f79000000000000000000000000000000000000000000000000000064656e69732d736b7269706e696b000000000000000000000000000000000000646d696c61736800000000000000000000000000000000000000000000000000696431636c7562000000000000000000000000000000000000000000000000006c6200000000000000000000000000000000000000000000000000000000000061652e7769746e657373000000000000000000000000000000000000000000006d61642d6d6178000000000000000000000000000000000000000000000000006c697472626f6f6800000000000000000000000000000000000000000000000070686f746f636c75620000000000000000000000000000000000000000000000743300000000000000000000000000000000000000000000000000000000000064656e69732d676f6c75620000000000000000000000000000000000000000006368617269747900000000000000000000000000000000000000000000000000",
  "num_scheduled_witnesses": 21,
  "median_props": {
    "account_creation_fee": "1.000 VIZ",
    "maximum_block_size": 65536,
    "create_account_delegation_ratio": 10,
    "create_account_delegation_time": 2592000,
    "min_delegation": "1.000 VIZ",
    "min_curation_percent": 0,
    "max_curation_percent": 10000,
    "bandwidth_reserve_percent": 0,
    "bandwidth_reserve_below": "0.000000 SHARES",
    "flag_energy_additional_cost": 0,
    "vote_accounting_min_rshares": 50000,
    "committee_request_approve_min_percent": 1000,
    "inflation_witness_percent": 2000,
    "inflation_ratio_committee_vs_reward_fund": 7500,
    "inflation_recalc_period": 806400,
    "data_operations_cost_additional_bandwidth": 0,
    "witness_miss_penalty_percent": 100,
    "witness_miss_penalty_duration": 86400
  },
  "majority_version": "2.4.0"
}
  • get_witnesses — 根据委托者ID返回委托者数组(可以在数组中请求多个);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witnesses",[["0"]]]}

回答:

[
  {
    "id": 0,
    "owner": "committee",
    "created": "1970-01-01T00:00:00",
    "url": "",
    "votes": 0,
    "penalty_percent": 0,
    "counted_votes": 0,
    "virtual_last_update": "0",
    "virtual_position": "0",
    "virtual_scheduled_time": "340282366920938463463374607431768211455",
    "total_missed": 15,
    "last_aslot": 144669,
    "last_confirmed_block_num": 132749,
    "signing_key": "VIZ1111111111111111111111111111111114T1Anm",
    "props": {
      "account_creation_fee": "1.000 VIZ",
      "maximum_block_size": 131072,
      "create_account_delegation_ratio": 10,
      "create_account_delegation_time": 2592000,
      "min_delegation": "0.001 VIZ",
      "min_curation_percent": 1600,
      "max_curation_percent": 1600,
      "bandwidth_reserve_percent": 1000,
      "bandwidth_reserve_below": "500.000000 SHARES",
      "flag_energy_additional_cost": 0,
      "vote_accounting_min_rshares": 5000000,
      "committee_request_approve_min_percent": 1000,
      "inflation_witness_percent": 2000,
      "inflation_ratio_committee_vs_reward_fund": 5000,
      "inflation_recalc_period": 806400,
      "data_operations_cost_additional_bandwidth": 0,
      "witness_miss_penalty_percent": 100,
      "witness_miss_penalty_duration": 86400
    },
    "last_work": "0000000000000000000000000000000000000000000000000000000000000000",
    "running_version": "1.0.1",
    "hardfork_version_vote": "0.0.0",
    "hardfork_time_vote": "1970-01-01T00:00:00"
  }
]
  • get_witnesses_by_vote — 返回按获得投票潜力排序的委托者数组(指定列表的左边界和数组中返回值的数量,但不超过100个);

示例:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witnesses_by_vote",["","10000"]]}

回答:

[
 {
   "id": 42,
   "owner": "solox",
   "created": "2018-12-22T19:09:51",
   "url": "http://viz.world/@solox/witness/",
   "votes": "2265575784725",
   "penalty_percent": 0,
   "counted_votes": "2265575784725",
   "virtual_last_update": "23453249838245982754942708691144",
   "virtual_position": "0",
   "virtual_scheduled_time": "23453400035105083671049497423272",
   "total_missed": 273,
   "last_aslot": 10942638,
   "last_confirmed_block_num": 10916550,
   "signing_key": "VIZ8MzGnSUeqbFaFr8g297XNDT7iWQZ8ktBgeBDYj1moWCHQ8a5PA",
   "props": {
     "account_creation_fee": "1.000 VIZ",
     "maximum_block_size": 65536,
     "create_account_delegation_ratio": 10,
     "create_account_delegation_time": 2592000,
     "min_delegation": "1.000 VIZ",
     "min_curation_percent": 0,
     "max_curation_percent": 10000,
     "bandwidth_reserve_percent": 100,
     "bandwidth_reserve_below": "1.000000 SHARES",
     "flag_energy_additional_cost": 0,
     "vote_accounting_min_rshares": 50000,
     "committee_request_approve_min_percent": 1000,
     "inflation_witness_percent": 3000,
     "inflation_ratio_committee_vs_reward_fund": 7500,
     "inflation_recalc_period": 806400,
     "data_operations_cost_additional_bandwidth": 0,
     "witness_miss_penalty_percent": 100,
     "witness_miss_penalty_duration": 86400
   },
   "last_work": "0000000000000000000000000000000000000000000000000000000000000000",
   "running_version": "2.4.0",
   "hardfork_version_vote": "2.4.0",
   "hardfork_time_vote": "2019-04-30T05:00:00"
 }
]
  • get_witnesses_by_counted_vote — 返回按获得投票潜力排序的委托者数组,考虑因错过区块而施加的惩罚(指定列表的左边界和数组中返回值的数量,但不超过100个);
  • lookup_witness_accounts — 返回声明自己为委托者的账户登录名数组(指定列表的左边界和数组中返回值的数量,但不超过1000个)。

示例:

 {"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","lookup_witness_accounts",["","4"]]}

回答:

 [
  "t3",
  "lb",
  "ae",
  "in"
]

用于处理VIZ的库

应用程序开发通常使用现成的库。特定编程语言库的可用性取决于是否有用于处理密码学、大数和传输协议(http/ws)的模板。由于VIZ历史上是从Graphene发展而来的,大多数适用于EOS/Steem/Golos等区块链系统的库也适用于VIZ。一个显著的区别是与节点的通信格式(json-rpc结构)、描述操作时参数的顺序和名称、二进制形式中复杂数据的格式(例如,VIZ和SHARES资产的格式与Steem中的新SMT格式不同)。

尽管语法不同,但与VIZ交互的基础是相同的:用于密钥和消息签名的密码学、使用数据和公钥验证签名、交易形成、与节点的交互。

每个开发者都可以搭建自己的节点来与VIZ交互,但为初学者理解提供了公共节点:

下面列出了支持大部分节点请求API和交易生成的主要VIZ库。


JavaScript

应用程序开发的最佳工具是vis-js-lib库。它支持服务器端(nodejs)和用户端(浏览器中的js)与VIZ交互所需的一切:

  • 创建和编码密钥;
  • API请求;
  • 交易形成;
  • 用于操作的简化交易构造器;
  • 请求的回调函数;

英文版Viz-js-lib文档可在GitHub上获取。关于常用操作的示例,请参阅代码示例部分。

Python

来自ksantoprotein的thallid-viz库支持API请求和交易形成。提供了许多示例涵盖不同操作。

viz-python-lib库支持大部分必要功能,但没有示例和文档(该库尚未完成)。

PHP

在切换到适配的BigNumber和椭圆曲线库之后,无需为PHP构建secp256k1并启用GMP支持即可使用密码学功能。

viz-php-lib库支持JsonRPC、密钥处理、交易生成、通过共享密钥进行消息加密(与viz-js-lib兼容),提供示例、PSR-4支持且无需额外依赖即可安装(一体化)。

支持VIZ的php-graphene-node-client库,可通过Composer安装。

GO

viz-go-lib库非常适合API请求和研究交易形成。遗憾的是,该库没有文档,也没有针对单个操作的示例。

Swift

viz-swift-lib库 — 一个Swift库,可通过Swift Package Manager安装。

Dart

viz_dart_ecc密码学库 — 允许从私钥生成公钥、签名数据、验证签名。

viz-transaction库 — 允许为VIZ区块链形成和签名交易(该库不包含将交易发送到区块链节点的方法,为此需要使用任何其他用于http/ws协议的库)。

其他

如果您没有找到所需的编程语言,可以关注现有的EOS和Steem库。要修改它们以获得VIZ支持,只需检查json-rpc请求的格式,更改chain_id(在VIZ中,它等于2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd — 这是签名原始交易的前缀)并配置操作构造器。

代码示例

始终建议新手开发者阅读特定库的文档。这既有助于理解库的工作原理,也能记住在开发应用程序时可以使用的特性。本节以示例形式描述了最常用的查询。VIZ应用程序最常用的库是viz-js-lib,因此示例将使用该库。


viz-js-lib

详细的英文文档,包含所有方法及其属性的说明,可通过此链接获取

连接库

根据服务器(nodejs)或浏览器(js)的使用场景,库需要以不同的方式连接。

对于nodejs,当前的安装指令是通过npm install viz-js-lib --save安装库,并在js文件中通过var viz = require('viz-js-lib');连接。

对于js连接,您可以通过控制台npm build自行构建webpack库,或使用已构建的库,来自jsDelivr CNDUnpkg CDN。只需将script标签添加到html文件并指定库的URL:<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/viz-js-lib@latest/dist/viz.min.js"></script>,之后您将通过控制台访问全局变量viz

使用公共节点

只要您的应用程序没有大量用户流,使用可用的公共节点是合理的。在撰写本文时,VIZ有两个可用的公共节点:

  • 来自lex委托者的公共节点:https://viz.lexa.host/用于通过HTTPS的JSON-RPC请求,wss://viz.lexa.host/ws用于通过SSL的WebSocket的JSON-RPC请求;
  • 来自 viz.world 委托者的公共节点:https://api.viz.world/ 用于通过 HTTPS 的 JSON-RPC 请求。

配置 viz 以使用 https://api.viz.world/ 节点的示例:

var api_gate='https://api.viz.world/';
viz.config.set('websocket',api_gate);

API请求

插件及其API部分列出了主要插件及其请求——所有这些在viz-js-lib库中都是可用的。要执行特定请求,只需将其名称转换为camelCase即可。

例如,如果您决定向database_api插件发出get_database_info请求,那么您需要运行以下代码:

viz.api.getDatabaseInfo(function(err,response){
    if(!err){
        //response received
        console.log(response);
    }
    else{
        //error
        console.log(err);
    }
});

如果请求需要输入数据,您需要将它们添加到调用的开头。例如,要向paid_subscription_api插件请求get_active_paid_subscriptions,您必须指定将为其搜索活跃付费订阅的用户:

var subscriber='on1x';
viz.api.getActivePaidSubscriptions(subscriber,function(err,response){
    if(!err){
        //response received
        console.log(response);
    }
    else{
        //error
        console.log(err);
    }
});

交易广播

对于VIZ协议中的每个操作,vis-js-lib库中都有一个单独的方法,该方法接受私钥(用于签名交易)和操作参数。操作名称,类似于API方法,应转换为驼峰命名法格式。广播accounts_metadata操作(将账户元数据写入区块链)的示例代码:

var regular_key='5K...';//private key
var user_login='test';//account login
var metadata={'name':'Test account','photo':'https://cdn.pixabay.com/photo/2015/12/06/14/14/tokyo-1079524_960_720.jpg'};
viz.broadcast.accountMetadata(regular_key,user_login,JSON.stringify(metadata),function(err,result){
    if(!err){
        //the transaction was accepted by the public node
        console.log(result);
    }
    else{
        //the node did not accept the transaction
        console.log(err);
    }
});

网络动态全局属性

一些新手希望定期向节点发送请求并获取关于DGP(动态全局属性)的最新数据,以便基于此显示新区块、满足不可逆区块的条件或在委托者列表中高亮显示最后一个签名区块的委托者。为此,只需每3秒(区块之间的时间间隔)在计时器上请求数据:

var dgp={}
function update_dgp(auto=false){
    viz.api.getDynamicGlobalProperties(function(err,response){
        if(!err){
            dgp=response;
        }
    });
    if(auto){
        setTimeout("update_dgp(true)",3000);
    }
}
update_dgp(true);

获取账户信息

获取账户信息并计算账户当前能量值(考虑其恢复速度)的示例代码:

var current_user='on1x';
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //response received
        if(typeof response[0] !== 'undefined'){
            //we have requested an array of accounts, we are looking at the zero element corresponding to current_user
            let last_vote_time=Date.parse(response[0].last_vote_time);
            //we take into account the user's time zone
            let delta_time=parseInt((new Date().getTime() - last_vote_time + (new Date().getTimezoneOffset()*60000))/1000);
            let energy=response[0].energy;
            //calculating the recovered energy
            //energy recovery rate from 0% to 100% CHAIN_ENERGY_REGENERATION_SECONDS 5 days (432000 seconds)
            let new_energy=parseInt(energy+(delta_time*10000/432000));
            //the energy can not be more than 100%
            if(new_energy>10000){
                new_energy=10000;
            }
            console.log('current energy of account',new_energy);
        }
        else{
            console.log('the account was not found',current_user);
        }
    }
    else{
        //error
        console.log(err);
    }
});

密钥操作

加密密钥是写在secp256k1椭圆曲线上通用DER格式的X和Y坐标(SHA-256作为哈希函数)。在viz-js-lib库中,密钥的转换和操作属于viz.auth模块。

Graphene生态系统发明了一种人类可读密码的机制。由于暴力破解的风险,不建议使用这些密码,因此,为了增加对公钥的私钥多重搜索的难度,我们制定了特定的密钥生成规则,即字符串连接形式:账户登录名、密码(复杂)、访问类型。

一些应用程序已约定使用这些规则,从而通过通用密码简化用户访问各种账户功能。例如,名为test的用户使用通用密码PK3452JENDK332注册。当使用此用户名和密码登录应用程序时,应用程序可以独立生成所需访问类型的密钥,只需使用字符串连接。用户想要转账代币吗?应用程序通过testPK3452JENDK332active字符串动态生成私有的活跃密钥。用户要奖励某人吗?应用程序通过testPK3452JENDK332regular字符串生成私有的常规密钥。这使用户通过共享密码访问更简便,但牺牲了灵活性并使账户面临风险。访问类型具有不同的权限,如果用户可信环境的信任链或网站被攻破,账户访问可能被截获。因此,为了用户安全,一些应用程序放弃这些规则或约定,并不支持通用密码。

账户注册

通常,在注册用户时,应用程序会自行生成密码。但也有例外,当应用程序允许使用自己的密码生成密钥时。该库允许在viz.auth.toWif(account_login,general_pass,auth_type);方法中独立指定用于生成密钥的字符串。以下示例显示了一个生成给定长度的随机密码并使用它生成密钥的函数,无需绑定到用户和访问类型:

function pass_gen(length=100,to_wif=true){
    let charset='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-=_:;.,@!^&*$';
    let ret='';
    for (var i=0,n=charset.length;i<length;++i){
        ret+=charset.charAt(Math.floor(Math.random()*n));
    }
    if(!to_wif){
        return ret;
    }
    let wif=viz.auth.toWif('',ret,'');
    return wif;
}

您可以使用viz.auth.wifToPublic(wif)方法通过指定的私钥获取公钥。对于那些希望通过连接登录名、密码和访问类型来生成密钥的应用程序,可以使用viz.auth.getPrivateKeys(account_login,general_pass,auth_types)方法。该方法使用result.type模板返回私钥数组(将传递给用户)和result.typePubkey模板返回公钥数组(需要传输到区块链以保存到用户账户)。使用主密码在VIZ中注册新账户的函数代码:

var user_login='test';//registrator account
var active_key='5K...';//private active key

function create_account_with_general_pass(account_login,token_amount,shares_amount,general_pass){
    let fixed_token_amount=''+parseFloat(token_amount).toFixed(3)+' VIZ';//tokens that will be transferred to the share of the new account
    let fixed_shares_amount=''+parseFloat(shares_amount).toFixed(6)+' SHARES';//the share that will be delegated to the new account
    if(''==token_amount){
        fixed_token_amount='0.000 VIZ';
    }
    if(''==shares_amount){
        fixed_shares_amount='0.000000 SHARES';
    }
    let auth_types = ['regular','active','master','memo'];//access types
    let keys=viz.auth.getPrivateKeys(account_login,general_pass,auth_types);
    //access types contain public keys with a weight sufficient for performing operations
    let master = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.masterPubkey, 1]
        ]
    };
    let active = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.activePubkey, 1]
        ]
    };
    let regular = {
        "weight_threshold": 1,
        "account_auths": [],
        "key_auths": [
            [keys.regularPubkey, 1]
        ]
    };
    let memo_key=keys.memoPubkey;
    let json_metadata='';
    let referrer='';
    viz.broadcast.accountCreate(active_key, fixed_token_amount, fixed_shares_amount, user_login, account_login, master, active, regular, memo_key, json_metadata, referrer, [],function(err,result){
        if(!err){
            console.log('VIZ Account: '+account_login+'\r\nGeneral pass (for private keys): '+general_pass+'\r\nPrivate master key: '+keys.master+'\r\nPrivate active key: '+keys.active+'\r\nPrivate regular key: '+keys.regular+'\r\nPrivate memo key: '+keys.memo+'');
        }
        else{
            console.log(err);
        }
    });
}

创建凭证(邀请码)

VIZ区块链中存在凭证机制。任何人都可以通过向其中转移VIZ代币来创建它们。凭证可以兑换或用作邀请码以简化新账户的注册。在第一种情况下,代币转移给持有者账户;在第二种情况下,代币转换为新账户的网络份额(所有访问类型使用单一访问密钥)。

创建凭证的示例代码:

var user_login='test';
var active_key='5K...';//private active key
var fixed_amount='100.000 VIZ';//the number of tokens spent on the voucher
var private_key=pass_gen();//generating a private key
var public_key=viz.auth.wifToPublic(private_key);//getting a public key from a private one
viz.broadcast.createInvite(active_key,user_login,fixed_amount,public_key,function(err,result){
    if(!err){
        console.log('The voucher has been created, the public key for verification: '+public_key+', private key to use: '+private_key);
    }
    else{
        console.log(err);
    }
});

通过邀请码注册

为了匿名使用邀请码,系统中存在一个invite账户,其私密活跃密钥为5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW,代码示例如下:

var receiver='newtestaccount';//new account login
var secret_key='5K...';//private key of the invite code
var private_key=pass_gen();//generating a private key
var public_key=viz.auth.wifToPublic(private_key);//getting a public key from a private one
viz.broadcast.inviteRegistration('5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW','invite',receiver,secret_key,public_key,function(err,result){
    if(!err){
        console.log('Account '+receiver+' registered, shared private key for all access types: '+private_key);
    }
    else{
        console.log(err);
    }
});

凭证兑换

为了匿名使用凭证,系统中存在一个invite账户,其私密活跃密钥为5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW,代码示例如下:

var receiver='test';//account login
var secret_key='5K...';//private key of the invite code
var public_key=viz.auth.wifToPublic(secret_key);//getting the public key of the voucher
viz.broadcast.claimInviteBalance('5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW','invite',receiver,secret_key,function(err,result){
    if(!err){
        console.log('Account '+receiver+' successfully redeemed a voucher with a public key '+public_key);
    }
    else{
        console.log(err);
    }
});

将VIZ代币转换为份额

为了自动将所有可用的VIZ代币转换为SHARES网络的份额,您需要请求账户信息并使用transfer_to_vesting操作将它们转换为自己的份额:

var current_user='test';
var active_key='5K...';//private active key
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //response received
        if(typeof response[0] !== 'undefined'){
            if('0.000 VIZ'!=response[0].balance){
                viz.broadcast.transferToVesting(active_key,current_user,current_user,response[0].balance,function(err,result){
                    if(!err){
                        console.log('convertation to a network share',response[0].balance);
                        console.log(result);
                    }
                    else{
                        console.log(err);
                    }
                });
            }
            else{
                console.log('the balance is at zero');
            }
        }
        else{
            console.log('the account was not found',current_user);
        }
    }
    else{
        //error
        console.log(err);
    }
});

将份额转换为VIZ代币

新开发者常常面临这样的问题:用户已将部分代币委托给另一个账户,他们需要计算可用于将SHARES转换为VIZ的可用份额。以下代码示例自动设置可用于从份额中提取转换的SHARES:

var current_user='test';
var active_key='5K...';//private active key
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //response received
        if(typeof response[0] !== 'undefined'){
            vesting_shares=parseFloat(response[0].vesting_shares);
            delegated_vesting_shares=parseFloat(response[0].delegated_vesting_shares);
            shares=vesting_shares - delegated_vesting_shares;
            let fixed_shares=''+shares.toFixed(6)+' SHARES';
            console.log('SHARES available for convertation',fixed_shares);
            viz.broadcast.withdrawVesting(active_key,current_user,fixed_shares,function(err,result){
                if(!err){
                    console.log('launching the convertation of the network share into VIZ tokens',fixed_shares);
                    console.log(result);
                }
                else{
                    console.log(err);
                }
            });
        }
        else{
            console.log('account was not found',current_user);
        }
    }
    else{
        //error
        console.log(err);
    }
});

代币转账

从账户余额向委员会转账1.000 VIZ的示例:

var current_user='test';
var active_key='5K...';//private active key
var target='committee';
var fixed_amount='1.000 VIZ';
var memo='备注';//utf-8 including emoji
viz.broadcast.transfer(active_key,current_user,target,fixed_amount,memo,function(err,result){
    if(!err){
        //response received
        console.log(result);
    }
    else{
        //error
        console.log(err);
    }
});

奖励网络成员

账户可以使用award操作奖励另一个网络成员。您可以指定目标奖励的目的、原因(custom_sequence编号或memo备注)以及受益人(将分享目标奖励的账户)。 示例:

var current_user='test';//the awarder account
var regular_key='5K...';//the private regular key of the awarder

var target='viz.plus';//the award recipient - the customer of the article
var energy='1000';//10.00% will be spent from the actual energy of the account
var custom_sequence=0;//the number of the custom operation
var memo='感谢您的VIZ Cookbook';//utf-8 including emoji
var beneficiaries_list=[{"account":"on1x","weight":2000}];//20% to the author of the article
viz.broadcast.award(regular_key,current_user,target,energy,custom_sequence,memo,beneficiaries_list,function(err,result){
    if(!err){
        //response received
        console.log(result);
    }
    else{
        //error
        console.log(err);
    }
});

更改账户密钥

有时需要更改账户的访问权限。这可能涉及添加新密钥、委托管理或创建多重签名账户管理条件(当操作需要多个密钥签名时)。

执行操作的权限具有以下结构:

  • weight_threshold — 批准具有必要操作类型的交易所需的权重;
  • account_auths — 账户及其权重的数组。账户在添加密钥签名时可以为交易增加权重;
  • key_auths — 公钥及其权重的数组。

系统检查交易及其中的操作,验证相关账户的签名以及它们是否具有足够权重来执行所需类型的操作。

单密钥权限示例:

{
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 1]
    ]
}

如果账户将这些权限写入常规访问类型,那么要执行奖励操作,区块链将需要使用密钥5KRLZitDd5c9uZzDgTMF4se4eVewENtZ29GbCuKwbT3msRbtLgi(对应权限中指定的公钥VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA)对交易进行签名。

当将控制权委托给另一个账户时,例如test,需要将权限更改为:

{
    "weight_threshold": 1,
    "account_auths": [
        ["test", 1]
    ],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 1]
    ]
}

此后,区块链将要求使用指定密钥或test账户类似访问类型的密钥进行签名。

多重签名管理意味着权限的复杂化,例如,要实现3个密钥中需要2个签名才能管理,可以使用以下权限:

{
    "weight_threshold": 4,
    "account_auths": [],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 2],
        ["VIZ5mK1zLnYHy7PbnsxRpS4NbKjEoH2J9eBmgSjVKJ5BKQpLLj9T4", 2],
        ["VIZ4uiqeDPsoteSFVbTWPBUbmfzxYkJyXYmA6B1pAFWZ59n4iBuUK", 2]
    ]
}

为了让交易被区块链接受,必须添加3个指定密钥中至少2个的签名。在此示例中,公钥 VIZ4uiqeDPsoteSFVbTWPBUbmfzxYkJyXYmA6B1pAFWZ59n4iBuUK 对应私钥 5KMBKopgd56MZvV8FYhp7AWFyLKiybqRnZYgjXukw34VRE78

让我们考虑一个重置账户访问权限(更改所有密钥和权限)的示例:

//the function requires a private master key from the account
function reset_account_with_general_pass(account_login,master_key,general_pass){
    if(''==general_pass){
        //if a general password is not specified, we will generate it
        general_pass=pass_gen(50,false);
    }
    let auth_types = ['regular','active','master','memo'];
    let keys=viz.auth.getPrivateKeys(account_login,general_pass,auth_types);
    let master = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.masterPubkey, 1]
        ]
    };
    let active = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.activePubkey, 1]
        ]
    };
    let regular = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.regularPubkey, 1]
        ]
    };
    let memo_key=keys.memoPubkey;
    viz.api.getAccounts([account_login],function(err,response){
        if(0==response.length){
            err=true;
        }
        if(!err){
            let json_metadata=response[0].json_metadata;
            viz.broadcast.accountUpdate(master_key,account_login,master,active,regular,memo_key,json_metadata,function(err,result){
                if(!err){
                    console.log('Reset Account: '+account_login+'\r\nGeneral pass (for private keys): '+general_pass+'\r\nPrivate master key: '+keys.master+'\r\nPrivate active key: '+keys.active+'\r\nPrivate regular key: '+keys.regular+'\r\nPrivate memo key: '+keys.memo+'');
                }
                else{
                    console.log(err);
                }
            });
        }
        else{
            console.log("The user was not found");
        }
    });
}

声明账户为委托者

每个委托者都有一个区块签名密钥,负责区块链对区块签名的验证。委托者节点配置有私有的签名密钥(最好提前生成),并将公钥发送到区块链以验证签名。如果委托者长时间缺席,区块链会通过将签名密钥清零来将其标记为禁用。您可以通过设置空的签名密钥viz111111111111111111111111111111111114t1anm来断开连接。声明账户为委托者的示例代码:

var account_login='test';
var active_key='5K...';
var url='https://...';//link to the statement of intent to be a delegate
var private_key=pass_gen();//generating a private key
var signing_key=viz.auth.wifToPublic(private_key);//public key
viz.broadcast.witnessUpdate(active_key,account_login,url,signing_key,function(err,result){
    if(!err){
        console.log('The delegate '+account_login+' declared a desire to be a delegate, a private key for signing blocks: '+private_key);
    }
    else{
        console.log(err);
    }
});

为委托者投票

为了使委托者开始签名区块,他必须拥有非零的投票权重。账户的份额在为多个委托者投票时会按比例分配。为委托者投票的示例代码:

var account_login='test';
var active_key='5K...';
var witness_login='witness';
var value=true;//boolean value of the voice (true - vote for a delegate, false - remove the vote)
viz.broadcast.accountWitnessVote(active_key,account_login,witness_login,value,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

将投票权转移给代理

如果用户不参与委托者的选择,他可以将自己的份额处置权委托给另一个账户。为此,有一个account_witness_proxy操作,注册器应用程序可以使用它,以免让用户被区块链系统的复杂信息所困扰,同时不失去自身的影响力(因为VIZ代币可以用于账户注册,应用程序将类似的网络份额潜力投资于用户)。

如果用户决定独立参与委托者的选择,第一次为委托者投票将取消代理。

var account_login='test';
var active_key='5K...';
var proxy_login='proxy';
viz.broadcast.accountWitnessProxy(active_key,account_login,proxy_login,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

设置网络投票参数

委托者广播他们对网络投票参数的立场。区块链系统每经过一个委托者队列周期(21个区块)计算一次投票参数的中位数值,并在该周期内固定这些值。有关需要投票的网络参数的更多信息,请参阅VIZ中的对象和结构部分。委托者广播网络投票参数的versioned_chain_properties_update操作示例:

var account_login='test';
var active_key='5K...';//private active key

var props={};
props.account_creation_fee='1.000 VIZ';
props.create_account_delegation_ratio=10;
props.create_account_delegation_time=2592000 ;
props.bandwidth_reserve_percent=1;
props.bandwidth_reserve_below='1.000000 SHARES';
props.committee_request_approve_min_percent=1000;
props.flag_energy_additional_cost=1000;//outdated parameter
props.min_curation_percent=0;//outdated parameter
props.max_curation_percent=10000;//outdated parameter
props.min_delegation='1.000 VIZ';
props.vote_accounting_min_rshares=5000000 ;
props.maximum_block_size=65536;

props.inflation_witness_percent=2000;
props.inflation_ratio_committee_vs_reward_fund=7500;
props.inflation_recalc_period=806400;

props.data_operations_cost_additional_bandwidth=0;
props.witness_miss_penalty_percent=100;
props.witness_miss_penalty_duration=86400;


viz.broadcast.versionedChainPropertiesUpdate(active_key,account_login,[2,props],function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

向委员会提交申请

var account_login='test';
var regular_key='5K...';//private regular key
var url='https://...';//URL with the application description
var worker='test';//the login of the account that, if approved, will receive funds from their committee fund
var min_amount='0.000 VIZ';//the minimum number of VIZ tokens to satisfy the application
var max_amount='10000.000 VIZ';//maximum number of VIZ tokens
var duration=5*24*3600;//the duration of the request in seconds (must be between COMMITTEE_MIN_DURATION (5 days) and COMMITTEE_MAX_DURATION (30 days))

viz.broadcast.committeeWorkerCreateRequest(regular_key,account_login,url,worker,min_amount,max_amount,duration,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

取消委员会中的申请

只有申请的创建者才能取消申请。

var account_login='test';
var regular_key='5K...';//private regular key
var request_id=14;//number of the canceled application of the committee

viz.broadcast.committeeWorkerCancelRequest(regular_key,account_login,request_id,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

对委员会申请进行投票

任何网络成员都可以对申请进行投票。在申请有效期结束时,根据投票者总权重中的份额以及他们与申请设定条款的同意程度(从-100%到+100%)从委员会计算满足的支付金额。如果投票者的网络份额金额超过网络的投票参数committee_request_approve_min_percent,并且估算金额在申请设定的框架内,则申请被委员会批准并进入支付队列。整个操作原理可以在database.cpp committee_processing() 方法文件中探索。为申请投票的示例代码:

var account_login='test';
var regular_key='5K...';//private regular key
var request_id=15;//application number of the committee
var percent=8000;//80% percentage of the maximum amount of the application, for which the voting person considers it correct to satisfy the application
viz.broadcast.committeeVoteRequest(regular_key,account_login,request_id,percent,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

付费订阅

VIZ中的付费订阅系统允许任何账户设置协议条款,签署后VIZ代币将从订阅者转移到协议提供者的余额中。该系统允许提供者更改协议条款,并在活动订阅到期和续订时自动尝试与订阅者协商达成一致,且不损害订阅者利益。订阅者可以指定他是根据协议一次性向提供者付款,还是同意在其余额中有足够VIZ代币的情况下自动续订订阅。发布付费订阅协议条款:

var account_login='test';
var active_key='5K...';//private active key
var url='https://...';//URL with a description of the paid subscription and agreement
var levels=3;//the number of paid subscription levels (the provider decides the number of levels and what it will provide for each), if you specify 0, then new subscriptions will not be able to be issued
var amount='100.000 VIZ';//the number of VIZ tokens for each paid subscription level
var period=30;//subscription validity period (number of days)
viz.broadcast.setPaidSubscription(active_key,account_login,url,levels,amount,period,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

希望签订付费订阅协议的账户必须向系统确认协议条款、所需的订阅级别以及自动续订的需求。您也可以更改订阅级别,系统将自动重新计算并扣除所需金额,或延长协议到期时间:

var account_login='subscriber';
var active_key='5K...';//private active key
var provider_account='test';//login of the paid subscription provider's account
var level=2;//the desired level of paid subscription
var amount='100.000 VIZ';//the number of VIZ tokens for each level according to the agreement
var period=30;//subscription validity period under the agreement
var auto_renewal=true;//the need for automatic renewal
viz.broadcast.paidSubscribe(active_key,account_login,provider_account,level,amount,period,auto_renewal,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

获取付费订阅协议条款的信息:

var provider_account='test';
viz.api.getPaidSubscriptionOptions(provider_account,function(err,response){
    if(!err){
        console.log(response);
    }
    else{
        console.log(err);
    }
});

您可以通过API请求获取活跃或非活跃的付费订阅列表:

var account_login='subscriber';
viz.api.getActivePaidSubscriptions(account_login,function(err,response){
    for(let i in response){
        console.log('A paid subscription agreement with the account is valid '+response[i]);
    }
}
viz.api.getInactivePaidSubscriptions(account_login,function(err,response){
    for(let i in response){
        console.log('A paid subscription agreement with the account is valid'+response[i]);
    }
}

您可以通过API请求检查当前协议:

var account_login='subscriber';
var provider_account='test';
viz.api.getPaidSubscriptionStatus(account_login,provider_account,function(err,response){
    if(!err){
        console.log('Agreement with the account '+response.creator);
        console.log('Status of the agreement: '+(response.active?'active':'inactive'));
        console.log('Auto-renewal: '+(response.auto_renewal?'enabled':'disabled'));
        console.log('Subscription level: '+response.level);
        console.log('The cost of the subscription level: '+(response.amount/1000)+' VIZ');
        console.log('Subscription period in days: '+response.period);
        console.log('Agreement start date: '+response.start_time);
        if(response.active){
            console.log('Expiration date of the agreement: '+response.next_time);
        }
    }
    else{
        console.log(err);
    }
});

委托份额

在VIZ区块链中,可以将份额委托给另一个账户,这允许您转移与奖励、委员会投票和获取网络带宽相关的份额管理权。委托不适用于为委托者投票,因为存在通过account_witness_proxy操作转移其全部份额投票权的单独操作。

份额委托的规则部分在网络协议中规定,部分由委托者控制:

  • 最低委托金额由委托者通过投票参数min_delegation设置;
  • 创建新账户时的委托保证金系数由委托者通过投票参数create_account_delegation_ratio设置;
  • 创建账户时的委托持续时间(无法取消)由委托者通过投票参数create_account_delegation_time设置;
  • 最低委托持续时间在协议中设置(CHAIN_CASHOUT_WINDOW_SECONDS常量等于一天);

委托时会触发防止滥用双重消耗能量的保护机制。如果委托者转移其50%的份额,则能量将减少50%。在这种情况下,能量可能变为负值(最多-100%)。值得注意的是,delegate_vesting_shares操作设置的是委托份额的实际值。如果您想取消委托,则需要将委托份额的值设置为0.000000 SHARES。如果您想将委托从1000.000000 SHARES增加到3000.000000 SHARES,则只需将委托值设置为3000.000000 SHARES

将份额委托给另一个账户的示例代码:

var account_login='test';
var active_key='5K...';//private active key
var delegatee='recipient';//target of delegation
var shares='100.000000 SHARES';
viz.broadcast.delegateVestingShares(active_key,account_login,delegatee,shares,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

获取委托相关信息:

var account_login='test';
var start_from=0;
var count=1000;
var type=0;//0 - outgoing delegation, 1 - incoming delegation
viz.api.getVestingDelegations(account_login,start_from,count,type,function(err,response){
    if(!err){
        if(0==response.length){
            console.log('There are no records of the delegated share');
        }
        for(delegation in response){
            console.log('Delegated to the account '+response[delegation].delegatee+', '+response[delegation].vesting_shares+', can be revoked after '+response[delegation].min_delegation_time);
        }
    }
});

获取委托取消后委托份额返还的相关信息:

var account_login='test';
var start_from=new Date().toISOString().substr(0,19);//searching for the return of the delegated share after the date issued in ISO format
var count=1000;
viz.api.getExpiringVestingDelegations(account_login,start_from,count,function(err,response){
    if(!err){
        if(0==response.length){
            console.log('There are no records of the delegated share being returned');
        }
        for(delegation in response){
            console.log(response[delegation].vesting_shares+' will return '+response[delegation].expiration);
        }
    }
});

自定义操作

当开发者需要在区块链中引入自己的结构,构建一个能够监控区块并记录网络中操作的去中心化应用(dApp)时,他们可以使用自定义操作。自定义操作具有灵活的结构:

  • required_active_auths — 账户数组,交易应包含这些账户使用活跃密钥的签名;
  • required_regular_auths — 账户数组,交易应包含这些账户使用常规密钥的签名;
  • custom_name — 自定义操作类别的名称(开发者自行决定其应用程序使用的名称);
  • custom_json — JSON格式的任意结构;

开发者可以通过自定义操作设计自己的数据结构、命令协议及其记录方式。例如,可以用于卡牌游戏、媒体博客、评论、产品目录或广告区块处理。

使用自定义操作的示例:

var account_login='test';
var required_active_auths=[];
var required_regular_auths=[account_login];
var private_key='5K...';//the private key of the desired access type (in this case, regular)
var custom_name='file_app';
var custom_json='{"directory":"/photos/2020/viz_conf/","filename":"moscow_camp.jpg","url":"https://..."}';
viz.broadcast.custom(private_key,required_active_auths,required_regular_auths,custom_name,custom_json,function(err,result){
    console.log(err,result);
});

出售账户

账户所有者可以使用主访问权限将其挂牌出售:

var account_login='test';
var master_key='5K...';
var seller_login='reseller';//the login of the account that will receive tokens upon successful sale of the account
var fixed_price_amount='10000.000 VIZ';//account price
var on_sale=true;//put the account up for sale (if false, then remove it from sale)
viz.broadcast.setAccountPrice(master_key,account_login,seller_login,fixed_price_amount,on_sale,function(err,result){
    console.log(err,result);
});

您也可以将子账户(*.登录名)挂牌出售:

var account_login='test';
var master_key='5K...';
var seller_login='test';//the login of the account that will receive tokens upon successful sale of the account
var fixed_price_amount='1000.000 VIZ';//subaccount price
var on_sale=true;//put up subaccounts for sale (if false, then remove them from sale)
viz.broadcast.setSubaccountPrice(master_key,account_login,seller_login,fixed_price_amount,on_sale,function(err,result){
    console.log(err,result);
});

您可以使用buy_account操作购买账户或子账户,系统将检查购买的可能性并在条件允许时完成交易:

var account_login='buyer';
var active_key='5K...';
var subaccount_login='enjoy.test';//purchasing of a subaccount from an account named "test"
var account_offer_price='1000.000 VIZ';//the agreed offer price (if the price changes, the blockchain will refuse the operation)
var private_key=pass_gen();//generating a private key
var public_key=viz.auth.wifToPublic(private_key);//getting a public key from a private key
var token_to_shares='5.000 VIZ';//additionally spending tokens to converting to a share of a new account
viz.broadcast.buyAccount(active_key,account_login,subaccount_login,account_offer_price,public_key,token_to_shares,function(err,result){
    if(!err){
        console.log('Buying an account '+account_login+' passed successfully, private shared key is '+private_key);
        console.log(result);
    }
    else{
        console.log(err);
    }
});

三方托管交易

三方交易基于担保人(agent)检查条件履行的原则运作。接收者和担保人必须通过escrow_approve操作确认交易开始(担保人在此阶段获得佣金),否则,当批准截止日期(ratification_deadline)到达时,所有代币将返还给交易发起者(database.cpp文件中的expire_escrow_ratification方法)。

如果出现争议时刻,发送方或接收方可以通过escrow_dispute操作启动争议处理程序,之后交易的决定权转移给担保人(由他决定谁将获得多少代币)。如果交易被暂停且长时间未解决——合约将到期(escrow_expiration),所有代币由担保人管理(如果争议已开启),或由交易任何一方管理。

创建托管转账:

var account_login='test';
var active_key='5K...';
var receiver_login='receiver';
var token_amount='100.000 VIZ';//number of tokens to be transferred
var escrow_id=1;//the escrow number, assigned manually for the approval of the application (uint32)
var agent_login='agent';
var fee='10.000 VIZ';//the guarantor's commission
var json_metadata='{}';//additional metadata in json format
var ratification_deadline=new Date().toISOString().substr(0,19);//the date of the forced completion of the transaction and the refund of funds if the guarantor and the recipient have not confirmed their participation in the transaction (deadline in the ISO format of the form 2019-10-17T13:30:18)
var escrow_expiration=new Date().toISOString().substr(0,19);//the deadline for making a decision on the transaction, after which it is impossible to initiate a dispute (deadline in the ISO format of the form 2019-10-17T13:30:18)
viz.broadcast.escrowTransfer(active_key,account_login,receiver_login,token_amount,escrow_id,agent_login,fee,json_metadata,ratification_deadline,escrow_expiration,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

确认在提议条件下参与交易(担保人和接收者必须通过escrow_approve操作确认他们的参与):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//the escrow number is assigned manually for the approval of the application (uint32)

var who=agent_login;//who confirms their participation
var active_key='5K...';//the key confirming the sides (who)

var approve=true;//false in case of refusal to participate in the transaction
viz.broadcast.escrowApprove(active_key,account_login,receiver_login,agent_login,who,escrow_id,approve,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

争议请求(发送方或接收方可以通过escrow_dispute操作对交易提出争议):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//the escrow number is assigned manually for the approval of the application (uint32)


var who=receiver_login;//who confirms their participation
var active_key='5K...';//the key confirming the sides (who)

viz.broadcast.escrowDispute(active_key,account_login,receiver_login,agent_login,who,escrow_id,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

释放资金(escrow_release操作):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//the escrow number is assigned manually for the approval of the application (uint32)
var token_amount='100.000 VIZ';//number of tokens to be transferred

var who=receiver_login;//who decided to release the funds (if a dispute is open, then only the guarantor decides to whom and how much to transfer)
var active_key='5K...';//key of the initiator of the operation (who)
var receiver=account_login;//the recipient of funds can only be the other side of the transaction before the expiration of the transaction, otherwise-any of the sides of the transaction

viz.broadcast.escrowRelease(active_key,account_login,receiver_login,agent_login,who,receiver,escrow_id,token_amount,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

要获取托管交易信息,您需要调用database_api插件的get_escrow API请求:

var account_login='test';
var escrow_id=1;
viz.api.getEscrow(account_login,escrow_id,function(err,response){
    if(!err){
        //response received
        console.log(response);
    }
    else{
        //error
        console.log(err);
    }
});

Account restoration

创建账户时,创建者会被记录在recovery账户字段中,作为恢复账户访问权限的受信任人,以防账户被黑客攻击和访问密钥被更改。区块链会记录主权限的更改并将其保存30天。正是在这30天内,受信任账户可以通过request_account_recovery操作创建恢复访问权限的请求:

var recovery_account='escrow';
var active_key='5K...';

var account_to_recover='test';
var private_key=pass_gen();//we generate a private key (we transfer the account owner or agree with him the public key for master permissions)
var public_key=viz.auth.wifToPublic(private_key);//getting a public key from a private one

var new_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [public_key, 1]
    ]
};//new master permissions
var extensions=[];//additional field, not used
viz.broadcast.requestAccountRecovery(active_key,recovery_account,account_to_recover,new_master_authority,extensions,function(err,result) {
    console.log(err,result);
});

创建更改访问权限的请求后,账户必须通过recover_account操作确认它。一个重要点是,交易必须同时使用两个密钥签名——来自信任账户申请中的旧密钥和新密钥,因此您需要使用viz.broadcast.send来构建交易:

var account_login='test';

var new_master_key='5K...';//new private master key
var new_master_public_key=viz.auth.wifToPublic(new_master_key);//public key from the private master key (agreed with trusted account)
var new_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [new_master_public_key, 1]
    ]
};//new master permissions

var recent_master_key='5K...';//old private master key for proof of identification
var recent_master_public_key=viz.auth.wifToPublic(recent_master_key);//old public master key
var recent_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [recent_master_public_key, 1]
    ]
};//old master credentials as proof of identification
var extensions=[];//additional field, not used

var operations=['recover_account',['account_to_recover':account_login,'new_master_authority':new_master_authority,'recent_master_authority':recent_master_authority,'extensions':extensions]];

var tx={'extensions':[],operations};

viz.broadcast.send(tx,[recent_master_key,new_master_key],function(err,result) {
    console.log(err,result);
});

要更改用于恢复访问的受信任账户,您可以使用change_recovery_account操作,更改将在30天后生效(以防止滥用):

var account_login='test';
var master_key='5K...';//master key
var new_recovery_account='escrow';//new trusted account
var extensions=[];//additional field, not used
viz.broadcast.changeRecoveryAccount(master_key,account_login,new_recovery_account,extensions,function(err,result) {
    console.log(err,result);
});

提议操作系统

为了管理提议操作系统,有3种操作:创建提议、提供签名(更新)、删除提议。创建提议后,区块链系统将等待所有必要的签名以执行嵌入在提议内部的操作,之后将执行这些操作。如果到期日期已到,提议将不会被执行。提议操作系统用于管理多重签名账户。要创建提议,请使用 proposal_create 操作:

var account_login='test';
var active_key='5K...';//active private key
var title='payments-14';//the name of the offer (plays the role of an identifier, must be unique)
var memo='Платежи по договору №14';

var expiration_date=new Date();//expiration date when the proposed operations will be rejected or executed upon receipt of all necessary signatures
expiration_date.setDate(expiration_date.getDate() + 10);//plus ten days from the current moment
var expiration_time=expiration_date.toISOString().substr(0,19);//the expiration date of the offer in ISO format
console.log('expiration_time',expiration_time);

var proposed_operations=[];
proposed_operations.push({'op':['transfer',{'from':'escrow','to':'test','amount':'10.000 VIZ','memo':memo}]});
proposed_operations.push({'op':['transfer',{'from':'escrow2','to':'test','amount':'10.000 VIZ','memo':memo}]});

var review_period_date=new Date(expiration_date.getTime() - 10);//signature acceptance termination date (minus 10 seconds before expiration date)
var review_period_time=review_period_date.toISOString().substr(0,19);//the expiration date of the offer in ISO format
console.log('review_period_time',review_period_time);

var extensions=[];

viz.broadcast.proposalCreate(active_key,account_login,title,memo,expiration_time,proposed_operations,review_period_time,extensions,function(err,result){
    console.log(err,result);
});

要获取用户提出的提议信息,您需要向database_api插件执行get_proposed_transactions API请求(将返回一个提议数组):

var looking_account='test';
var from=0;
var limit=100;
viz.api.getProposedTransactions(looking_account,from,limit,function(err,response){
    console.log(err,response);
});

提议系统支持区块链中所有现有的操作,但不允许混合需要不同权限的操作(例如,需要活跃权限的transfer操作和需要常规权限的award操作)。要提供所需访问类型的签名,您可以使用proposal_update操作,通过在数组中指定交易签名者的登录名来添加或移除确认提议的签名者列表(为此有4种数组类型:活跃、主权限、常规和用于单个密钥的密钥数组)。一旦提供了所有必需的签名,提议中的操作将被执行(前提是未指定review_period_time周期)。如果执行出错,将在到期时进行第二次尝试。 示例:

var account_login='escrow';
var active_key='5K...';//active private key

var proposal_author='test';//the author of the offer
var proposal_title='payments-14';//offer ID

var active_approvals_to_add=[];//the list of accounts that signed this transaction with the active access type for confirming the offer
var active_approvals_to_remove=[];//list of accounts to delete from the list of those who confirmed the offer
var master_approvals_to_add=[];
var master_approvals_to_remove=[];
var regular_approvals_to_add=[];
var regular_approvals_to_remove=[];
var key_approvals_to_add=[];
var key_approvals_to_remove=[];
var extensions=[];

active_approvals_to_add.push(account_login);

viz.broadcast.proposalUpdate(active_key,proposal_author,proposal_title,active_approvals_to_add,active_approvals_to_remove,master_approvals_to_add,master_approvals_to_remove,regular_approvals_to_add,regular_approvals_to_remove,key_approvals_to_add,key_approvals_to_remove,extensions,function(err,result){
    console.log(err,result);
});

提议可以由申请人或任何需要其签名的参与者删除。为此,只需执行proposal_delete操作并使用活跃密钥进行签名:

var account_login='escrow2';//participant of the offer
var active_key='5K...';//active private key

var proposal_author='test';//the author of the offer
var proposal_title='payments-14';//offer ID

var extensions=[];

viz.broadcast.proposalDelete(active_key,proposal_author,proposal_title,account_login,extensions,function(err,result){
        console.log(err,result);
});

在同一交易中由同一账户创建延迟奖励操作并使用活跃密钥和常规密钥对交易进行签名的实现示例:

var expiration_date=new Date(new Date().getTime() + 20000);//+20 seconds from the current moment
var expiration_time=expiration_date.toISOString().substr(0,19);//the expiration date of the offer in ISO format
var review_period_date=new Date(expiration_date.getTime() - 10000);//signature acceptance termination date (minus 10 seconds from the expiration date)
var review_period_time=review_period_date.toISOString().substr(0,19);//the expiration date of the offer in ISO format

var login='test';
var active_wif='5K...';
var regular_wif='5J...';

var target='committee';//the target of the award operation
var energy=200;//2%
var memo='通过提案实现延迟奖励';

var regular_public_wif = viz.auth.wifToPublic(regular_wif);//to add to key_approvals_to_add via the proposal_update operation

const operations = [
["proposal_create",{
    "author": login,
    "title": 'proposal-award',
    "memo": login + '-award',
    "expiration_time": expiration_time,
    "proposed_operations": [
    {"op":['award', {'initiator':login,'receiver':target,'energy':200,'custom_sequence':0,'memo':memo}]}],
    "review_period_time": review_period_time,
    "extensions": []
}],
["proposal_update",{
    "author": login,
    "title": 'proposal-award',
    "active_approvals_to_add": [],
    "active_approvals_to_remove": [],
    "owner_approvals_to_add": [],
    "owner_approvals_to_remove": [],
    "posting_approvals_to_add": [],
    "posting_approvals_to_remove": [],
    "key_approvals_to_add": [regular_public_wif],
    "key_approvals_to_remove": [],
    "extensions": []
}]
];

viz.broadcast.send({extensions:[],operations},[active_wif,regular_wif],function(err,result){
    console.log(err,result)
});

使用私钥签名数据并使用公钥验证签名

要使用私钥对数据进行签名(viz.auth.signature.sign方法,为了寻找规范签名,需要添加一个将在签名数据中的随机数),并使用公钥验证签名(viz.auth.signature.verifyData方法),您可以使用标准的viz-js-lib方法:

var private_key='5KRLZitDd5c9uZzDgTMF4se4eVewENtZ29GbCuKwbT3msRbtLgi';
var public_key='VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA';

var invalid_public_key='VIZ65kiW3JsxsF7NCabAuSJUk8Efhx5PW6cbgSS5uuZpbkSTpSjn6';

var data={data:"data signature check!",nonce:0};
var nonce=0;
var data_with_nonce='';
var signature='';

function auth_signature_check(hex){//checking for the canonicity of the signature
    if('1f'==hex.substring(0,2)){
        return true;
    }
    return false;
}

while(!auth_signature_check(signature)){
    data.nonce=nonce;
    data_with_nonce=JSON.stringify(data);
    signature=viz.auth.signature.sign(data_with_nonce,private_key).toHex();
    nonce++;
}
console.log('data with nonce',data_with_nonce);
console.log('signature',signature);

console.log('check by public_key',viz.auth.signature.verifyData(data_with_nonce,viz.auth.signature.fromHex(signature),public_key));
console.log('check by invalid_public_key',viz.auth.signature.verifyData(data_with_nonce,viz.auth.signature.fromHex(signature),invalid_public_key));

不使用库向VIZ公共节点发送js请求

如果您的应用程序不需要密码学和交易签名,那么您可以使用原生工具通过js进行json-rpc请求。

WebSocket连接示例:

var api_gate='wss://solox.world/ws';
var latency_start=new Date().getTime();
var latency=-1;
var socket = new WebSocket(api_gate);
socket.onmessage=function(event){
    latency=new Date().getTime() - latency_start;
    let json=JSON.parse(event.data);
    if(json.result){
        console.log(json.result);
    }
    else{
        console.log(json.error);
    }
    socket.close();
}
socket.onopen=function(){
    socket.send('{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}');
};

HTTP连接示例:

var api_gate='https://viz.lexa.host/';
var latency_start=new Date().getTime();
var latency=-1;
var xhr = new XMLHttpRequest();
xhr.overrideMimeType('text/plain');
xhr.open('POST',api_gate);
xhr.setRequestHeader('accept','application/json, text/plain, */*');
xhr.setRequestHeader('content-type','application/json');
xhr.onreadystatechange=function(){
    if(4==xhr.readyState && 200==xhr.status){
        latency=new Date().getTime() - latency_start;
        console.log(xhr);
        let json=JSON.parse(xhr.response);
        if(json.result){
            console.log(json.result);
        }
        else{
            console.log(json.error);
        }
    }
}
xhr.send('{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}');

生成交易

VIZ协议(使用Graphene代码库)中嵌入了形成交易的规则。使用加密密钥签名的数据必须符合代码中嵌入的所有二进制表示结构和规则。本节描述密钥的编码方式、数据的二进制表示或其准备过程。


用于签名的交易结构

需要根据嵌套操作账户中使用的相应访问类型密钥进行签名的数据结构,其二进制表示包含以下字段:

  • chain_id — 链ID,在VIZ中是通过对字符串VIZ进行fc::sha256::hash得到的:2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd。值得注意的是,fc::sha256::hash将字符串转换为c_str,并在其十六进制值56495A的开头添加字符串长度,最终是对十六进制值0356495A计算sha256;
  • tapos_link权益交易证明概念是指每笔交易都引用一个必须存在于链中的特定区块,才能使其生效。其二进制表示是交易参数ref_block_num和ref_block_prefix的表示。
  • expiration — 交易的过期unixtime(必须在过期时间之前包含到链中);
  • operations — 交易中的操作数组,每个操作的二进制表示包含协议中该操作的所有属性;
  • extensions — 交易的服务扩展数组(未使用,因此在二进制格式中是无元素的数组的十六进制值:00);

为什么需要chain_id

基于Graphene的区块链系统代码是开放和免费的,允许您启动新的链,既可以保持不变,也可以完全重新设计,拥有自己的机制和经济体系。此外,许多项目运行公共测试链以检查更改。为了防止节点混淆,防止同一网络中的交易不能在分叉(或具有类似账户和密钥的链)中使用,存在一个链标识符,它在每笔交易和签名操作中作为标签存在。

密钥格式

VIZ中的私钥和公钥基于DSA算法定位,并使用密码学来验证数据集的签名。许多非密码学专家的开发者只是使用专门的库,而不深入细节。

让我们考虑将私钥(由32字节组成)转换为可读的WIF格式的步骤:

  • 在密钥的十六进制表示前添加二进制前缀80
  • 从密钥二进制表示的sha256哈希值再次计算sha256哈希,以得到校验和;
  • 将校验和的前8个字节添加到密钥的末尾;
  • 使用base58算法和字母表123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz对得到的二进制结果进行编码;

将公钥(由32字节组成)逐步转换为可读格式:

  • 通过哈希密钥的二进制表示(使用ripemd 160算法)获取校验和;
  • 将校验和的前8个字节添加到密钥的末尾;
  • 使用base58算法和字母表123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz对得到的二进制结果进行编码;
  • 添加网络前缀(字符串值VIZ);

vis-js-lib库使用auth模块(GitHub链接),允许使用预安装的方法来处理密钥和签名数据。

不同数据类型在二进制形式中的表示

  • 字符串 — 字符串值在二进制形式中是一个包含字符串长度的字节和字符串本身(例如,账户登录名escrow的字符串值在二进制表示中对应十六进制值06657363726f77);
  • 整数 — 数值在二进制表示中被翻转,空维度用零填充(如果指定了值类型)。例如,award操作中指定的energy是uint16_t类型,传输只需2字节。如果需要传递10.00%的值,则整数值为1000,十六进制表示为03EB,其翻转值将为EB03。代表uint64_t的custom_sequence字段由8字节组成,因此要传输十进制值377,其十六进制值0179在二进制值中将是十六进制:7901000000000000
  • 日期 — JSON表示中的日期字段以ISO格式写入(例如UTC+0时区的2019-02-07T06:19:23,也称为GMT)。它们的二进制值以十进制unixtime格式写入,遵循整数表示规则。例如2019-02-07T06:19:23在unixtime中为1549520363(十六进制值为5C5BCDEB),其在二进制值中将是十六进制:EBCD5B5C
  • 资产 — VIZ或SHARES代币的二进制值是一个序列:一个8字节无精度的整数(0.012代表12,1.002代表1002),1字节代币精度(VIZ为03,SHARES为06),7字节的字符串代码值(VIZ为56495A00000000,SHARES为53484152455300)。例如:JSON操作值中的1.002 VIZ在十六进制中的二进制表示为:EA030000000000000356495A
  • 公钥 — 公钥值在二进制表示中包含33字节,第一个字节是用于恢复公钥的值(ECDSA中的恢复ID),16字节—公钥点在X轴上的坐标,最后16字节—在Y轴上的坐标。例如,026a1dbaacb805f145f9276025627102152840bb1aa09b7fac580f892d93b572b4二进制值对应一个恢复ID为02、X坐标为6a1dbaacb805f145f927602562710215(十六进制表示)、Y坐标为2840bb1aa09b7fac580f892d93b572b4(十六进制表示)的私钥。这对应于公钥VIZ5hDwvV1PPUTmehSmZecaxo1ameBpCMNVmYHKK2bL1ppLGRvh85
  • 操作类型 — 操作类型是根据VIZ协议中操作编号的整数值,写在1字节中(更多信息请参阅操作及其类型部分)。例如,transfer操作在二进制形式中将有十六进制条目02,而create_invite操作将有十六进制值2b

交易结构示例

让我们分析一个JSON格式的交易示例及其二进制表示:

{"ref_block_num":9023,"ref_block_prefix":1971875185,"expiration":"2019-02-07T06:19:23","operations":[["transfer",{"from":"test1","to":"test2","amount":"1.002 VIZ","memo":"<3"}]],"extensions":[]}
  • ref_block_num — 引用区块号与十六进制 ffff 进行按位与后的值(例如,数字9023的十六进制表示为 233F,根据整数表示规则应进行翻转,得到 3F23);
  • ref_block_prefix — 来自区块ID二进制状态的第5、6、7、8字节(共4字节),以十进制格式表示,可以通过向 database_api 插件发送 get_block_header API请求(指定下一个区块号9024)来获取。响应中将包含所需区块的 previous 字段,其ID为 0000233F716D887523BB63AD3E6107C96EDCFD8A。我们取 716D8875 作为二进制表示,翻转字节得到 75886D71,然后转换为JSON所需的十进制格式:1971875185
  • expiration — 交易过期的unixtime时间戳。2019-02-07T06:19:23 转换为unixtime为 1549520363(十六进制值为 5C5BCDEB),在二进制值中将被翻转并以十六进制表示为:EBCD5B5C
  • operations — 操作数组(因为数组中只有一个元素,十六进制表示为:01);
    • transfer — 代币转账操作(根据协议中操作编号的十六进制表示:02);
      • from — 发送者账户登录名(test1 的字符串长度和十六进制表示:057465737431);
      • to — 接收者账户登录名(test2 的字符串长度和十六进制表示:057465737432);
      • amount — 传输的VIZ代币数量(1.002 VIZ 的十六进制表示:EA030000000000000356495A00000000);
      • memo — 给接收者的备注(<3 的字符串长度和十六进制表示:023C33);
  • extensions — 交易的服务扩展数组(由于未使用且没有元素,其表示为 00)。

交易数据的最终二进制表示(十六进制):3F23716D8875EBCD5B5C0102057465737431057465737432EA030000000000000356495A00000000023C3300

要将交易发送到区块链,需要在此表示的开头补充 chain_id 并用私钥进行签名。得到的签名必须添加到JSON的 signatures 数组字段中,例如:

{"ref_block_num":9023,"ref_block_prefix":1971875185,"expiration":"2019-02-07T06:19:23","operations":[["transfer",{"from":"test1","to":"test2","amount":"1.002 VIZ","memo":"<3"}]],"extensions":[],"signatures":["1f500f2a5d721e45c53e76fca786d690c7c0556f1923aa07c944e26614b50481d353e88f82e731be74c18e3fb8d117dc992a475991974b6e1364a66f5ccb618f83"]}

并通过 network_broadcast_api 插件的 broadcast_transaction API 接口传递此 JSON 数据。

交易ID

节点的源代码将已签名交易的类型定义为 transaction_id_type,这在Graphene 协议中被写为 fc::ripemd160。但实践表明,交易ID并非ripemd160(哈希大小为20字节),而是sha256哈希的一部分(哈希大小为32字节)。不确定为什么会发生这种情况,但我们可以做出两种假设:

  • 这是有意为之,以减少交易哈希的大小(20字节代替32字节),但增加了碰撞风险;
  • 这是一个无意的错误,在启动Graphene链之前未被发现,后来决定不修复它(推测该错误发生在通过 digest_type::encoder 转换交易时,位于协议文件 transaction.cpp中);

值得注意的是,这个错误在EOS协议中得到了修复,EOS使用了fc::sha256交易类型,这表明这确实是一个错误。因此,在EOS之前的Graphene项目中,交易ID是sha256哈希,但是被截断的前20字节(而不是完整的32字节)。

例如,VIZ中的一笔交易来自区块11142739的ID是c84f9e8255859b2083be720cf9b64b3542e4360f。原始交易{"ref_block_num": 1612, "ref_block_prefix":2641357798,"expiration": "2019-10-22T05:59:27","operations":[["award",{"initiator":"on1x","receiver":"viz-social-bot","energy":20,"custom_sequence":0,"memo":"telegram:262632819","beneficiaries":[]}]],"extensions":[]}的十六进制表示为:4c06e6eb6f9dbf9aae5d012f046f6e31780e76697a2d736f6369616c2d626f74140000000000000000001274656c656772616d3a3236323633323831390000

原始交易的sha256哈希为:c84f9e8255859b2083be720cf9b64b3542e4360f0a62e33363bca5d984ee608a,其前20字节即为它在区块链中的标识符:c84f9e8255859b2083be720cf9b64b3542e4360f

获取ref_block_num和ref_block_prefix以形成交易

区块链节点存储最后65537个区块的ID(更多信息,请阅读关于VIZ中的TaPoS概念)。在大多数情况下,开发者会引用最近的一个区块,通常通过执行一系列操作来完成:

  • 通过向 database_api 插件发送 get_dynamic_global_properties API请求来获取系统状态数据;
  • 使用 head_block_number 字段的值减去3个区块,设定将基于哪个区块形成ref_block_num并请求其ID;
  • 通过向 database_api 插件执行 get_block_header API请求,获取所使用的区块ID,请求的区块号是所需区块号加一(因为每个区块的头部包含指向前一个区块ID的引用,所需的ID位于下一个区块中);
  • 从该标识符形成ref_block_prefix。

大多数包含抽象层以简化调用和向区块链传输交易的库都会自动完成此操作。

在viz-js-lib中手动获取 ref_block_numref_block_prefix 的示例可以在库本身的交易准备抽象层的源代码中找到。

在PHP中类似获取 ref_block_numref_block_prefix 的示例位于php-graphene-node-client库的源代码中。

操作中数据序列化的顺序

所有操作及其参数都记录在VIZ协议中,并位于 chain_operations.hpp 文件中。

正是在那里,您可以研究参数的类型及其在操作中的必需顺序。注意! 操作结构中的参数顺序与操作本身中的参数顺序并不一致。让我们以 escrow_transfer_operation 为例,该操作的结构(通常在操作之前有描述它的注释):

/**
 *  The purpose of this operation is to enable someone to send money contingently to
 *  another individual. The funds leave the *from* account and go into a temporary balance
 *  where they are held until *from* releases it to *to* or *to* refunds it to *from*.
 *
 *  In the event of a dispute the *agent* can divide the funds between the to/from account.
 *  Disputes can be raised any time before or on the dispute deadline time, after the escrow
 *  has been approved by all parties.
 *
 *  This operation only creates a proposed escrow transfer. Both the *agent* and *to* must
 *  agree to the terms of the arrangement by approving the escrow.
 *
 *  The escrow agent is paid the fee on approval of all parties. It is up to the escrow agent
 *  to determine the fee.
 *
 *  Escrow transactions are uniquely identified by 'from' and 'escrow_id', the 'escrow_id' is defined
 *  by the sender.
 */
struct escrow_transfer_operation : public base_operation {
    account_name_type from;
    account_name_type to;
    account_name_type agent;
    uint32_t escrow_id = 30;

    asset token_amount = asset(0, TOKEN_SYMBOL);
    asset fee;

    time_point_sec ratification_deadline;
    time_point_sec escrow_expiration;

    string json_metadata;

    void validate() const;

    void get_required_active_authorities(flat_set<account_name_type> &a) const {
        a.insert(from);
    }
};

操作中参数的顺序已在文件末尾通过以下方法定义:

FC_REFLECT((graphene::protocol::escrow_transfer_operation), (from)(to)(token_amount)(escrow_id)(agent)(fee)(json_metadata)(ratification_deadline)(escrow_expiration));

除了操作结构的描述外,validate 方法中还有参数处理逻辑,可以在 chain_operations.cpp 文件 中找到:

void escrow_transfer_operation::validate() const {
    validate_account_name(from);
    validate_account_name(to);
    validate_account_name(agent);
    FC_ASSERT(fee.amount >= 0, "fee cannot be negative");
    FC_ASSERT(token_amount.amount >=
              0, "tokens amount cannot be negative");
    FC_ASSERT(from != agent &&
              to != agent, "agent must be a third party");
    FC_ASSERT(fee.symbol == TOKEN_SYMBOL, "fee must be TOKEN_SYMBOL");
    FC_ASSERT(token_amount.symbol ==
              TOKEN_SYMBOL, "amount must be TOKEN_SYMBOL");
    FC_ASSERT(ratification_deadline <
              escrow_expiration, "ratification deadline must be before escrow expiration");
    validate_json_metadata(json_metadata);
}

大多数操作会检查相应权限的签名,例如,在 escrow_transfer_operation 结构中,get_required_active_authorities 方法会检查操作发起者(from 字段)的活跃权限签名。

VIZ+ Whitepaper