# 交易所 FAQ

# 常用信息

  • TOP RPC 文档 (opens new window)

  • TOP 浏览器 (opens new window)

  • TOP节点程序编译 (opens new window),执行时参考编译相关章节,也可以由官方提供编译好的二进制程序。

    • 说明:
    • 目前只能在centos上编译和运行,不支持在Ubuntu上编译。
    • 官方的RPC域名是有限流的,不能操作太频繁。交易所自己的节点,可以增加编译选项 noratelimit,来规避限流问题。
  • 搭建交易所节点 (opens new window)

    • 说明:
    • 交易所节点不需要质押TOP,只要有0.1TOP币就可以。
    • 有些节点端口,可以关闭。
      • TCP 19081 RPC服务 要使用,可以不开放到公网
      • TCP 19082 GRPC服务 可以关闭
      • TCP 19085 WEBSOCKET服务 可以关闭
      • TCP 8080 EVM RPC服务 可以关闭
      • UDP 9000和9001 节点同步消息 要使用,要开放到公网
    • 在没有搭建好交易所自己的节点之前,可以使用官方域名进行调试 http://mainnet.edge.topnetwork.org (opens new window)
  • 分片多链的说明

    • TOP是一条全状态分片的区块链,总共64个分片,分片也称为Table。一个分片具有一条独立的Table链,64个Table链的名称分别是Ta0000@0 ~ Ta0000@63。
    • 每个Table链是相互独立不影响的。
    • 对于交易所来说,我们强烈建议把账户地址创建到指定的一条table链上,好处是:
      • 同一个table内的账号之间转账,只需要一次共识,交易只会打包在一个区块上,交易实时性好。
      • 交易所只需要监控一条Table链,和非分片链一样的逻辑,监控方式简单。
    • 如何监控一条Table:
  • 多少个区块可以确认不回滚?

    • TOP采用BFT共识算法,一个交易会触发出3个块,分别是commit块,lock块,cert块,3个块之后,区块就变成确认不可回滚的状态。TOP的getBlock接口只能查询到确认状态的区块。
    • 所以,我们建议只要通过RPC接口查询到区块就认为区块确认了。不要再等待一定数量的区块才确认。
    • 如果采用多个区块确认的方式,可能会导致交易确认时间很长。因为TOP是分片链,单个TABLE上的交易量比较少,如果没有交易,就会停止出块。 当然,我们自己会随机定时发一下测试交易,但是这个交易是不可靠发的。
  • RPC接口的公共字段

    • 以RPC接口“getBlock”为例子:
    • RPC请求
    • curl -X 'POST' -d 'target_account_addr=Ta0000@2&body={"params":{"account_addr":"Ta0000@2","height":"latest"}}&method=getBlock&sequence_id=8&version=2.0'
    • RPC响应
{
  "data": {
    "value": {
      "body": {
        "tableblock": {
          "txs": [
            {
              "tx_consensus_phase": "send",
              "tx_hash": "0x33fce026ca88e9132bdf492dcbc8135660a156c35e7e5f4d2a6a0a7efd1f4ead"
            }
          ],
          "units": [
            {
              "account": "T00000Lf68NwzEfQT8BmVdZrvxotGxJNrbfxuq91",
              "unit_height": 138028
            }
          ]
        }
      },
      "hash": "66316913b18a3515cb39a4f00dd8639be159892daab3b9b9a172baecb884d658",
      "header": {
        "auditor": "T00000LgDBrJ4TpoyW3Lk15jPicZ2agQPNuvDAXg",
        "auditor_xip": "1000000000007aa3:f60000000004043",
        "multisign_auditor": "740000005f00000089205f1300000000210002782ae6869158e00f59cc0b003473c62f236a224b4eb3ed7084275243cc3df38d20000b21bf9333f982735b2d151d44abddd1dc86070e885a8445d6462b8c01b861284000bffbfadbc3d6b89f",
        "multisign_validator": "740000006700000089205f13000000002100021813ee23c0cdfa3abf043f7fbf6bb1c25a9ad8aef1eb93a333c68436f062d1482000c72b898282f346ea7cbd9d74cad543e7ed13b13755e180de986b65e0aee03ee880007bee3d7d9d7f7a66d4fb9d955ff9b2f6",
        "timerblock_height": 10664162,
        "validator_xip": "2000000000007aa3:f6000000000503f",
        "version": 327680
      },
      "height": 519281,
      "owner": "Ta0000@2",
      "prev_hash": "9baec8ff4e3129f113a85230f48fb208f98ae52b3f980780885862618e249f37",
      "table_height": 519281,
      "timestamp": 1679830820
    }
  },
  "errmsg": "OK",
  "errno": 0,
  "sequence_id": "8"
}

