TRON 私链

搭建独立的 TRON 私有测试网络,包括至少一个超级代表(SR)出块节点与一个普通全节点,并通过治理提议校准测试所需的链参数。

📘

前置阅读

TRON 私链是 TRON 协议的完全隔离运行实例——拥有独立的创世区块、独立的超级代表(SR)集合以及专属的账本记录。私链的典型用途包括:端到端集成测试、智能合约上线前的预发布演练,以及需要在链上频繁执行操作但不想消耗主网真实 TRX 的开发场景。

最小规模的私链通常由一个出块 SR 全节点至少一个普通同步全节点(负责同步区块、广播交易)组成。本文以双节点最小配置为例展开说明,若要扩展到包含多个超级代表的集群网络,只需沿用相同的配置模式即可。

准备工作

在开始部署前,请确保具备以下条件:

  • 根据部署平台,安装 Oracle JDK 8(x86_64)或 17(ARM64)环境。
  • 安装 Node.js 18 或更高版本与 npm,用于运行后文的 TronWeb 治理提议脚本。
  • 准备至少两个 TRON 账户地址及其对应的私钥:一个用作超级代表账户,另一个或多个作为预充值的测试账户。可使用 TronWeb、命令行工具 wallet-cliTronLink 钱包生成这些地址。

由于该私链完全隔离,创世区块中配置的超级代表地址将作为网络唯一的区块生产者,直到通过标准的超级代表选举流程选出更多 SR。

部署步骤

部署私链节点的基本流程与主网全节点一致,唯一的区别在于配置文件。两个节点运行相同的 FullNode.jar,仅在创世块定义、节点发现种子以及链上参数覆盖上有差异。

1. 创建部署目录

为超级代表(SR)和普通全节点分别创建独立的存放目录,以防数据库和配置文件冲突:

mkdir SR FullNode

2. 将 FullNode.jar 放入对应目录

两个节点使用完全相同的 JAR 包。可根据部署节点中的指导自行编译或下载发布版:

cp FullNode.jar SR/
cp FullNode.jar FullNode/

3. 将配置文件放入对应目录

从构建 FullNode.jar 所用的 java-tron release 标签或源码 commit 中取得 framework/src/main/resources/config.conf,复制到两个目录中并重命名。配置文件必须与 JAR 的源码版本一致;master 分支中的配置文件仅用于查看当前字段,不应直接用于其他版本的 JAR。

cp config.conf SR/supernode.conf
cp config.conf FullNode/fullnode.conf

4. 编辑各配置文件

请分别修改两份配置文件中的以下关键参数项:

配置文件键名SR 节点(supernode.conf普通全节点(fullnode.conf修改原因
localwitnessSR 地址的私钥(保留为空列表)SR 节点必须加载此私钥以对区块进行共识签名
genesis.block.witnessesSR 地址与 SR 相同创世块的初始出块代表,两端配置必须一致
genesis.block.assets预充值账户及其初始 TRX 余额与 SR 相同创世块的初始资金分配,两端配置必须一致
node.p2p.version不与公共网络重复的自定义正整数与 SR 相同避免使用主网的 11111、Shasta 的 1 和 Nile 的 201910292;两端必须一致
seed.node.ip.list清空模板中的公共种子节点仅保留 SR 节点的 IP:listen.port引导普通全节点连接并发现 SR 节点,避免私链节点尝试连接公共网络种子节点
block.needSyncCheckfalsetrue首个启动的出块节点需跳过同步检查,普通节点则需进行同步
node.discovery.enabletruetrue启用节点发现,以便节点间能正常建立 P2P 握手连接
block.proposalExpireTime600000(10 分钟)与 SR 相同缩短治理提议的过期时间以加快测试进度(主网默认值为 3 天)
block.maintenanceTimeInterval300000(5 分钟)与 SR 相同缩短维护周期以加快投票生效(主网默认值为 6 小时)
committee.allowCreationOfContracts11在私链启动时启用智能合约创建,并满足后续 TVM 功能参数的前置条件
committee.allowSameTokenName11允许存在同名的 TRC-10 Token
committee.allowTvmTransferTrc1011允许 TVM 转移 TRC-10 Token

5. 单机部署时的端口调整

如果超级代表节点和普通全节点运行在同一台服务器上,为避免端口冲突,请为各自配置独立的端口:

  • listen.port:P2P 网络监听端口
  • http 端口组:HTTP RPC 端口
  • rpc 端口组:gRPC 端口

如果两个节点部署在不同的服务器上,可以直接使用配置文件中的默认端口。

6. 启动节点

打开两个终端,并在每个终端中进入包含 SRFullNode 目录的部署根目录。

在第一个终端中启动 SR 节点:

cd SR
java -Xmx6g -XX:+HeapDumpOnOutOfMemoryError -jar FullNode.jar --witness -c supernode.conf

在第二个终端中启动普通全节点:

cd FullNode
java -Xmx6g -XX:+HeapDumpOnOutOfMemoryError -jar FullNode.jar -c fullnode.conf

启动几秒钟后,全节点日志中应能看到节点成功连接至 SR 节点并开始同步新产生的区块。

校准私链的关键链参数

全新启动的私链会采用初始默认参数,这与目标网络的实际运行环境(如 Energy 单价、出块奖励、智能合约执行上限、动态能量模型等)可能不同。为了让测试结果具有可比性,应以超级代表(SR)身份通过治理提议校准业务依赖的参数。

链参数会随治理和 java-tron 版本变化。提交前先通过目标网络的 wallet/getchainparameters 查询当前值,并在所用 java-tron 源码的 ProposalUtil.ProposalType 中确认参数编号和依赖关系。不要把本文示例当作永久有效的主网参数全集。

下面的两组参数是 2026-08-04 主网快照,适用于支持参数 #94 的 java-tron 4.8.1 及以上版本。修改分为两笔提议:第一笔先激活基础功能,待其到期并生效后,第二笔再设置依赖这些功能的参数。

先安装脚本所需的 TronWeb 依赖:

npm install tronweb@6

脚本默认连接保留 HTTP 默认端口 8090 的 SR 节点。如果修改了 SR 节点的 HTTP 端口,请通过 PRIVATE_FULL_HOST 指定实际地址。

const { TronWeb } = require('tronweb');

const privateKey = process.env.TRON_PRIVATE_KEY;
if (!/^[0-9a-fA-F]{64}$/.test(privateKey || '')) {
    throw new Error('Set TRON_PRIVATE_KEY to the 64-character hex private key of the SR');
}

const tronWeb = new TronWeb({
    fullHost: process.env.PRIVATE_FULL_HOST || 'http://localhost:8090',
    privateKey
});

// 治理提议 1 — 必须首先执行,以激活基础链参数和功能开关
const parametersForProposal1 = [
    { key: 11, value: 100 },
    { key: 19, value: 180000000000 }, { key: 16, value: 1 },
    { key: 20, value: 1 }, { key: 26, value: 1 },
    { key: 30, value: 1 }, { key: 5, value: 8000000 }, { key: 31, value: 128000000 },
    { key: 32, value: 1 }, { key: 39, value: 1 }, { key: 41, value: 1 },
    { key: 3, value: 1000 }, { key: 47, value: 10000000000 }, { key: 49, value: 1 },
    { key: 13, value: 80 }, { key: 7, value: 1000000 }, { key: 61, value: 600 },
    { key: 63, value: 1 }, { key: 65, value: 1 }, { key: 66, value: 1 },
    { key: 67, value: 1 }, { key: 68, value: 1000000 }, { key: 69, value: 1 },
    { key: 70, value: 14 }, { key: 71, value: 1 }, { key: 76, value: 1 }
];

// 治理提议 2 — 必须在治理提议 1 获批并到期生效后才能执行
const parametersForProposal2 = [
    { key: 47, value: 15000000000 }, { key: 59, value: 1 }, { key: 72, value: 1 },
    { key: 73, value: 5000000000 }, { key: 74, value: 2000 }, { key: 75, value: 34000 },
    { key: 77, value: 1 }, { key: 78, value: 864000 }, { key: 79, value: 1 },
    { key: 81, value: 1 }, { key: 82, value: 1000 }, { key: 83, value: 1 },
    { key: 87, value: 1 }, { key: 88, value: 1 }, { key: 89, value: 1 },
    { key: 94, value: 1 }
];

async function broadcast(unsignedTxn) {
    const signedTxn = await tronWeb.trx.sign(unsignedTxn);
    const result = await tronWeb.trx.sendRawTransaction(signedTxn);
    if (!result.result || !result.txid) {
        throw new Error(`Broadcast failed: ${JSON.stringify(result)}`);
    }
    return result.txid;
}

async function createProposal(parameters) {
    const sorted = [...parameters].sort((a, b) => a.key - b.key);
    const unsignedTxn = await tronWeb.transactionBuilder.createProposal(
        sorted,
        tronWeb.defaultAddress.base58
    );
    console.log('创建提议交易 txID:', await broadcast(unsignedTxn));
}

async function approveProposal(proposalID) {
    const unsignedTxn = await tronWeb.transactionBuilder.voteProposal(
        proposalID,
        true,
        tronWeb.defaultAddress.base58
    );
    console.log('批准提议交易 txID:', await broadcast(unsignedTxn));
}

async function main() {
    const [action, argument] = process.argv.slice(2);
    if (action === 'create' && (argument === '1' || argument === '2')) {
        const parameters = argument === '1' ? parametersForProposal1 : parametersForProposal2;
        await createProposal(parameters);
    } else if (action === 'approve' && /^\d+$/.test(argument || '')) {
        await approveProposal(Number(argument));
    } else {
        throw new Error('Usage: node proposal.js create <1|2> | approve <proposal_id>');
    }
}

main().catch(error => {
    console.error(error);
    process.exitCode = 1;
});

将脚本保存为 proposal.js。运行前,将 YOUR_SR_PRIVATE_KEY 替换为 SR 账户的 64 位十六进制私钥:

export TRON_PRIVATE_KEY="YOUR_SR_PRIVATE_KEY"
# 仅当 SR 节点未使用默认 HTTP 地址时设置:
# export PRIVATE_FULL_HOST="http://localhost:YOUR_SR_HTTP_PORT"

然后按以下顺序处理每笔提议:

  1. 运行 node proposal.js create 1,记录输出的交易 txID
  2. 确认创建交易已经上链,再调用 wallet/listproposals 查找该交易创建的实际 proposal_id。不要假定它一定是 1
  3. 运行 node proposal.js approve <proposal_id>。多 SR 私链需要由足够多的当前活跃 SR 分别批准。
  4. 等待 expiration_time 到期并确认状态为 APPROVED,然后对第二组参数重复以上步骤,将创建命令改为 node proposal.js create 2

最后调用 wallet/getchainparameters 验证实际生效值。若目标不是主网,或使用的 java-tron 版本不同,应根据目标网络和对应版本源码调整参数集合。

完整的链上参数定义及各自的作用说明,请参阅网络参数


相关资源