# 在交易所中添加 TOP

本文介绍如何在您的交易所中添加 TOP。

# 前置说明

  • 关于分片

    • TOP 链有 64 个分片,每个分片叫做一个 Table,每个分片有一条的table链。
    • 每个 Table 链有一个链地址,64 条 Table 链地的址范围为:Ta0000@0 - Ta0000@63,其中 0-63 也叫做 TableID。
    • 账户地址和TABLE的映射关系。
    • 账户地址平均映射到 64 个 Table 地址。
  • 如何通过普通账号地址计算 TableID

    1. 对普通账号地址(不带 @ 后缀)进行 hash64 计算

      uint32_t account_index = get_index_from_account(account_addr); //hash whole account address
      
    2. 强制转换为uint16。

      uint16_t ledger_sub_addr = (uint16_t)(account_index); 
      
    3. 取模,强制转换为小于 64 的 TableID。

      uint16_t tableID = (ledger_sub_addr &63);
      

      说明:

      hash64 使用的是 xxhash 开源库,C++ 版本,详情参见 GitHub (opens new window)

  • 关于交易

    • 交易分为 send 和 recv 两个阶段。
    • send 阶段在交易的发送地址所在的 Table 分片上进行共识打包出块。
    • recv 阶段在交易的接收地址所在的 Table 分片上进行共识打包出块。

# 搭建节点

建议您租用云服务器搭建节点,并在您的计算机上通过编译机(如 Xshell)远程访问该云服务器。

搭建节点主要分为以下几步:

  1. 配置服务器
  2. 下载并编译代码
  3. 安装 TOPIO
  4. 注册节点与节点入网

# 1. 配置服务器

要搭建节点,首先需要创建一台用于运行 TOPIO 的服务器。

操作系统要求

操作系统 版本
Linux CentOS 7,64 位,内核 3.10 及以上版本(含 CentOS 8)
Ubuntu 16.04,64 位,内核 4.4 及以上版本

服务器最低配置

对于交易所节点,最低配置如下:

节点类型 最低硬件配置
交易所节点 2CPU/4GB mem
1TB SSD
200Mb/s

# 2. 下载并编译代码

本节中的操作需在 Linux 的或其编译机(如 Xshell)中运行。

  1. 在编译机中执行以下命令将代码库克隆至本地。

    git clone git@github.com:telosprotocol/TOP-chain.git
    

    当前最新版本为 1.8.0,您也可以前往 TOP Chain GitHub 库 (opens new window),在页面右侧查看当前最新版本。

    version

  2. 进入 TOP-chain/ 目录,运行以下命令获取指定分支的 base。

    git submodule update --init --recursive
    
  3. 安装rust

    curl https://sh.rustup.rs -sSf | sh
    
  4. 回到 TOP-chain 目录,运行以下命令进行编译。

    其中 noratelimit 用于关闭 RPC 流量控制,由于交易所节点查询较频繁,若存在流量控制会影响交易所节点的查询功能。

    sh build.sh release noratelimit
    

    若执行成功,会生成如下文件:

    cbuild_release/bin/Linux/topio
    
  5. 随后运行以下命令进行打包。

    sh pack.sh release
    

    若执行成功,会在 TOP-chain 目录下生成 TOPIO 安装包 topio-1.8.0-release.tar.gz

# 3. 安装 TOPIO

说明:对于非 root 用户,需要先 添加管理员权限(root 用户无需添加)。

# 3.1 安装

在编译代码并生成 TOPIO 安装包后,运行以下命令安装 TOPIO。

tar zxvf topio-1.8.0-release.tar.gz && cd topio-1.8.0-release && sudo bash install.sh && source /etc/profile && bash set_topio.sh && source ~/.bashrc && ulimit -n 65535

# 3.2 验证是否安装成功

安装完成后,安装页面会打印以下信息:

install topio done, good luck
now run command to check md5: topio -v
now run command for help info: topio -h

此时执行 topio -v 来查看 TOPIO 版本信息,以验证 TOPIO 是否安装成功。

安装成功

如果输出 TOPIO 版本信息,证明安装成功。

topio version: 1.8.0
git commit info: 88a1a2a7
git submodule: xbase:0a7b3b1|xdepends/boringssl:10fef972e|xdepends/googletest:ec44c6c1|xdepends/libevent:5df3037d|xdepends/xquic:0805699|
build date: Nov 29 2022 06:49:04
build options: release
MD5:78f49d77d6e5e81badf9e54b04e6d667

注意

如 MD5 码与官网展示的对应安装包 MD5 码不一致,您虽然安装成功,但是安装文件存在被篡改的可能性!

安装失败

如果输出以下信息,证明安装失败。

-bash: topio: 未找到命令

以上示例基于支持中文的 Linux 操作系统。

如安装失败,请重新执行安装命令。

# 3.3 配置环境变量(可选)

TOPIO 安装完成后,您可以执行以下命令配置环境变量,将以下命令中的 /opt/topnetwork 设置成您的权限目录。