说明:

1)正常返回时,errno字段为0,errmsg为"ok"。查询失败时,errno为非0值,errmsg显示具体错误信息。

比如:账户地址非法,"errmsg": "account address is valid","errno": 1,

比如:账户地址或交易hash不存在,"errmsg": "account address or transaction hash error/does not exist","errno": 3,

2)请求头里面的"target_account_addr"字段是必须要填写的,目的是用于路由查找地址所在的分片。对于交易所节点来说,如果是查询命令,总是向交易所节点查询,该字段可以填写任意的T8地址或者Table地址。但是,如果是发送交易的请求,这个字段必须填写成交易的from地址。

3)请求头里面的“sequence_id”字段必须填写,对于交易所来说,可以填写成固定值,比如1。

4)body里面的"account_addr"字段表示账户地址或者Table链地址,比如上述getBlock命令填写为指定Table的table地址。

  • 交易所节点启动信息和监控说明
    • topio node isJoined 返回yes,就表示节点入网成功。然后节点会自动进行数据同步。所以,需要只需要监控“topio node isJoined 返回yes”,然后等待节点数据同步好并开启端口。
    • 一个新的交易所节点启动时,数据同步需要花费一段时间(几个小时),直到找到节点的注册节点数据,才会真正开启交易所节点功能,比如启用TCP 19081端口。
    • 节点启动的提示信息有以下几类:
//数据同步还未同步到节点的注册信息,调用topio node startNode时的启动信息
topio node startNode
The miner has not joined in node-standby-pool, please start node by command 'topio node startnode'.
Start node successfully.

//数据同步还未同步到节点选举信息数据,调用topio node startNode时的启动信息
topio node startNode
Not elected to any node role.
Start node successfully.

//数据同步完成时,调用topio node startNode时的启动信息
topio node startNode
Elected as exchange.
Start node successfully.

# TOP Token 相关

# 1. TOP Token

TOP 里最小单位为:微(uTOP)

  • 1 TOP = 10 ^ 6 微(uTOP)
  • 1 分(cTOP):Cent-top = 1/100 TOP, 1/hundred
  • 1 毫(mTOP):Milli-top = 1/1000 TOP, 1/Thousand
  • 1 微(μTOP):Micro-TOP = 1/10^6 TOP, 1/Million

# 2. TOP Token 总发行量

TOP Token 的总发行量为 200亿。

# 账户、交易相关

# 3. 用户账户(地址)和 Table

  • TOP 采用的是 Account 模型。

  • TOP 账户地址以 T80000 开头,格式为 T80000 + 以太坊格式地址。

  • 任一个账户地址通过计算 Hash 逻辑上映射到 64 个 table 中的固定一个,每个 Table 类似一条独立的子链(也是一个逻辑分片)。账户地址和Table的映射规则为:

    • 第一步:对整个账户地址进行hash64计算:uint32_t account_index = hash64(account_addr);
    • 第二步:对hash64结果进行强制转换成uint16:uint16_t ledger_sub_addr = (uint16_t)(account_index);
    • 第三步:对uint16结果进行64求余:uint16_t tableID = (ledger_sub_addr &63);
  • 每个 table 会对所管辖的账户的交易进行打包并独立出块。

  • 64 个 Table 子链地址对应 Ta0000@0 ~ Ta0000@63。

  • 64 个 Table 子链序号(ID)为 0 ~ 63。

# 4. 账户地址格式

说明:

1)交易所需要使用T8开头的地址,例子:T80000bf28ca5dc9f1f4cddabeb6da4c29e0db47d4b9c4

2)地址对大小写敏感,要求除了首字母T之外,其余全部小写。

package org.topnetwork.account;

import java.io.IOException;

import org.topnetwork.account.property.AccountUtils;

import com.alibaba.fastjson.JSONObject;

/**
* 构建离线 account
*/
public class CreateOfflineAccount {

public static void main(String[] args) throws IOException {
  // 1.随机创建离线地址
  Account account=new Account();
  System.out.println("account > "+ JSONObject.toJSONString(account));
  System.out.println("privateKeyBytes > "+ account.getPrivateKeyBytes());

  // 2.指定 table 创建离线地址,targetTableId[0,63]
  int targetTableId = 0;
  Account account1 = AccountUtils.genAccount(targetTableId);
  System.out.println("account1 > "+ JSONObject.toJSONString(account1));

  // 3.根据私钥构建地址
  String privateKey = "0x638785b5e9bb9271f031f6ef852e3d5f33b9f46bff6d920b8622d44e69d6666f";
  Account account2 = new Account("0x638785b5e9bb9271f031f6ef852e3d5f33b9f46bff6d920b8622d44e69d6666f");
  System.out.println("account2 > "+ JSONObject.toJSONString(account2));

  // 4. 根据 privateKeyBytes 构建帐户
  byte[] privateKeyBytes = account2.getPrivateKeyBytes();
  Account account3 = new Account(account2.getPrivateKeyBytes());
  System.out.println("account3 >" + JSONObject.toJSONString(account3));
}
}

# 5. 如何扫描某个账户下所有交易

您可以先 查询用户账户所在的 Table 子链地址以及最新块高,再 扫描指定 Table 子链下的所有块

# 6. 如何获取用户地址下的余额

RPC接口为getAccount (opens new window) :

  • table_id字段表示账户所在的tableid。
  • burned_token字段表示该账户历史花费的交易费用,交易费用是销毁掉的。

Java范例代码:

package org.topnetwork.account;

import java.io.IOException;

import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.AccountInfoResponse;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

/**
 * 获取指定帐户余额
 */
public class GetAccountBalance {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));
        // 查询指定地址的帐户余额
        String address = "T80000f4d41bf4035e972649a8168ba06a15fa19a15ded";
        Account account=new Account();
        account.setAddress(address);
        topj.passport(account);
        ResponseBase<AccountInfoResponse> accountResult = topj.getAccount(account);
        System.out.println("account balance (utop) > "+accountResult.getData().getBalance());
    }

}

# 7. 如何获取用户账户下的最新 nonce

package org.topnetwork.account;

import java.io.IOException;

import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.AccountInfoResponse;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

/**
 * 获取指定帐户最新 nonce
 */
public class GetAccountNonce {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));

        // 查询指定地址的帐户信息
        String address = "T80000f4d41bf4035e972649a8168ba06a15fa19a15ded";
        Account account=new Account();
        account.setAddress(address);
        topj.passport(account);
        ResponseBase<AccountInfoResponse> accountResult = topj.getAccount(account);
        System.out.println("account latest nonce > "+accountResult.getData().getNonce());
    }
}

# 8. 如何通过交易 Hash 查询交易详情

RPC接口为getTransaction (opens new window)

  • 交易是否成功,只要关注tx_state字段,判断是否为success即可。
  • 实际扣除的交易手续费 = send_block_info.tx_fee + send_block_info.used_deposit + confirm_block_info.used_deposit
  • getTransaction查询到的信息,都是不可回滚的
  • top是一条分片链,交易的两个账户可能分布在不同table(也叫做跨table交易),跨table交易会有3个阶段(即三次共识,分别是send,recv,confirm),所以交易会有三部分信息,分别是send_block_info,recv_block_info,confirm_block_info。 转账交易只有两次共识,confirm和send总是相同。
