波场的产块节点称为 Supper Node,官方文档称之为SR。
波场使用DPOS共识算法,这个算法的特点是并不基于算力,而是基于股权证明来实现共识和产块。
DPOS共识算法后面会专门讲到一期。
【需要搭建tron FULLNode 全节点,或者需要服务器,可以右上角联系群主。】
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节点的产块相关的原理和流程,甚至可以通过源码自己塔建一条自己的链。
前提
- 具备至少两个钱包账户的私钥与地址;
- 至少部署一个SuperNode用于出块;
- 至少部署一个FullNode节点用于同步区块、广播交易;
- SuperNode与FullNode组成了私有网络,可以进行网络发现、区块同步、广播交易;
波场的 SupperNode 是不对外暴露,最少需要一个FullNode成为一个对外的入口,将交易和区块转发到SupperNode节点当中。
生成钱包账户
钱包账户通过TRON提供的钱包进行生成,有移动端和浏览器插件。
为了截图文件,直接通过浏览器插件进行生成账户和私钥。
导出私钥
准备文件和程序
以下程序和配置文件放在同一个目录下,推荐直接下载官方源码进行编译,这样可以直接从项目中获得所有需要的文件、配置文件等,主要需要包含:
- 配置文件 private_net_config.conf
- 主程序 FullNode.jar
- 启动脚本 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. ] } } |
需要改几处配置
- 在localwitness中添加自己的私钥
- 设置genesis.block.witnesses为私钥对应的地址
- 设置p2p.version为除了11111之外的任意正整数,如33333
- 第1个SR设置needSyncCheck为false,其他可以设置为true
- 设置node.discovery.enable为true
解释一下上面的配置,官方没有说明,从源码中去分析出来的:
- 如果自己是SR时,需要配置localwitness,开启本地模式,将自己初始化为witness
- 配置自己成为默认的 witnesses,源码中分析,当轮到节点产块时,程序会从SR列表中获得默认SR,所以需要先进行配置
- p2p.version,TRON官方主链中使用的是11111,为了不跟主链冲突
- needSyncCheck 用来控制是否需要同步区块,当前只有一个节点,不会需要同步区块的
- 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 |
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完整产块日志
如果看到 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 地址有误,可以参考我文章中的配置。