echo "export TOPIO_HOME=/opt/topnetwork" >> ~/.bashrc && source ~/.bashrc

如不配置环境变量,您将使用系统默认目录作为 TOPIO 的工作目录:

  • root 用户,默认目录为 /root/topnetwork
  • 非 root 用户,默认目录为 /home/user/topnetwork,其中 user 是您登录服务器的用户名。

# 4. 注册节点与节点入网

# 4.1 创建账户

安装完 TOPIO 后,即可创建 TOP 账户。

在 TOPIO 中输入以下命令创建 TOP 账户(需要设置密码):

topio wallet createAccount

创建成功后,系统会返回您的账户地址以及公钥;公钥信息页可以从Keystore中找到。

Account Address: T800003126428b8a65aaad2e08ecbe4bd4a345645c8676
Owner public-Key: BBMZFRt4fcuJwuDwAmy8apKa9ru6n1ov8gCRghDwe3+23E+/oGGh1CliyiSAJOnUy0SBV6m5Klw4AWR53B4ap/U=

重要:

  • 新创建的账户均为未激活状态,需要至少 0.1 TOP 方可激活,请联系工作人员为您转入 TOP。

  • 账户余额大于 100 TOP 时可享受每日免费 gas,此时的链上操作均不收取 gas 费;余额少于 100 TOP 时,链上操作会消耗您的 TOP 余额。

# 4.2 注册节点

创建账户后,还需要设置默认账号,才能注册节点。在设置默认账号的过程中,需要交互式输入密码。

topio wallet setDefaultAccount Your_Account_Address

请记得为账号获取适量 TOP 后,才可注册节点。

在 TOPIO中输入命令注册节点,命令示例如下:

topio mining registerMiner 0 exchange miner_name --miner_key Your_Miner_Public_Key

其中:

  • 0 为所需保证金(TOP)数量,交易所节点所需的 TOP 为 0。
  • exchange 为交易所节点。
  • miner_name 为节点名,可自定义,4-16字符,支持字母、数字或下划线。
  • Your_Miner_Public_Key为Miner key公钥

如返回以下信息,证明节点注册成功。

Successfully registering to the mining pool.

# 4.3 节点入网

节点注册成功后,还需要设置Miner key(需要交互式需求密码)。

topio mining setMinerKey Your_Miner_Public_Key

完成设置后需要启动节点进程,让节点加入TOP Network网络。

执行以下命令启动节点进程:

topio node startNode

若返回以下信息,则成功启动节点进程。

Start node successfully.

查询是否入网

您可执行以下命令查询节点是否成功加入网络。

topio node isJoined
  • 若返回 topio not ready,说明 TOPIO 暂未准备好,请等待一分钟后再次运行命令重试。
  • 若返回 YES,则节点成功入网,进入节点候选池,等待选举。
  • 返回 No,则节点入网失败,可能因为网络故障,网络抖动。

# 升级节点

若 TOPIO 发布新版本,您需要升级至最新版 TOPIO。

  1. 运行以下命令结束旧版 TOPIO 进程。

    ps -ef |grep topio|grep -v 'grep'|awk '{print $2}'|xargs kill -9
    
  2. 重新执行 搭建节点 中的的下载、编译代码、安装 TOPIO 的步骤。

  3. 安装成功后,执行 topio node startNode 重新启动节点即可。

    升完级后,CPU 占用率会达到 100% 并持续一段时间,导致在 TOPIO 中输入命令时发生卡顿,此时可稍等片刻再进行尝试。

# RPC API

您可 点击此处 查看 TOP Network 所有 RPC API 信息。

交易所节点主要通过以下 RPC API 与链进行互动:

# 查询交易信息

可根据指定的交易 hash,查询该交易的情信息。

请求方法

getTransaction

详情参见 查询账户交易信息

# 查询区块信息

可根据账户地址及区块高度查询区块信息。

请求方法

getBlock

详情参见 查询区块信息

# 查询最新区块高度

可查询 256 个 shard table 的高度。

请求方法

getLatestTables

详情参见 查询块高度

# 发送交易

支持发送交易,包括转账、节点staking、节点注册、节点投票、节点领取奖励、调用合约等。

请求方式

sendTransaction

详情参见 发送交易

# SDK

您可 点击此处 查看 TOP Network 所有 Java SDK。

常用 SDK 代码示例如下:

# 获取用户地址下的余额

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());
    }

}

# 获取用户账户下的最新 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());
    }
}

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

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

  • send block 对应发送交易的块。

  • recv block 对应接收交易的块。

  • confirm block 对应确认交易的块。

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));
    }

}

# 如何计算交易手续费

交易手续费 = 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"
}

# 查询用户账户所在的 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());
    }

}

# 查询指定 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));
    }

}

# 扫描指定 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));
    }

}

# 扫描所有的 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));
        }
    }

}

# 查询所有 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()));

    }

}

# FAQ

更多接入相关问题或代码示例,可查看 交易所 FAQ