curl -X 'POST' -d 'target_account_addr=T00000LWY6p1GfHJQ65NyiXGnEtc51i71ujuGDJQ&body={"params":{"account_addr":"T00000LWY6p1GfHJQ65NyiXGnEtc51i71ujuGDJQ","tx_hash":"0x76db850ced18deb00f176a7ecad93d20d6df2b5ad806810a8ed5965450edf012"}}&method=getTransaction&sequence_id=8&version=2.0' http://mainnet.edge.topnetwork.org

{
  "data": {
    "original_tx_info": {
      "amount": 1,
      "authorization": "0x00684998cde3d692ae2cf85b0e6889328bfbe1c9fe185027a5ad8f6da59f5afae34821bed2f7c029ea27b4534f70b9b34543004ea886a7c5930782870f4a54a2c5",
      "edge_nodeid": "",
      "ext": "",
      "last_tx_nonce": 60722,
      "note": "subscription",
      "premium_price": 0,
      "receiver_account": "T00000LMnPbhyrVSjuZPTibWGWK112sCor6aVcvm",
      "receiver_action_name": "",
      "receiver_action_param": {
        "amount": 1,
        "token_name": "TOP"
      },
      "send_timestamp": 1679922302,
      "sender_account": "T00000LWY6p1GfHJQ65NyiXGnEtc51i71ujuGDJQ",
      "sender_action_name": "",
      "sender_action_param": {
        "amount": 1,
        "token_name": "TOP"
      },
      "token_name": "",
      "tx_deposit": 100000,
      "tx_expire_duration": 100,
      "tx_hash": "0x76db850ced18deb00f176a7ecad93d20d6df2b5ad806810a8ed5965450edf012",
      "tx_len": 187,
      "tx_structure_version": 2,
      "tx_type": 4
    },
    "tx_consensus_state": {
      "confirm_block_info": {
        "account": "Ta0000@0",
        "exec_status": "success",
        "height": 1227248,
        "recv_tx_exec_status": "success",
        "used_deposit": 0,
        "used_gas": 0
      },
      "recv_block_info": {
        "account": "Ta0000@56",
        "height": 563013,
        "logs": [],
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "used_gas": 0
      },
      "send_block_info": {
        "account": "Ta0000@0",
        "height": 1227248,
        "tx_fee": 0,
        "used_deposit": 0,
        "used_gas": 561
      }
    },
    "tx_state": "success"
  },
  "errmsg": "OK",
  "errno": 0,
  "sequence_id": "8"
}

# 9. 如何通过交易 Hash 查询对应的 Table 块

Self 交易为发送和接受均为自己的地址,所以 self 交易只有一个块,而非 self 交易每笔交易有 3 个块。

  • send block 对应发送交易的块。
  • recv block 对应接收交易的块。
  • confirm block 对应确认交易的块。

RPC接口为getBlock (opens new window)

  • 查询指定Table链的最高块,设置参数account_addr为Table链名称和参数height为latest,比如"account_addr":"Ta0000@10","height":"latest"
  • 查询指定Table链和指定高度的块,设置参数account_addr为Table链名称和参数height为具体高度,比如"account_addr":"Ta0000@10","height":"10000"
  • getBlock返回的内容,包含每条交易的hash和共识阶段信息(tx_consensus_phase字段)。tx_consensus_phase取值范围:send,recv,confirm,self,其中self表示的是交易的发送地址和接收地址是相同的。对于转账交易来说,只有send和recv两种情况。
curl -X 'POST' -d 'target_account_addr=Ta0000@10&body={"params":{"account_addr":"Ta0000@10","height":"latest"}}&method=getBlock&sequence_id=8&version=2.0' http://mainnet.edge.topnetwork.org

