波场的产块节点称为 Supper Node,官方文档称之为SR。

波场使用DPOS共识算法,这个算法的特点是并不基于算力,而是基于股权证明来实现共识和产块。
DPOS共识算法后面会专门讲到一期。

Supper Node(SR)

以下称Supper Node 为SR,官方SR节点为产块节点,共有27个SR节点。
这27个SR节点每隔3秒轮流负责产块,注意是轮流产块,即:节点A产块-->节点B产块...依次类推。
这种产块方式相较于POW的共识,更加的节能省电,但是缺点也很明显,就是节点理论是更容易被控制作恶,只要半数节点被控制或者半数节点掌握在某个团队中,对于社区来说,并不为是一件好事,缺乏透明度。

成为 SR 的好处是,SR负责产块,产块后会获得产块奖励,SR可以产块后,再将奖励分发给投票者。

产块

超级代表

产块者由所有用户投票,得票最多的128个超级代表成员中选出27个进行产块,实际官方的27个产块SR是在配置文件中写死的。
这27个SR节点即为产块者,如果其中一个节点挂掉后,会从超级代表中的成员节点顶上,继续进行产块。

如何产块

27个SR节点,分别进行产块。
27个节点分属于不同的机器上,如何确定严格的产块顺序。
这个是DPOS的灵魂,如果节点无法按照严格的顺序进行产块,那整个DPOS将无法成立。

通过确认创世块的时间点,并在启动时,通过Hash算法,算出自己的Slot来排序。

维护期

出块轮:波场设定每6个小时作为一个出块轮,称为一个Epoch。每个出块轮最后的2个出块时间是一个维护期。每个出块轮的维护期决定下一个出块轮的出块顺序。

维护期:波场设定是2个区块时间,即6秒钟。这段时间用于统计候选人得票数。因为24个小时有4个出块轮,自然就有4个维护期,维护期中不进行区块生产,主要用来确定下个出块轮的出块顺序。

查看超级代表

搭建SR

搭建私有网络,可以验证SR节点的产块相关的原理和流程,甚至可以通过源码自己塔建一条自己的链。

前提

  1. 具备至少两个钱包账户的私钥与地址;
  2. 至少部署一个SuperNode用于出块;
  3. 至少部署一个FullNode节点用于同步区块、广播交易;
  4. SuperNode与FullNode组成了私有网络,可以进行网络发现、区块同步、广播交易;

波场的 SupperNode 是不对外暴露,最少需要一个FullNode成为一个对外的入口,将交易和区块转发到SupperNode节点当中。

生成钱包账户

钱包账户通过TRON提供的钱包进行生成,有移动端和浏览器插件。
为了截图文件,直接通过浏览器插件进行生成账户和私钥。

导出私钥

准备文件和程序

以下程序和配置文件放在同一个目录下,推荐直接下载官方源码进行编译,这样可以直接从项目中获得所有需要的文件、配置文件等,主要需要包含:

  1. 配置文件 private_net_config.conf
  2. 主程序 FullNode.jar
  3. 启动脚本 start.sh(可选)

配置文件

获取官方提供的配置文件,可以单击这条链接查看配置文件

1
wget https://raw.githubusercontent.com/tronprotocol/tron-deployment/master/private_net_config.conf

private_net_config.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
net {
type = mainnet
# type = testnet
}

storage {
# Directory for storing persistent data

db.version = 2,
db.engine = "LEVELDB",
db.directory = "database",
index.directory = "index",

# You can custom these 14 databases' configs:

# account, account-index, asset-issue, block, block-index,
# block_KDB, peers, properties, recent-block, trans,
# utxo, votes, witness, witness_schedule.

# Otherwise, db configs will remain defualt and data will be stored in
# the path of "output-directory" or which is set by "-d" ("--output-directory").

# Attention: name is a required field that must be set !!!
properties = [
// {
// name = "account",
// path = "storage_directory_test",
// createIfMissing = true,
// paranoidChecks = true,
// verifyChecksums = true,
// compressionType = 1, // compressed with snappy
// blockSize = 4096, // 4 KB = 4 * 1024 B
// writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
// cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
// maxOpenFiles = 100
// },
// {
// name = "account-index",
// path = "storage_directory_test",
// createIfMissing = true,
// paranoidChecks = true,
// verifyChecksums = true,
// compressionType = 1, // compressed with snappy
// blockSize = 4096, // 4 KB = 4 * 1024 B
// writeBufferSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
// cacheSize = 10485760, // 10 MB = 10 * 1024 * 1024 B
// maxOpenFiles = 100
// },
]

}

# this part of config is used to node discovery.
node.discovery = {
enable = false # you should set this entry value with ture if you want your node can be discovered by other node.
persist = true # this entry is used to determined to whether storing the peers in the database or not.
bind.ip = ""
external.ip = 127.0.0.1
}