{
	"data": {
		"value": {
			"body": {
				"tableblock": {
					"txs": [{
						"tx_consensus_phase": "send",
						"tx_hash": "0x692ea56daeba98383db35cb8fdbc4a111941999c6b44b66580d6e08ce3aabb61"
					}],
					"units": [{
						"account": "T00000LYpGz3V6QJ52SGumo1yWwX7LyxrQqjDLdq",
						"unit_height": 178899
					}]
				}
			},
			"hash": "01fdabdaee0856b1960c2eae7c2fe53fa0dfabcffd9ad28e6ac80319ce573007",
			"header": {
				"auditor_xip": "1000000000007ae1:f6000000000407f",
				"multisign_auditor": "740000005f0000006cc26cfc00000000210002e0e03814ac48d9e8427d8a97e1dfb4f52f8f061d11e4cebf634c12ad107f44fe20001274c5ad1672c5761021c261f5dd4ce6d35fb2457bd41900f895c0469cd695b84000fffee7ffffbfdfff",
				"multisign_validator": "74000000670000006cc26cfc00000000210002b2b041d64a0ea1eba01c3994673e0c1c8d15db43fcbed304030a9a2a7c81bf38200060cda855245ebb99fad75f283ab7b5d64d5d2e767d329df0c8125c6a58d8a12980003fce7fed2bce85eff5fb9f77fcb0ba33",
				"timerblock_height": 10687171,
				"validator": "T00000LUoiK1jSZMvEzfu7urJ4G9sH1PitAqsuWe",
				"validator_xip": "2000000000007ae1:f60000000005003",
				"version": 327680
			},
			"height": 592406,
			"owner": "Ta0000@10",
			"prev_hash": "24ed46340ef4387b6bcb3b46bde22dc360e0c99682c3a16836af1e72014f7843",
			"table_height": 592406,
			"timestamp": 1680060910
		}
	},
	"errmsg": "OK",
	"errno": 0,
	"sequence_id": "8"
}

Java范例代码为:

package org.topnetwork.block;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.topnetwork.account.Account;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.block.TableBlockResponse;
import org.topnetwork.methods.response.tx.TxConsensusState;
import org.topnetwork.methods.response.tx.XTransactionResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

import java.io.IOException;

/**
 * 如何通过交易 Hash 查询对应的 Table 块
 */
public class GetTableBlockByTxHash {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));

        Account account=new Account();
        topj.passport(account);

        // 正常发送交易每笔交易会有 3 个块
        String txHash = "0x052945e6f8f56bd9215e603aecde92a255ada57bd3d40cf662fc802805be406a";
        ResponseBase<XTransactionResponse> transactionResponseResponseBase = topj.getTransaction(account,txHash);
        System.out.println("transaction >> " + JSON.toJSONString(transactionResponseResponseBase));
        TxConsensusState state = transactionResponseResponseBase.getData().getTxConsensusState();
        // 1.send block
        ResponseBase<TableBlockResponse> sendTableIdBlock = topj.getTableBlockByHeight(account,state.getSendBlockInfo().getAccount(),state.getSendBlockInfo().getHeight().intValue());
        System.out.println("sendTableIdBlock info > "+ JSONObject.toJSONString(sendTableIdBlock));

        // 2.recv block
        ResponseBase<TableBlockResponse> recvTableIdBlock = topj.getTableBlockByHeight(account,state.getSendBlockInfo().getAccount(),state.getRecvBlockInfo().getHeight().intValue());
        System.out.println("recvTableIdBlock info > "+ JSONObject.toJSONString(recvTableIdBlock));

        // 3.confirm block
        ResponseBase<TableBlockResponse> confirmTableIdBlock = topj.getTableBlockByHeight(account,state.getSendBlockInfo().getAccount(),state.getConfirmBlockInfo().getHeight().intValue());
        System.out.println("confirmTableIdBlock info > "+ JSONObject.toJSONString(confirmTableIdBlock));
    }

}

# 10. 如何发送交易,以及如何在交易里携带 Memo 信息

交易里面有一个memo字段,可以自定义设置内容,要求长度不能超过100字节。交易所可以利用memo实现memo模式,把memo字段设置成一个中心化用户id,从而识别出这笔交易的目标用户。当然,交易所也可以采用地址模式,TOP的交易费用非常低。

Java范例代码:

package org.topnetwork.transaction;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Map;

import org.topnetwork.account.Account;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.Model.TransferParams;
import org.topnetwork.methods.request.Transfer;
import org.topnetwork.methods.response.AccountInfoResponse;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.XTransaction;
import org.topnetwork.methods.response.tx.XTransactionResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.utils.ArgsUtils;

/**
 *  连接rpc直接发送交易
 */
public class SendTransaction {
    // 根据节点地址和端口,初始化通信协议对象,支持websocket
    private static HttpService httpService = new HttpService("http://206.189.210.106:19081");
    private static Topj topj = Topj.build(httpService);

    public static void main(String[] args) throws IOException {
        //1. 构建发送帐户
        Account sendAccount=new Account("0x638785b5e9bb9271f031f6ef852e3d5f33b9f46bff6d920b8622d44e69d6666f");
        topj.passport(sendAccount);
        ResponseBase<AccountInfoResponse> accountResult = topj.getAccount(sendAccount);
        System.out.println("当前账户 nonce > "+accountResult.getData().getBalance());
        System.out.println("当前账户 nonce > "+accountResult.getData().getNonce());
        // 2. 接收者地址
        Account toAccount = new Account();

        // 3. 构建发送交易参数,签名信息
        Transfer transfer = new Transfer();
        TransferParams transferParams = new TransferParams(toAccount.getAddress(), BigInteger.valueOf(1000000), "your note");

        // 发送交易方式一:
        Map<String, String> requestTokenArgsMap = transfer.getArgs(sendAccount, Arrays.asList(transferParams));
        System.out.println("交易体 > "+requestTokenArgsMap);
        // 4.发送交易
        ResponseBase<XTransactionResponse> result = httpService.send(requestTokenArgsMap, XTransactionResponse.class);
        XTransaction xTransaction = ArgsUtils.decodeXTransFromArgs(requestTokenArgsMap);
        XTransactionResponse xTransactionResponse = new XTransactionResponse();
        xTransactionResponse.setOriginalTxInfo(xTransaction);
        result.setData(xTransactionResponse);
        System.out.println(result.getData().getOriginalTxInfo().getTxHash());

        // 发送交易方式二:
        ResponseBase<XTransactionResponse> result2 = topj.transfer(sendAccount,transferParams);
        System.out.println(result2.getData().getOriginalTxInfo().getTxHash());
    }

}

# 11. 交易超时机制

交易有两个字段,fire_timestamp字段表示发起交易的时间戳,要求和链上节点时间在5分钟的偏差之内,expire_duration字段表示交易在交易池持续的时间,范围0-65535秒。超时的交易将被丢弃。

有几种情况会导致交易超时:

  • 客户端构建好交易后,过了较长时间才发到链上——如果是这种情况,可以将expire_duration字段设置成更大,默认是100秒。
  • 客户端的时间同步功能有问题,落后于标准的UTC时间。

# 12. 如何计算交易手续费

机制说明:

  • 免费机制。账号余额大于100TOP,预估一天能发10多笔免费的转账交易。
  • 低费用。免费额度用完后,交易需要付费。对于转账交易,费用上限(也称为交易保证金txDeposit)设置为0.1TOP,可以保证100%成功,实际费用不会超过0.1TOP。
  • 每个账户有0.1TOP是不能转出去的,账户至少要有0.1TOP才能发交易。
  • 无论是否为跨片交易,还是片内交易,都是发送账户付费。

公式:

交易手续费 = used_depositsend_block_info) + used_depositconfirm_block_info) + tx_fee。其中:

  • used_deposit 是作为手续费而被扣除的 TOP,当免费的 Tgas 充足时,该值为 0。
  • 对于普通交易,扣除的是 send_block_info 下的 used_deposit ;对于合约交易,扣除的是 confirm_block_info 下的 used_deposit,详见下方代码示例。
  • tx_fee 是一部分系统合约交易的固定手续费(例如在注册节点时会扣除 100 TOP 手续费),对于普通转账交易,该值为 0。
{
    "data": {
        "original_tx_info": {
            "amount": 0,
            "authorization": "0x00da74315ede21da0ebc0ce4f8d1fb8409c978c74a0be4d7660410e3eae7ebcfa36f386bff302c3fd6c7c1be040076fe7f5cda50afc7f842aa71bf05d1a6feea02",
            "edge_nodeid": "",
            "ext": "",
            "last_tx_nonce": 0,
            "note": "",
            "premium_price": 0,
            "receiver_account": "T800002276a7d58218ac4978733e5cca927a7d86cb7c87",
            "receiver_action_name": "",
            "receiver_action_param": "0x2e00000054383030303033376434666263303862663435313361363861323837656432313862306164626434393765663330",
            "send_timestamp": 1631791128,
            "sender_account": "T8000037d4fbc08bf4513a68a287ed218b0adbd497ef30",
            "sender_action_name": "",
            "sender_action_param": "",
            "token_name": "",
            "tx_deposit": 100000,
            "tx_expire_duration": 100,
            "tx_hash": "0xfb33b056757f7d3ba6bccd0c8cd1a923a68dec1fd0c0633f513cee58214b648d",
            "tx_len": 189,
            "tx_structure_version": 2,
            "tx_type": 0
        },
        "tx_consensus_state": {
            "confirm_block_info": {
                "account": "Ta0000@39",
                "exec_status": "success",
                "height": 7,
                "recv_tx_exec_status": "success",
                "used_deposit": 0,
                "used_gas": 0
            },
            "recv_block_info": {
                "account": "Ta0000@55",
                "height": 7,
                "used_gas": 0
            },
            "send_block_info": {
                "account": "Ta0000@39",
                "height": 4,
                "tx_fee": 0,
                "used_deposit": 0,
                "used_gas": 468
            }
        },
        "tx_state" : "success"
    },
    "errmsg": "ok",
    "errno": 0,
    "sequence_id": "17"
}

# 13. (高级用法)如何让创建的所有用户地址,都分布在同一个 Table 子链下

package org.topnetwork.account;

import java.io.IOException;

import org.topnetwork.account.property.AccountUtils;

import com.alibaba.fastjson.JSONObject;

/**
 * 构建离线 account
 */
public class CreateOfflineAccount {

    public static void main(String[] args) throws IOException {
        
        // 指定 table 创建离线地址,targetTableId[0,63]
        int targetTableId = 0;
        Account account1 = AccountUtils.genAccount(targetTableId);
        System.out.println("account1 > "+ JSONObject.toJSONString(account1));
        
    }
}

# Table 链相关

# 14. 查询用户账户所在的 Table 子链地址以及最新块高

package org.topnetwork.account;

import java.io.IOException;

import org.topnetwork.account.property.AccountUtils;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.AccountInfoResponse;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.block.TableBlockResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

import com.alibaba.fastjson.JSONObject;

/**
 * 查询用户账号所在的 Table 子链地址,以及最新块高
 */
public class CheckAccount {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));
     	// 离线获取指定地址对应的 table 子链 id[0,63]
        int tableId = AccountUtils.getAddressTableId(account2.getAddress());
        System.out.println("tableId::"+tableId);

        // 查询帐户最新块高
        ResponseBase<TableBlockResponse> lastTableBlock = topj.getLastTableBlock(account2,account2.getAddress());
        System.out.println("Last Block info > "+ lastTableBlock.getData().getValue().getTableHeight());
    }

}

# 15. 查询指定 Table 子链下指定块高

package org.topnetwork.block;

import java.io.IOException;

import org.topnetwork.account.Account;
import org.topnetwork.account.property.AccountUtils;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.block.TableBlockResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

import com.alibaba.fastjson.JSONObject;

/**
 * 查询指定帐户所属子链的块信息
 */
public class GetTableBlockByHeight {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));

        int height = 67;// 指定块高
        String address = "T80000f4d41bf4035e972649a8168ba06a15fa19a15ded";//指定地址
        Account account=new Account();
        account.setAddress(address);
        topj.passport(account);
        // 指定帐户所在的子链地址
        String targetTableAccount = AccountUtils.getAddressTable(account.getAddress());
        // 查询指定 Table 子链下指定块高的块信息
        ResponseBase<TableBlockResponse> subTableIdBlock = topj.getTableBlockByHeight(account, targetTableAccount,height);
        System.out.println("Block info > "+ JSONObject.toJSONString(subTableIdBlock));
    }

}