# this part of config is used to set backup node for witness service.
node.backup {
port = 10001
priority = 8
members = [
]
}

node {
# trust node for solidity node
# trustNode = "ip:port"
trustNode = "127.0.0.1:50051"

# expose extension api to public or not
walletExtensionApi = true

listen.port = 16666

connection.timeout = 2

tcpNettyWorkThreadNum = 0

udpNettyWorkThreadNum = 1

# Number of validate sign thread, default availableProcessors / 2
# validateSignThreadNum = 16

maxActiveNodes = 30

maxActiveNodesWithSameIp = 2

minParticipationRate = 0

# check the peer data transfer ,disconnect factor
disconnectNumberFactor = 0.4
maxConnectNumberFactor = 0.8
receiveTcpMinDataLength = 2048
isOpenFullTcpDisconnect = true

p2p {
version = 1 # 11111: mainnet; 20180622: testnet; you can set other number when you deploy one private net, but the node must have the same number in some private net.
}

active = [
# Active establish connection in any case
# Sample entries:
# "ip:port",
# "ip:port"
]

passive = [
# Passive accept connection in any case
# Sample entries:
# "ip:port",
# "ip:port"
]

http {
fullNodePort = 16667
solidityPort = 16668
}

rpc {
port = 16669

# Number of gRPC thread, default availableProcessors / 2
# thread = 16

# The maximum number of concurrent calls permitted for each incoming connection
# maxConcurrentCallsPerConnection =

# The HTTP/2 flow control window, default 1MB
# flowControlWindow =

# Connection being idle for longer than which will be gracefully terminated
maxConnectionIdleInMillis = 60000

# Connection lasting longer than which will be gracefully terminated
# maxConnectionAgeInMillis =

# The maximum message size allowed to be received on the server, default 4MB
# maxMessageSize =

# The maximum size of header list allowed to be received, default 8192
# maxHeaderListSize =

# Transactions can only be broadcast if the number of effective connections is reached.
minEffectiveConnection = 0
}

}



seed.node = {
# List of the seed nodes. This is used to enable the node can connect when join one net at first.
# If you deploy one private net, you must add some "ip:port" here for other node connecting.
# Seed nodes are stable full nodes, and the first SuperNode must be inclued in.
# example:
# ip.list = [
# "ip:port",
# "ip:port"
# ]
ip.list = [
]
}

genesis.block = {
# Reserve balance
assets = [
{
accountName = "Zion"
accountType = "AssetIssue"
address = "TPL66VK2gCXNCD7EJg9pgJRfqcRazjhUZY"
balance = "95000000000000000"
},
{
accountName = "Sun"
accountType = "AssetIssue"
address = "TWsm8HtU2A5eEzoT8ev8yaoFjHsXLLrckb"
balance = "5000000000000000"
},
{
accountName = "Blackhole"
accountType = "AssetIssue"
address = "TSJD5rdu6wZXP7F2m3a3tn8Co3JcMjtBip"
balance = "-9223372036854775808"
},
{
accountName = "TestA"
accountType = "AssetIssue"
address = "TVdyt1s88BdiCjKt6K2YuoSmpWScZYK1QF"
balance = "1000000000000000"
},
{
accountName = "TestB"
accountType = "AssetIssue"
address = "TCNVmGtkfknHpKSZXepZDXRowHF7kosxcv"
balance = "1000000000000000"
},
{
accountName = "TestC"
accountType = "AssetIssue"
address = "TAbzgkG8p3yF5aywKVgq9AaAu6hvF2JrVC"
balance = "1000000000000000"
},
{
accountName = "TestD"
accountType = "AssetIssue"
address = "TMmmvwvkBPBv3Gkw9cGKbZ8PLznYkTu3ep"
balance = "1000000000000000"
},
{
accountName = "TestE"
accountType = "AssetIssue"
address = "TBJHZu4Sm86aWHtt6VF6KQSzot8vKTuTKx"
balance = "1000000000000000"
}
]

witnesses = [
{
address: TPL66VK2gCXNCD7EJg9pgJRfqcRazjhUZY,
url = "http://tronstudio.com",
voteCount = 10000
}
]

timestamp = "0" #2017-8-26 12:00:00

parentHash = "957dc2d350daecc7bb6a38f3938ebde0a0c1cedafe15f0edae4256a2907449f6"
}

localwitness = [
da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0 # you must enable this value and the witness address are match.
]

#localwitnesskeystore = [
# "src/main/resources/localwitnesskeystore.json" # if you do not set the localwitness above, you must set this value.Otherwise,your SuperNode can not produce the block.
#]

block = {
needSyncCheck = false # first node : false, other : true
maintenanceTimeInterval = 21600000 // 1 day: 86400000(ms), 6 hours: 21600000(ms)
}