# 16. 扫描指定 Table 子链下的最新块

package org.topnetwork.block;

import java.io.IOException;

import org.topnetwork.account.Account;
import org.topnetwork.account.property.AccountUtils;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.block.TableBlockResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

import com.alibaba.fastjson.JSONObject;

/**
 * 查询指定 Table 子链最新的块信息
 */
public class GetTableBlockByTableId {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));

        int height = 67;// 指定块高
        String address = "T80000f4d41bf4035e972649a8168ba06a15fa19a15ded";//指定地址
        Account account=new Account();
        account.setAddress(address);
        topj.passport(account);

        // 指定帐户所在的子链地址
        String targetTableAccount = AccountUtils.getAddressTable(account.getAddress());
        // 查询指定 Table 子链最新的块信息
        ResponseBase<TableBlockResponse> subTableIdBlock = topj.getLastTableBlock(account, targetTableAccount);
        System.out.println("Block info > "+ JSONObject.toJSONString(subTableIdBlock));
    }

}

# 17. 如何扫描所有的 Table 下指定高度的块

遍历 Ta0000@0 ~ Ta0000@63,扫描每一个 Table 子链中高度为 0 的块。

package org.topnetwork.block;

import java.io.IOException;

import org.topnetwork.account.Account;
import org.topnetwork.account.property.AccountUtils;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.methods.response.block.TableBlockResponse;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;
import org.topnetwork.utils.TopjConfig;

import com.alibaba.fastjson.JSONObject;

/**
 * 扫描所有子链的块
 */
public class ScanAllTableBlock {

    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        // 请求失败每 5s 循环调用,最多 3 次 默认 topj.setTransactionReceiptProcessor(new NoOpProcessor());
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));
        Account account=new Account();
        topj.passport(account);
        // 扫描指定子链帐户下的块信息,height 从 0 开始 ,
        // 扫描所有的 tabe 子链 [0-63],可以选择每个子链开启线程 height 从 0 开始扫描,最终到 latest height
        int height = 0;
        for (int i=0;i<64;i++){
            // 子链地址
            String targetTableAddress = TopjConfig.getShardingTableBlockAddr() + "@" + i;
            ResponseBase<TableBlockResponse> subTableIdBlock = topj.getTableBlockByHeight(account, targetTableAddress,height);
            System.out.println("Block info > table" +i +" >"+ JSONObject.toJSONString(subTableIdBlock));
        }
    }

}

# 18. 如何查询所有 Table 子链的最新高度

package org.topnetwork.block;

import java.io.IOException;
import java.math.BigInteger;
import java.util.List;

import org.topnetwork.account.Account;
import org.topnetwork.core.Topj;
import org.topnetwork.methods.response.ResponseBase;
import org.topnetwork.procotol.http.HttpService;
import org.topnetwork.tx.PollingTransactionReceiptProcessor;

import com.alibaba.fastjson.JSON;

/**
 * 如何查询所有 Table 子链的最新高度
 */
public class GetAllLastTableBlockHeight {
    private static Topj topj =null;

    public static void main(String[] args) throws IOException {
        HttpService httpService = new HttpService("http://206.189.210.106:19081");
        topj = Topj.build(httpService);
        topj.setTransactionReceiptProcessor(new PollingTransactionReceiptProcessor(5000, 3));
        // 构建随机帐户
        Account firstAccount = new Account();
        // 设置 identityToken
        topj.passport(firstAccount);
        // 获取 64 个 table 的最新高度
        ResponseBase<List<BigInteger>> latestTables = topj.getLatestTables(firstAccount);
        System.out.println("latestTables info > " + JSON.toJSONString(latestTables.getData()));

    }

}

# 浏览器相关

# 19. 如何从 TOP 浏览器查询一个账户信息

点击此处 (opens new window) 进入 TOPscan 查询账户信息。

# 20. 如何从 TOP 浏览器查询一笔交易

点击此处 (opens new window) 进入 TOPscan 查询交易。