vm = {
supportConstant = true
minTimeRatio = 0.0
maxTimeRatio = 5.0
}

committee = {
allowCreationOfContracts = 1 //mainnet:0 (reset by committee),test:1
}

event.subscribe = {
native = {
useNativeQueue = true // if true, use native message queue, else use event plugin.
bindport = 5555 // bind port
sendqueuelength = 1000 //max length of send queue
}

path = "" // absolute path of plugin
server = "" // target server address to receive event triggers
dbconfig = "" // dbname|username|password
contractParse = true,
topics = [
{
triggerName = "block" // block trigger, the value can't be modified
enable = false
topic = "block" // plugin topic, the value could be modified
},
{
triggerName = "transaction"
enable = false
topic = "transaction"
},
{
triggerName = "contractevent"
enable = false
topic = "contractevent"
},
{
triggerName = "contractlog"
enable = false
topic = "contractlog"
},
{
triggerName = "solidity" // solidity block event trigger, the value can't be modified
enable = true // the default value is true
topic = "solidity"
}
]

filter = {
fromblock = "" // the value could be "", "earliest" or a specified block number as the beginning of the queried range
toblock = "" // the value could be "", "latest" or a specified block number as end of the queried range
contractAddress = [
"" // contract address you want to subscribe, if it's set to "", you will receive contract logs/events with any contract address.
]

contractTopic = [
"" // contract topic you want to subscribe, if it's set to "", you will receive contract logs/events with any contract topic.
]
}
}

需要改几处配置

  1. 在localwitness中添加自己的私钥
  2. 设置genesis.block.witnesses为私钥对应的地址
  3. 设置p2p.version为除了11111之外的任意正整数,如33333
  4. 第1个SR设置needSyncCheck为false,其他可以设置为true
  5. 设置node.discovery.enable为true

解释一下上面的配置,官方没有说明,从源码中去分析出来的:

  1. 如果自己是SR时,需要配置localwitness,开启本地模式,将自己初始化为witness
  1. 配置自己成为默认的 witnesses,源码中分析,当轮到节点产块时,程序会从SR列表中获得默认SR,所以需要先进行配置
  2. p2p.version,TRON官方主链中使用的是11111,为了不跟主链冲突
  3. needSyncCheck 用来控制是否需要同步区块,当前只有一个节点,不会需要同步区块的
  1. node.discovery.enable 这项是开启节点发现:

对源码有兴趣的朋友自行查看官方源码。

获取最新的程序

从官方github上选择最新的release版本,选择FullNode.jar

官方github: https://github.com/tronprotocol/java-tron

release: https://github.com/tronprotocol/java-tron/releases

启动脚本

获取官方启动脚本,这个脚本里对内存做了限制,最少不能低于8G内存,否则脚本限制不让启动。
如果内存低于这个配置的话,建议直接使用命令启动。

1
wget https://raw.githubusercontent.com/tronprotocol/java-tron/develop/start.sh

准备工作就完成了,然后就可以启动程序了。

启动私链

配置好之后,就可以进行本地私链部署并生产区块了。

1
sh start.sh -w

如果不想使用脚本,直接手动启动,直接使用命令

1
nohup java -Xmx6g -jar FullNode.jar  --witness  -c private_net_config.conf &>/dev/null &

启动后会生成 logs/tron.log,查看日志,关键日志: generated by myself说明这块是自己产的块。

1
2
3
4
5
6
7
8
9
[ hash=0000000000000055cf5362a54801104d9d261b652e9db7b099c99247527b833d
number=85
parentId=000000000000005413ae634329790c4b944b18591a1a1b1705d65a3b6819f2a7
witness address=41763087eab1c1387954698d451ff9c6215b189430
generated by myself=true
generate time=2022-02-04 01:19:57.0
account root=0000000000000000000000000000000000000000000000000000000000000000
txs are empty
]

查看SR完整产块日志

SR产块日志

如果看到 generate time 这个日志,说明已经成功产块了,但是FullNode还没有部署,怎么就产块了,没有问题吗。
FullNode 的主要做用是转发,如果没有FullNode,SR接收不到交易和区块,产的块都是空块,在实际使用中需要测试SR的产块相关的功能,最好是部署上FullNode,搭建成一个完整的链。
节点个数的话,FullNode不限个数,SR最多27个,当然可以改源码调整个数,只要保证最终有2/3个节点来保证能完成共识就OK。有兴趣的话,可以一直探讨这个问题。

最大witness个数,即SR节点个数

1
public static final int MAX_ACTIVE_WITNESS_NUM = 27

参考文档:

https://tronprotocol.github.io/documentation-zh/architecture/network/
官方文档中的 private_net_config.conf 地址有误,可以参考我文章中的配置。