├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── blockj-base
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── rockyang
│ │ └── blockj
│ │ └── base
│ │ ├── constants
│ │ └── CryptoAlgorithms.java
│ │ ├── crypto
│ │ ├── Base58.java
│ │ ├── Bip39Wallet.java
│ │ ├── Credentials.java
│ │ ├── ECKeyPair.java
│ │ ├── Hash.java
│ │ ├── HexBin.java
│ │ ├── Keys.java
│ │ ├── LinuxSecureRandom.java
│ │ ├── MnemonicUtils.java
│ │ ├── SecureRandomUtils.java
│ │ ├── Sign.java
│ │ ├── Wallet.java
│ │ ├── WalletFile.java
│ │ └── WalletUtils.java
│ │ ├── enums
│ │ ├── MessageMethod.java
│ │ └── MessageStatus.java
│ │ ├── exceptions
│ │ ├── CipherException.java
│ │ ├── MessageDecodingException.java
│ │ └── MessageEncodingException.java
│ │ ├── model
│ │ ├── Account.java
│ │ ├── Block.java
│ │ ├── BlockHeader.java
│ │ ├── Message.java
│ │ ├── Peer.java
│ │ └── Wallet.java
│ │ ├── store
│ │ ├── Datastore.java
│ │ └── RocksDatastore.java
│ │ ├── utils
│ │ ├── ByteUtils.java
│ │ ├── CmdArgsParser.java
│ │ ├── Numeric.java
│ │ ├── SerializeUtils.java
│ │ ├── Strings.java
│ │ └── ThreadUtils.java
│ │ └── vo
│ │ ├── JsonVo.java
│ │ └── MnemonicWallet.java
│ └── test
│ └── java
│ └── org
│ └── rockyang
│ └── blockj
│ └── base
│ └── store
│ └── RocksDbTest.java
├── blockj-client
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── rockyang
│ │ └── blockj
│ │ └── client
│ │ ├── BlockClient.java
│ │ ├── cmd
│ │ ├── Chain.java
│ │ ├── Command.java
│ │ ├── Net.java
│ │ ├── Wallet.java
│ │ ├── chain
│ │ │ ├── ChainBlock.java
│ │ │ ├── ChainHead.java
│ │ │ ├── ChainMessage.java
│ │ │ └── SendMessage.java
│ │ ├── net
│ │ │ ├── NetConnect.java
│ │ │ ├── NetListen.java
│ │ │ └── NetPeers.java
│ │ ├── utils
│ │ │ ├── CliContext.java
│ │ │ └── Printer.java
│ │ └── wallet
│ │ │ ├── WalletBalance.java
│ │ │ ├── WalletList.java
│ │ │ └── WalletNew.java
│ │ ├── exception
│ │ ├── ApiError.java
│ │ ├── ApiException.java
│ │ ├── Error.java
│ │ └── ErrorDetails.java
│ │ └── rpc
│ │ ├── BlockRpcService.java
│ │ ├── BlockService.java
│ │ └── impl
│ │ ├── BlockServiceImpl.java
│ │ └── BlockServiceMock.java
│ └── test
│ ├── java
│ └── org
│ │ └── rockyang
│ │ └── blockj
│ │ └── client
│ │ └── test
│ │ ├── BaseTester.java
│ │ ├── ChainTest.java
│ │ ├── MessageTest.java
│ │ ├── TestApp.java
│ │ └── WalletTest.java
│ └── resources
│ └── log4j.properties
├── blockj-miner
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── org
│ │ │ └── rockyang
│ │ │ └── blockj
│ │ │ ├── AppRunner.java
│ │ │ ├── Application.java
│ │ │ ├── chain
│ │ │ ├── BlockPool.java
│ │ │ ├── Chain.java
│ │ │ ├── MessagePool.java
│ │ │ ├── event
│ │ │ │ ├── NewBlockEvent.java
│ │ │ │ ├── NewMessageEvent.java
│ │ │ │ ├── NewPeerEvent.java
│ │ │ │ └── SyncBlockEvent.java
│ │ │ ├── listener
│ │ │ │ ├── BlockEventListener.java
│ │ │ │ ├── MessageEventListener.java
│ │ │ │ └── PeerEventListener.java
│ │ │ └── sync
│ │ │ │ ├── ClientHandler.java
│ │ │ │ └── ServerHandler.java
│ │ │ ├── conf
│ │ │ ├── AppConfig.java
│ │ │ ├── ApplicationContextProvider.java
│ │ │ └── MinerConfig.java
│ │ │ ├── miner
│ │ │ ├── Miner.java
│ │ │ └── pow
│ │ │ │ ├── PowMiner.java
│ │ │ │ ├── PowResult.java
│ │ │ │ └── ProofOfWork.java
│ │ │ ├── net
│ │ │ ├── base
│ │ │ │ ├── BaseHandler.java
│ │ │ │ ├── MessagePacket.java
│ │ │ │ └── MessagePacketType.java
│ │ │ ├── client
│ │ │ │ ├── P2pClient.java
│ │ │ │ ├── P2pClientHandler.java
│ │ │ │ └── P2pClientListener.java
│ │ │ ├── conf
│ │ │ │ └── NetConfig.java
│ │ │ └── server
│ │ │ │ ├── P2pServer.java
│ │ │ │ ├── P2pServerHandler.java
│ │ │ │ └── P2pServerListener.java
│ │ │ ├── service
│ │ │ ├── AccountService.java
│ │ │ ├── BlockService.java
│ │ │ ├── MessageService.java
│ │ │ ├── PeerService.java
│ │ │ ├── WalletService.java
│ │ │ └── impl
│ │ │ │ ├── AccountServiceImpl.java
│ │ │ │ ├── BlockServiceImpl.java
│ │ │ │ ├── MessageServiceImpl.java
│ │ │ │ ├── PeerServiceImpl.java
│ │ │ │ └── WalletServiceImpl.java
│ │ │ ├── vo
│ │ │ ├── PacketVo.java
│ │ │ └── Result.java
│ │ │ └── web
│ │ │ ├── controller
│ │ │ ├── ChainController.java
│ │ │ ├── MessageController.java
│ │ │ ├── NetController.java
│ │ │ └── WalletController.java
│ │ │ └── handler
│ │ │ └── AppExceptionHandler.java
│ └── resources
│ │ └── en-mnemonic-word-list.txt
│ └── test
│ └── java
│ └── org
│ └── rockyang
│ └── jblock
│ └── TempTest.java
├── client
├── miner
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .mvn
3 | .mvnw
4 | .mvnw.cmd
5 |
6 | ### STS ###
7 | .apt_generated
8 | .classpath
9 | .factorypath
10 | .project
11 | .settings
12 | .springBeans
13 | .sts4-cache
14 |
15 | ### IntelliJ IDEA ###
16 | .idea
17 | *.iws
18 | *.iml
19 | *.ipr
20 |
21 | ### NetBeans ###
22 | nbproject/private/
23 | build/
24 | nbbuild/
25 | dist/
26 | nbdist/
27 | .nb-gradle/
28 | keystore
29 | *.log
30 | genesis.car
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | -------------------------------------------------------------------------------------------------------------
4 |
5 | ## Version 2.0.0
6 |
7 | 1. 将项目更名为 blockj
8 | 2. 重构项目,重写了 80% 的代码,将项目拆分成一个多模块的项目,分为 `blockj-base`, `blockj-miner`, `blockj-client`
9 | 3. 完全采用命令行的方式运行,不再依赖 IED
10 | 4. 交互采用命令模式,不再通过浏览器调用 API 的形式
11 | 5. 优化了区块同步和有以及存储服务
12 |
13 | ## Version 1.3
14 |
15 | 1. 移除账号同步广播功能,各个节点根据交易记录生成本地账号
16 | 2. 加入 swagger 依赖,生成 API 文档
17 | 3. 调整 API 结构, 更新 README 文档
18 | 4. 修复一些已知的bug
19 | 5. 加入共识和自动挖矿
20 |
21 | ## Version 1.1
22 |
23 | 1. 将项目名称修改为 ppblock
24 | 2. 重构钱包生成算法,修改了签名算法,新增通过助记词创建钱包, 新增生成 keystore file 钱包文件功能
25 | 3. 支持通过私钥字符串, 助记词,助记词+密码,keystore + 密码等多种方式恢复钱包(此处可以点赞)
26 | 4. 修改了交易(Transaction) 和账户(Account)实体的数据结构, 职责更加明确,Account 不在存储公钥等数据,只存储地址和余额。
27 | 5. 鉴于网上很多同学都吐槽说项目启动的时候一直不断的抛出异常,其实这是系统启动之后自动同步其他节点的最新区块数据导致的,因为 tio 配置的初始同步节点都没有启动,所以一直抛出连接超时的异常,其实这并不影响系统运行,但是为了方便那些有强迫症的同学(本人也是强迫症患者), 增加了 peer-discover
28 | 配置,可以手动关闭 p2p 网络同步,作为单节点测试运行。
29 | 6. 添加发送交易后自动挖矿功能,可配置成手动挖矿,默认是自动挖矿,如果初学者建议配置手动挖矿,这样你能更清楚的了解整个区块链交易工作的流程
30 | 7. 修复了一些已知的 Bug...
31 |
32 | ## Version 1.0
33 |
34 | 1. 初步了完成的区块链的各个模块,包括账户,区块链,网络等模块实现
35 | 2. 实现了账户创建,发送交易和挖矿功能
36 | 3. 实现了网络功能,包括发送广播账户,广播区块,广播交易以及自动同步区块功能。
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blockj
2 |
3 | > Java 实现的一个简易区块链(联盟链)项目,包括加密工具,钱包,P2P 传输,区块同步,网络共识等基础实现。
4 | > 它使用 SpringBoot + Tio 网络框架实现,是一个非常好的区块链学习项目,目前只实现了 POW 共识算法,如果要用于生产项目需要根据自己的项目需求修改共识。
5 |
6 | -----------------------------------------------
7 |
8 | ## 项目架构
9 |
10 | 主程序使用 SpringBoot 实现, P2P 传输这块使用的是 [t-io 网络框架](https://github.com/tywo45/t-io)。
11 |
12 | **运行环境为 JDK1.8 以上版本。**
13 |
14 | ## 项目模块
15 |
16 | * blockj-base 基础公共的工具包,如加密,区块,消息等数据模型,数据存储等。
17 | * blockj-miner 区块链主程序,如矿工,区块同步,P2P 网络,RPC API 等。
18 | * blockj-client 客户端命令行工具,主要就是调用 Miner 的相关 API,用户跟链交互。
19 |
20 | ## 快速开始
21 |
22 | 创建一条链的操作流程如下:
23 |
24 | 1. 创建创世节点(创建一个网络)
25 | 2. 启动创世节点(Genesis Miner)
26 | 3. 其他节点要加入网络的话,只需要以创世区块初始化 Miner,然后再启动 Miner 即可。
27 |
28 | ### 创建创世节点
29 |
30 | 首先我们需要编译打包程序:
31 |
32 | ```bash
33 | git clone https://gitee.com/blackfox/blockj.git
34 | cd blockj
35 | mvn clean package
36 | ```
37 |
38 | 然后创建创世节点:
39 |
40 | ```bash
41 | ./miner genesis --repo=/data/genesis --enable-mining=true
42 | ```
43 |
44 | 启动创世 Miner:
45 |
46 | ```bash
47 | ./miner run --repo=/data/genesis
48 | ```
49 |
50 | ### 启动新 Miner
51 |
52 | 首先需要初始化 miner,需要导入创世区块(genesis.car)来加入网络:
53 |
54 | ```bash
55 | ./miner init --repo=/data/miner1 --genesis=genesis.car --api.port=8002 --p2p.port=3456
56 | ```
57 |
58 | 启动 Miner
59 |
60 | ```bash
61 | ./miner run --repo=/data/miner1
62 | ```
63 |
64 | ### 客户端使用
65 |
66 | 1. 查看钱包列表
67 | ```shell
68 | ./client wallet list
69 | # 输出
70 | Address Balance Nonce
71 |
72 | 0x0d181310331612e107b5e0dfdf971cfb9de780bb 800 1
73 | 0x2505bf54f3a63848e44a105e8de599ad08ae8c58 2400 0
74 | 0xb6258bc70240ee5daa213f671c08db51e50a4cbe 50800 3
75 | 0xcd3da3ec4195070e13a6d88b75101f6ceb427a8e 0 0
76 | ```
77 |
78 | 2. 创建新钱包
79 | ```shell
80 | # 创建普通钱包
81 | ./client wallet new
82 | 0xb640636a77381b6589c78d58d629221131946dc
83 | # 创建带助记词的钱包,可设置密码,生成 keystore 文件
84 | ./client wallet new --mnemonic=true 123456
85 |
86 | Mnemonic words: rain fog canal matrix tonight initial frog wear feel movie worry whisper
87 | Address: 0x42a8037f2876f649e08f7be6764b810e9a2f21da
88 | ```
89 | 3. 查询钱包余额
90 |
91 | ```shell
92 | ./client wallet balance 0x2505bf54f3a63848e44a105e8de599ad08ae8c58
93 | # 输出
94 | Address Balance
95 | 0x2505bf54f3a63848e44a105e8de599ad08ae8c58 2400
96 | ```
97 |
98 | 4. 转账
99 | ```shell
100 | ./client chain send --from=0x0d181310331612e107b5e0dfdf971cfb9de780bb 0x2505bf54f3a63848e44a105e8de599ad08ae8c58 123
101 | # 输出
102 | Send message, CID: 05b6074241f1406cd1a68731d74cf612f55981692a3f4e5d9da01b13b4ee3631
103 | ```
104 |
105 | 5. 查看当前链高度
106 |
107 | ```shell
108 | ./client chain head
109 | Chain head: 1217
110 | ```
111 |
112 | 6. 查看指定的链上消息
113 |
114 | ```shell
115 | ./client chain getMessage 05b6074241f1406cd1a68731d74cf612f55981692a3f4e5d9da01b13b4ee3631
116 | # 输出
117 | Message{version=1, from='0x0d181310331612e107b5e0dfdf971cfb9de780bb', to='0x2505bf54f3a63848e44a105e8de599ad08ae8c58', value=123, timestamp=1672826743640, pubKey='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCw5EXe33S25zZDT25sNYu1bjtBfaCwEGSgnGhJiE31fCfDsyE3pNFw7cC87VfQZQqiEdntMmztfpiDcRe1gv3aJJ4', cid='05b6074241f1406cd1a68731d74cf612f55981692a3f4e5d9da01b13b4ee3631', status=APPENDING, nonce=2, params='null', height=0, sign='30460221009A8B2750A6D986EB926B67163B740BBACDD07FE2D87F8FA9AE2F08424989477602210082C1C36EAEEC6367C023847F995291873F305B867E9B65A5C68ED8A4293DB890'}
118 | ```
119 |
120 | 7. 查看指定高度的区块信息
121 |
122 | ```shell
123 | ./client chain getBlock 1
124 | # 输出
125 | Block{header=BlockHeader{height=1, difficulty=28269553036454149273332760011886696253239742350009903329945699220681916416, nonce=703, createTime=1672813674, hash='0004c262f7ead28cc66c9336d7a8335cb8fea5a06b0b1fd7488c3c9b140987cc', previousHash='ed5126ddd65f39a17739b8e26ea3edecfff6bf196148dc259d9a3eddeefc23d5'}, messages=[Message{version=0, from='B099', to='0xb6258bc70240ee5daa213f671c08db51e50a4cbe', value=50, timestamp=1672813674163, pubKey='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSD1ErKp8AqSj4Ph9Jsj2Gvk7w1pyLgqDRiguC7JvjeGZJZ1si1qRYCAsVmu1UvYRqvhiCBgYDpmyuWK5VzD5KK4RNY', cid='3e1f8987b0d66b2de155e78aeef6984ab7cb6c3acdf03c835c01f4b4088fb90d', status=SUCCESS, nonce=0, params='Miner Reward.', height=1, sign='304402202DF6EABBF5C81C41996C44F8E1230D44CAE8ADA9184B466D9B708ADC8B050225022049D74E4E99E0EA8E56208AD2E4B7B17C2320DC3E6461A17C94D1820818559CA9'}], blockSign='3046022100C04714C00642527AF6AA1DB2B537E5FD887F52999F66929AA2A928D4C6A4897A022100AF98888FDC825FFB0683E8D65494363790E5173F3991AF61AB86DEEFEAF15D81'}
126 | ```
127 |
128 | 8. 查看当前网络中 P2P 节点列表
129 |
130 | ```shell
131 | ./client net peers
132 | ```
133 |
134 | 9. 查看当前节点 P2P 连接信息
135 |
136 | ```shell
137 | ./client net listen
138 | # P2P 连接信息,用于被其他节点连接
139 | 127.0.0.1:2345
140 | ```
141 |
142 | 10. 手动连接某个节点
143 | ```shell
144 | ./client net connect 192.22.33.11:3456
145 | ```
146 |
147 | ## TODOLIST
148 |
149 | - [x] blockj-miner api 实现
150 | - [x] 消息同步和打包
151 | - [x] blockj-client(客户端)功能实现:网络,钱包,转账
152 | - [x] 文档完善
153 | - [ ] 添加 PBFT 共识支持
154 |
--------------------------------------------------------------------------------
/blockj-base/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.rockyang
7 | blockj
8 | 2.0.0
9 |
10 |
11 | blockj-base
12 | 2.0.0
13 | jar
14 |
15 | blockj-base
16 | common utils and models for blockj
17 |
18 |
19 | UTF-8
20 | UTF-8
21 | 8
22 |
23 |
24 |
25 |
26 |
27 | org.rocksdb
28 | rocksdbjni
29 | 7.1.2
30 |
31 |
32 |
33 |
34 | com.esotericsoftware
35 | kryo
36 | 4.0.1
37 |
38 |
39 |
40 |
41 | org.bouncycastle
42 | bcprov-jdk15on
43 | 1.69
44 |
45 |
46 | commons-codec
47 | commons-codec
48 | 1.13
49 |
50 |
51 |
52 |
53 | com.fasterxml.jackson.core
54 | jackson-databind
55 | 2.12.7.1
56 |
57 |
58 |
59 | org.apache.commons
60 | commons-lang3
61 | 3.7
62 |
63 |
64 |
65 |
66 | junit
67 | junit
68 | 4.13.1
69 | test
70 |
71 |
72 | org.slf4j
73 | slf4j-api
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | org.apache.maven.plugins
83 | maven-compiler-plugin
84 | 3.10.1
85 |
86 | 8
87 | 8
88 | true
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/constants/CryptoAlgorithms.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.constants;
2 |
3 | /**
4 | * 加密算法用到的常量
5 | *
6 | * @author yangjian
7 | */
8 | public interface CryptoAlgorithms {
9 | /**
10 | * 椭圆曲线密钥生成算法,ECDSA
11 | */
12 | String KEY_GEN_ALGORITHM = "ECDSA";
13 |
14 | /**
15 | * 椭圆曲线(EC)域参数设定
16 | */
17 | String EC_PARAM_SPEC = "secp256k1";
18 |
19 | /**
20 | * 签名算法
21 | */
22 | String SIGN_ALGORITHM = "SHA1withECDSA";
23 | }
24 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/Bip39Wallet.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | /**
4 | * Data class encapsulating a BIP-39 compatible Ethereum wallet.
5 | *
6 | * @author yangjian
7 | */
8 | public class Bip39Wallet {
9 |
10 | private final ECKeyPair keyPair;
11 | /**
12 | * Path to wallet file.
13 | */
14 | private final String filename;
15 |
16 | /**
17 | * Generated BIP-39 mnemonic for the wallet.
18 | */
19 | private final String mnemonic;
20 |
21 | public Bip39Wallet(ECKeyPair keyPair, String filename, String mnemonic)
22 | {
23 | this.keyPair = keyPair;
24 | this.filename = filename;
25 | this.mnemonic = mnemonic;
26 | }
27 |
28 | public String getFilename()
29 | {
30 | return filename;
31 | }
32 |
33 | public String getMnemonic()
34 | {
35 | return mnemonic;
36 | }
37 |
38 | public ECKeyPair getKeyPair()
39 | {
40 | return keyPair;
41 | }
42 |
43 | @Override
44 | public String toString()
45 | {
46 | return "Bip39Wallet{"
47 | + "filename='" + filename + '\''
48 | + ", mnemonic='" + mnemonic + '\''
49 | + '}';
50 | }
51 | }
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/Credentials.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import org.rockyang.blockj.base.utils.Numeric;
4 |
5 | /**
6 | * Credential(账户凭证) 封装
7 | *
8 | * @author yangjian.
9 | */
10 | public class Credentials {
11 |
12 | private final ECKeyPair ecKeyPair;
13 | private final String address;
14 |
15 | private Credentials(ECKeyPair ecKeyPair, String address)
16 | {
17 | this.ecKeyPair = ecKeyPair;
18 | this.address = address;
19 | }
20 |
21 | public ECKeyPair getEcKeyPair()
22 | {
23 | return ecKeyPair;
24 | }
25 |
26 | public String getAddress()
27 | {
28 | return address;
29 | }
30 |
31 | public static Credentials create(ECKeyPair ecKeyPair)
32 | {
33 | String address = Numeric.prependHexPrefix(Keys.getAddress(ecKeyPair));
34 | return new Credentials(ecKeyPair, address);
35 | }
36 |
37 | public static Credentials create(String privateKey, String publicKey) throws Exception
38 | {
39 | return create(new ECKeyPair(Numeric.toBigInt(privateKey), Numeric.toBigInt(publicKey)));
40 | }
41 |
42 | public static Credentials create(String privateKey) throws Exception
43 | {
44 | return create(ECKeyPair.create(Numeric.toBigInt(privateKey)));
45 | }
46 |
47 | @Override
48 | public boolean equals(Object o)
49 | {
50 | if (this == o) {
51 | return true;
52 | }
53 | if (o == null || getClass() != o.getClass()) {
54 | return false;
55 | }
56 |
57 | Credentials that = (Credentials) o;
58 |
59 | if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) {
60 | return false;
61 | }
62 |
63 | return address != null ? address.equals(that.address) : that.address == null;
64 | }
65 |
66 | @Override
67 | public int hashCode()
68 | {
69 | int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0;
70 | result = 31 * result + (address != null ? address.hashCode() : 0);
71 | return result;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/ECKeyPair.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
4 | import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
5 | import org.rockyang.blockj.base.utils.Numeric;
6 |
7 | import java.math.BigInteger;
8 | import java.security.KeyPair;
9 | import java.security.PrivateKey;
10 | import java.security.PublicKey;
11 | import java.util.Arrays;
12 |
13 | /**
14 | * 使用椭圆曲线算法生成密钥对
15 | * Elliptic Curve SECP-256k1 generated key pair.
16 | *
17 | * @author yangjian
18 | */
19 | public class ECKeyPair {
20 |
21 | private final PrivateKey privateKey;
22 | private final PublicKey publicKey;
23 | private final BigInteger privateKeyValue;
24 | private final BigInteger publicKeyValue;
25 |
26 | public ECKeyPair(BigInteger privateKeyValue, BigInteger publicKeyValue) throws Exception
27 | {
28 | this.privateKeyValue = privateKeyValue;
29 | this.publicKeyValue = publicKeyValue;
30 | this.privateKey = Sign.privateKeyFromBigInteger(privateKeyValue);
31 | this.publicKey = Sign.publicKeyFromPrivate(privateKeyValue);
32 | }
33 |
34 | public ECKeyPair(PrivateKey privateKey, PublicKey publicKey)
35 | {
36 | this.privateKey = privateKey;
37 | this.publicKey = publicKey;
38 | // 生成 BigInteger 形式的公钥和私钥
39 | BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) this.privateKey;
40 | BCECPublicKey bcecPublicKey = (BCECPublicKey) this.publicKey;
41 | // 分别计算公钥和私钥的值
42 | BigInteger privateKeyValue = bcecPrivateKey.getD();
43 | byte[] publicKeyBytes = bcecPublicKey.getQ().getEncoded(false);
44 | BigInteger publicKeyValue = new BigInteger(1, Arrays.copyOfRange(publicKeyBytes, 1, publicKeyBytes.length));
45 | this.privateKeyValue = privateKeyValue;
46 | this.publicKeyValue = publicKeyValue;
47 | }
48 |
49 | public PrivateKey getPrivateKey()
50 | {
51 | return privateKey;
52 | }
53 |
54 | /**
55 | * export the private key to hex string
56 | *
57 | * @return
58 | */
59 | public String exportPrivateKey()
60 | {
61 |
62 | return Numeric.toHexStringNoPrefix(this.getPrivateKeyValue());
63 | }
64 |
65 | /**
66 | * get the address
67 | *
68 | * @return
69 | */
70 | public String getAddress()
71 | {
72 | return Keys.getAddress(this.getPublicKeyValue());
73 | }
74 |
75 | public PublicKey getPublicKey()
76 | {
77 | return publicKey;
78 | }
79 |
80 | public BigInteger getPrivateKeyValue()
81 | {
82 | return privateKeyValue;
83 | }
84 |
85 | public BigInteger getPublicKeyValue()
86 | {
87 | return publicKeyValue;
88 | }
89 |
90 | public static ECKeyPair create(KeyPair keyPair)
91 | {
92 | BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
93 | BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
94 |
95 | return new ECKeyPair(privateKey, publicKey);
96 | }
97 |
98 | public static ECKeyPair create(BigInteger privateKeyValue) throws Exception
99 | {
100 |
101 | PrivateKey privateKey = Sign.privateKeyFromBigInteger(privateKeyValue);
102 | PublicKey publicKey = Sign.publicKeyFromPrivate(privateKeyValue);
103 | return new ECKeyPair(privateKey, publicKey);
104 | }
105 |
106 | public static ECKeyPair create(byte[] privateKey) throws Exception
107 | {
108 | return create(Numeric.toBigInt(privateKey));
109 | }
110 |
111 | @Override
112 | public boolean equals(Object o)
113 | {
114 | if (this == o) {
115 | return true;
116 | }
117 | if (o == null || getClass() != o.getClass()) {
118 | return false;
119 | }
120 |
121 | ECKeyPair ecKeyPair = (ECKeyPair) o;
122 |
123 | if (privateKey != null
124 | ? !privateKey.equals(ecKeyPair.privateKey) : ecKeyPair.privateKey != null) {
125 | return false;
126 | }
127 |
128 | return publicKey != null
129 | ? publicKey.equals(ecKeyPair.publicKey) : ecKeyPair.publicKey == null;
130 | }
131 |
132 | @Override
133 | public int hashCode()
134 | {
135 | int result = privateKey != null ? privateKey.hashCode() : 0;
136 | result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0);
137 | return result;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/Hash.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import org.apache.commons.codec.digest.DigestUtils;
4 | import org.bouncycastle.jcajce.provider.digest.Keccak;
5 | import org.rockyang.blockj.base.utils.Numeric;
6 |
7 | import java.nio.charset.StandardCharsets;
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 |
11 | /**
12 | * hash 工具类
13 | *
14 | * @author yangjian
15 | * @since 2018-04-07 下午8:32.
16 | */
17 | public class Hash {
18 | private Hash()
19 | {
20 | }
21 |
22 | /**
23 | * Keccak-256 hash function.
24 | *
25 | * @param hexInput hex encoded input data with optional 0x prefix
26 | * @return hash value as hex encoded string
27 | */
28 | public static String sha3(String hexInput)
29 | {
30 | byte[] bytes = Numeric.hexStringToByteArray(hexInput);
31 | byte[] result = sha3(bytes);
32 | return Numeric.toHexString(result);
33 | }
34 |
35 | public static String sha256Hex(byte[] input)
36 | {
37 |
38 | return DigestUtils.sha256Hex(input);
39 | }
40 |
41 | /**
42 | * Keccak-256 hash function.
43 | *
44 | * @param input binary encoded input data
45 | * @param offset of start of data
46 | * @param length of data
47 | * @return hash value
48 | */
49 | public static byte[] sha3(byte[] input, int offset, int length)
50 | {
51 | Keccak.DigestKeccak kecc = new Keccak.Digest256();
52 | kecc.update(input, offset, length);
53 | return kecc.digest();
54 | }
55 |
56 | /**
57 | * Keccak-256 hash function.
58 | *
59 | * @param input binary encoded input data
60 | * @return hash value
61 | */
62 | public static byte[] sha3(byte[] input)
63 | {
64 | return sha3(input, 0, input.length);
65 | }
66 |
67 | /**
68 | * Keccak-256 hash function that operates on a UTF-8 encoded String.
69 | *
70 | * @param utf8String UTF-8 encoded string
71 | * @return hash value as hex encoded string
72 | */
73 | public static String sha3String(String utf8String)
74 | {
75 | return Numeric.toHexString(sha3(utf8String.getBytes(StandardCharsets.UTF_8)));
76 | }
77 |
78 | /**
79 | * Generates SHA-256 digest for the given {@code input}.
80 | *
81 | * @param input The input to digest
82 | * @return The hash value for the given input
83 | * @throws RuntimeException If we couldn't find any SHA-256 provider
84 | */
85 | public static byte[] sha256(byte[] input)
86 | {
87 | try {
88 | MessageDigest digest = MessageDigest.getInstance("SHA-256");
89 | return digest.digest(input);
90 | } catch (NoSuchAlgorithmException e) {
91 | throw new RuntimeException("Couldn't find a SHA-256 provider", e);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/HexBin.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | /**
4 | * copy from com.sun.org.apache.xerces.internal.impl.dv.util.HexBin
5 | *
6 | * @author yangjian
7 | */
8 | public final class HexBin {
9 | private static final int BASELENGTH = 128;
10 | private static final int LOOKUPLENGTH = 16;
11 | private static final byte[] hexNumberTable = new byte[128];
12 | private static final char[] lookUpHexAlphabet = new char[16];
13 |
14 | public HexBin()
15 | {
16 | }
17 |
18 | public static String encode(byte[] binaryData)
19 | {
20 | if (binaryData == null) {
21 | return null;
22 | } else {
23 | int lengthData = binaryData.length;
24 | int lengthEncode = lengthData * 2;
25 | char[] encodedData = new char[lengthEncode];
26 |
27 | for (int i = 0; i < lengthData; ++i) {
28 | int temp = binaryData[i];
29 | if (temp < 0) {
30 | temp += 256;
31 | }
32 |
33 | encodedData[i * 2] = lookUpHexAlphabet[temp >> 4];
34 | encodedData[i * 2 + 1] = lookUpHexAlphabet[temp & 15];
35 | }
36 |
37 | return new String(encodedData);
38 | }
39 | }
40 |
41 | public static byte[] decode(String encoded)
42 | {
43 | if (encoded == null) {
44 | return null;
45 | } else {
46 | int lengthData = encoded.length();
47 | if (lengthData % 2 != 0) {
48 | return null;
49 | } else {
50 | char[] binaryData = encoded.toCharArray();
51 | int lengthDecode = lengthData / 2;
52 | byte[] decodedData = new byte[lengthDecode];
53 |
54 | for (int i = 0; i < lengthDecode; ++i) {
55 | char tempChar = binaryData[i * 2];
56 | byte temp1 = tempChar < 128 ? hexNumberTable[tempChar] : -1;
57 | if (temp1 == -1) {
58 | return null;
59 | }
60 |
61 | tempChar = binaryData[i * 2 + 1];
62 | byte temp2 = tempChar < 128 ? hexNumberTable[tempChar] : -1;
63 | if (temp2 == -1) {
64 | return null;
65 | }
66 |
67 | decodedData[i] = (byte) (temp1 << 4 | temp2);
68 | }
69 |
70 | return decodedData;
71 | }
72 | }
73 | }
74 |
75 | static {
76 | int i;
77 | for (i = 0; i < 128; ++i) {
78 | hexNumberTable[i] = -1;
79 | }
80 |
81 | for (i = 57; i >= 48; --i) {
82 | hexNumberTable[i] = (byte) (i - 48);
83 | }
84 |
85 | for (i = 70; i >= 65; --i) {
86 | hexNumberTable[i] = (byte) (i - 65 + 10);
87 | }
88 |
89 | for (i = 102; i >= 97; --i) {
90 | hexNumberTable[i] = (byte) (i - 97 + 10);
91 | }
92 |
93 | for (i = 0; i < 10; ++i) {
94 | lookUpHexAlphabet[i] = (char) (48 + i);
95 | }
96 |
97 | for (i = 10; i <= 15; ++i) {
98 | lookUpHexAlphabet[i] = (char) (65 + i - 10);
99 | }
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/Keys.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import org.bouncycastle.jce.ECNamedCurveTable;
4 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
5 | import org.bouncycastle.jce.spec.ECParameterSpec;
6 | import org.rockyang.blockj.base.constants.CryptoAlgorithms;
7 | import org.rockyang.blockj.base.utils.Numeric;
8 | import org.rockyang.blockj.base.utils.Strings;
9 |
10 | import java.math.BigInteger;
11 | import java.security.*;
12 | import java.util.Arrays;
13 |
14 |
15 | /**
16 | * Crypto key utilities.
17 | *
18 | * @author yangjian
19 | */
20 | public class Keys {
21 |
22 | static final int PRIVATE_KEY_SIZE = 32;
23 | static final int PUBLIC_KEY_SIZE = 64;
24 |
25 | public static final int ADDRESS_SIZE = 160;
26 | public static final int ADDRESS_LENGTH_IN_HEX = ADDRESS_SIZE >> 2;
27 | static final int PUBLIC_KEY_LENGTH_IN_HEX = PUBLIC_KEY_SIZE << 1;
28 | public static final int PRIVATE_KEY_LENGTH_IN_HEX = PRIVATE_KEY_SIZE << 1;
29 |
30 | static {
31 | if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
32 | Security.addProvider(new BouncyCastleProvider());
33 | }
34 | }
35 |
36 | private Keys()
37 | {
38 | }
39 |
40 | /**
41 | * Create a keypair using SECP-256k1 curve.
42 | *
43 | *
Private keypairs are encoded using PKCS8
44 | *
45 | *
Private keys are encoded using X.509
46 | */
47 | static KeyPair createSecp256k1KeyPair() throws NoSuchProviderException,
48 | NoSuchAlgorithmException, InvalidAlgorithmParameterException
49 | {
50 |
51 | // 注册 BC Provider
52 | Security.addProvider(new BouncyCastleProvider());
53 | // 创建椭圆曲线算法的密钥对生成器
54 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
55 | CryptoAlgorithms.KEY_GEN_ALGORITHM,
56 | BouncyCastleProvider
57 | .PROVIDER_NAME);
58 | // 椭圆曲线(EC)域参数设定
59 | ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(CryptoAlgorithms.EC_PARAM_SPEC);
60 | keyPairGenerator.initialize(ecSpec, new SecureRandom());
61 | return keyPairGenerator.generateKeyPair();
62 | }
63 |
64 | public static ECKeyPair createEcKeyPair() throws InvalidAlgorithmParameterException,
65 | NoSuchAlgorithmException, NoSuchProviderException
66 | {
67 | KeyPair keyPair = createSecp256k1KeyPair();
68 | return ECKeyPair.create(keyPair);
69 | }
70 |
71 | public static String getAddress(ECKeyPair ecKeyPair)
72 | {
73 | return getAddress(ecKeyPair.getPublicKeyValue());
74 | }
75 |
76 | public static String getAddress(BigInteger publicKey)
77 | {
78 | return getAddress(
79 | Numeric.toHexStringWithPrefixZeroPadded(publicKey, PUBLIC_KEY_LENGTH_IN_HEX));
80 | }
81 |
82 | public static String getAddress(String publicKey)
83 | {
84 | String publicKeyNoPrefix = Numeric.cleanHexPrefix(publicKey);
85 |
86 | if (publicKeyNoPrefix.length() < PUBLIC_KEY_LENGTH_IN_HEX) {
87 | publicKeyNoPrefix = Strings.zeros(
88 | PUBLIC_KEY_LENGTH_IN_HEX - publicKeyNoPrefix.length())
89 | + publicKeyNoPrefix;
90 | }
91 | String hash = Hash.sha3(publicKeyNoPrefix);
92 | // right most 160 bits
93 | return Numeric.HEX_PREFIX + hash.substring(hash.length() - ADDRESS_LENGTH_IN_HEX);
94 | }
95 |
96 | /**
97 | * get address with 0x prefix
98 | *
99 | * @param publicKey
100 | * @return
101 | */
102 | public static String getAddressWithoutPrefix(BigInteger publicKey)
103 | {
104 | return Numeric.cleanHexPrefix(getAddress(publicKey));
105 | }
106 |
107 | public static byte[] getAddress(byte[] publicKey)
108 | {
109 | byte[] hash = Hash.sha3(publicKey);
110 | // right most 160 bits
111 | return Arrays.copyOfRange(hash, hash.length - 20, hash.length);
112 | }
113 |
114 | /**
115 | * Checksum address encoding as per
116 | * EIP-55 .
117 | *
118 | * @param address a valid hex encoded address
119 | * @return hex encoded checksum address
120 | */
121 | public static String toChecksumAddress(String address)
122 | {
123 | String lowercaseAddress = Numeric.cleanHexPrefix(address).toLowerCase();
124 | String addressHash = Numeric.cleanHexPrefix(Hash.sha3String(lowercaseAddress));
125 |
126 | StringBuilder result = new StringBuilder(lowercaseAddress.length() + 2);
127 |
128 | result.append("0x");
129 |
130 | for (int i = 0; i < lowercaseAddress.length(); i++) {
131 | if (Integer.parseInt(String.valueOf(addressHash.charAt(i)), 16) >= 8) {
132 | result.append(String.valueOf(lowercaseAddress.charAt(i)).toUpperCase());
133 | } else {
134 | result.append(lowercaseAddress.charAt(i));
135 | }
136 | }
137 |
138 | return result.toString();
139 | }
140 |
141 | public static byte[] serialize(ECKeyPair ecKeyPair)
142 | {
143 | byte[] privateKey = Numeric.toBytesPadded(ecKeyPair.getPrivateKeyValue(), PRIVATE_KEY_SIZE);
144 | byte[] publicKey = Numeric.toBytesPadded(ecKeyPair.getPublicKeyValue(), PUBLIC_KEY_SIZE);
145 |
146 | byte[] result = Arrays.copyOf(privateKey, PRIVATE_KEY_SIZE + PUBLIC_KEY_SIZE);
147 | System.arraycopy(publicKey, 0, result, PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE);
148 | return result;
149 | }
150 |
151 | public static ECKeyPair deserialize(byte[] input) throws Exception
152 | {
153 | if (input.length != PRIVATE_KEY_SIZE + PUBLIC_KEY_SIZE) {
154 | throw new RuntimeException("Invalid input key size");
155 | }
156 |
157 | BigInteger privateKey = Numeric.toBigInt(input, 0, PRIVATE_KEY_SIZE);
158 | BigInteger publicKey = Numeric.toBigInt(input, PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE);
159 |
160 | return new ECKeyPair(privateKey, publicKey);
161 | }
162 |
163 | /**
164 | * 将 byte[] 公钥转成字符串
165 | *
166 | * @param publicKey
167 | * @return
168 | */
169 | public static String publicKeyEncode(byte[] publicKey)
170 | {
171 | return Base58.encode(publicKey);
172 | }
173 |
174 | /**
175 | * 将字符串转成 byte[]
176 | *
177 | * @param publicKey
178 | * @return
179 | */
180 | public static byte[] publicKeyDecode(String publicKey)
181 | {
182 | return Base58.decode(publicKey);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/LinuxSecureRandom.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.rockyang.blockj.base.crypto;
18 |
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import java.io.*;
23 | import java.security.Provider;
24 | import java.security.SecureRandomSpi;
25 | import java.security.Security;
26 |
27 | /**
28 | * Implementation from
29 | * BitcoinJ implementation
30 | *
31 | *
A SecureRandom implementation that is able to override the standard JVM provided
32 | * implementation, and which simply serves random numbers by reading /dev/urandom. That is, it
33 | * delegates to the kernel on UNIX systems and is unusable on other platforms. Attempts to manually
34 | * set the seed are ignored. There is no difference between seed bytes and non-seed bytes, they are
35 | * all from the same source.
36 | */
37 | public class LinuxSecureRandom extends SecureRandomSpi {
38 | private static final FileInputStream urandom;
39 |
40 | private static class LinuxSecureRandomProvider extends Provider {
41 | public LinuxSecureRandomProvider()
42 | {
43 | super("LinuxSecureRandom", 1.0, "A Linux specific random number provider that uses /dev/urandom");
44 | put("SecureRandom.LinuxSecureRandom", LinuxSecureRandom.class.getName());
45 | }
46 | }
47 |
48 | private static final Logger log = LoggerFactory.getLogger(LinuxSecureRandom.class);
49 |
50 | static {
51 | try {
52 | File file = new File("/dev/urandom");
53 | // This stream is deliberately leaked.
54 | urandom = new FileInputStream(file);
55 | if (urandom.read() == -1) {
56 | throw new RuntimeException("/dev/urandom not readable?");
57 | }
58 | // Now override the default SecureRandom implementation with this one.
59 | int position = Security.insertProviderAt(new LinuxSecureRandomProvider(), 1);
60 |
61 | if (position != -1) {
62 | log.info("Secure randomness will be read from {} only.", file);
63 | } else {
64 | log.info("Randomness is already secure.");
65 | }
66 | } catch (FileNotFoundException e) {
67 | // Should never happen.
68 | log.error("/dev/urandom does not appear to exist or is not openable");
69 | throw new RuntimeException(e);
70 | } catch (IOException e) {
71 | log.error("/dev/urandom does not appear to be readable");
72 | throw new RuntimeException(e);
73 | }
74 | }
75 |
76 | private final DataInputStream dis;
77 |
78 | public LinuxSecureRandom()
79 | {
80 | // DataInputStream is not thread safe, so each random object has its own.
81 | dis = new DataInputStream(urandom);
82 | }
83 |
84 | @Override
85 | protected void engineSetSeed(byte[] bytes)
86 | {
87 | // Ignore.
88 | }
89 |
90 | @Override
91 | protected void engineNextBytes(byte[] bytes)
92 | {
93 | try {
94 | dis.readFully(bytes); // This will block until all the bytes can be read.
95 | } catch (IOException e) {
96 | throw new RuntimeException(e); // Fatal error. Do not attempt to recover from this.
97 | }
98 | }
99 |
100 | @Override
101 | protected byte[] engineGenerateSeed(int i)
102 | {
103 | byte[] bits = new byte[i];
104 | engineNextBytes(bits);
105 | return bits;
106 | }
107 | }
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/SecureRandomUtils.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import java.security.SecureRandom;
4 |
5 | /**
6 | * Utility class for working with SecureRandom implementation.
7 | *
8 | *
This is to address issues with SecureRandom on Android. For more information, refer to the
9 | * following issue .
10 | */
11 | public final class SecureRandomUtils
12 | {
13 |
14 | private static final SecureRandom SECURE_RANDOM;
15 |
16 | static {
17 | if (isAndroidRuntime()) {
18 | new LinuxSecureRandom();
19 | }
20 | SECURE_RANDOM = new SecureRandom();
21 | }
22 |
23 | public static SecureRandom secureRandom()
24 | {
25 | return SECURE_RANDOM;
26 | }
27 |
28 | // Taken from BitcoinJ implementation
29 | // https://github.com/bitcoinj/bitcoinj/blob/3cb1f6c6c589f84fe6e1fb56bf26d94cccc85429/core/src/main/java/org/bitcoinj/core/Utils.java#L573
30 | private static int isAndroid = -1;
31 |
32 | static boolean isAndroidRuntime()
33 | {
34 | if (isAndroid == -1) {
35 | final String runtime = System.getProperty("java.runtime.name");
36 | isAndroid = (runtime != null && runtime.equals("Android Runtime")) ? 1 : 0;
37 | }
38 | return isAndroid == 1;
39 | }
40 |
41 | private SecureRandomUtils()
42 | {
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/crypto/Sign.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.crypto;
2 |
3 | import org.bouncycastle.asn1.x9.X9ECParameters;
4 | import org.bouncycastle.crypto.ec.CustomNamedCurves;
5 | import org.bouncycastle.crypto.params.ECDomainParameters;
6 | import org.bouncycastle.jce.ECNamedCurveTable;
7 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
8 | import org.bouncycastle.jce.spec.ECParameterSpec;
9 | import org.bouncycastle.jce.spec.ECPrivateKeySpec;
10 | import org.bouncycastle.jce.spec.ECPublicKeySpec;
11 | import org.bouncycastle.math.ec.ECPoint;
12 | import org.bouncycastle.math.ec.FixedPointCombMultiplier;
13 | import org.rockyang.blockj.base.constants.CryptoAlgorithms;
14 | import org.rockyang.blockj.base.utils.Numeric;
15 |
16 | import java.math.BigInteger;
17 | import java.security.*;
18 | import java.security.interfaces.ECPublicKey;
19 | import java.security.spec.PKCS8EncodedKeySpec;
20 | import java.security.spec.X509EncodedKeySpec;
21 |
22 | /**
23 | * 签名工具类
24 | *
25 | * @author yangjian
26 | * @since 18-4-10
27 | */
28 | public class Sign {
29 |
30 | private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName(CryptoAlgorithms.EC_PARAM_SPEC);
31 | static final ECDomainParameters CURVE = new ECDomainParameters(
32 | CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
33 |
34 | /**
35 | * 使用私钥对源数据签名
36 | *
37 | * @param privateKey
38 | * @param data
39 | * @return
40 | */
41 | public static String sign(String privateKey, String data) throws Exception
42 | {
43 |
44 | return sign(privateKeyFromString(privateKey), data);
45 | }
46 |
47 | public static String sign(BigInteger privateKeyValue, String data) throws Exception
48 | {
49 |
50 | return sign(privateKeyFromBigInteger(privateKeyValue), data);
51 | }
52 |
53 | public static String sign(PrivateKey privateKey, String data) throws Exception
54 | {
55 |
56 | PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
57 | Security.addProvider(new BouncyCastleProvider());
58 | KeyFactory keyFactory = KeyFactory.getInstance(CryptoAlgorithms.KEY_GEN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
59 | PrivateKey pkcs8PrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
60 | Signature signature = Signature.getInstance(CryptoAlgorithms.SIGN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
61 | signature.initSign(pkcs8PrivateKey);
62 | signature.update(data.getBytes());
63 | byte[] res = signature.sign();
64 | return HexBin.encode(res);
65 | }
66 |
67 | public static String sign(Credentials credentials, String data) throws Exception
68 | {
69 |
70 | return sign(credentials.getEcKeyPair().getPrivateKey(), data);
71 | }
72 |
73 | /**
74 | * 使用公钥验证签名
75 | *
76 | * @param publicKey
77 | * @param sign
78 | * @param data
79 | * @return
80 | */
81 | public static boolean verify(byte[] publicKey, String sign, String data) throws Exception
82 | {
83 |
84 | return verify(publicKeyFromByte(publicKey), sign, data);
85 | }
86 |
87 | public static boolean verify(PublicKey publicKey, String sign, String data) throws Exception
88 | {
89 |
90 | X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
91 | Security.addProvider(new BouncyCastleProvider());
92 | KeyFactory keyFactory = KeyFactory.getInstance(CryptoAlgorithms.KEY_GEN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
93 | PublicKey x509PublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
94 | Signature signature = Signature.getInstance(CryptoAlgorithms.SIGN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
95 | signature.initVerify(x509PublicKey);
96 | signature.update(data.getBytes());
97 | return signature.verify(HexBin.decode(sign));
98 | }
99 |
100 |
101 | /**
102 | * 通过秘钥值(BigInteger)生成 PrivateKey 对象
103 | *
104 | * @param privateKeyValue 秘钥值
105 | * @return
106 | */
107 | public static PrivateKey privateKeyFromBigInteger(BigInteger privateKeyValue) throws Exception
108 | {
109 |
110 | ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(CryptoAlgorithms.EC_PARAM_SPEC);
111 | ECPrivateKeySpec keySpec = new ECPrivateKeySpec(privateKeyValue, ecSpec);
112 | Security.addProvider(new BouncyCastleProvider());
113 | KeyFactory keyFactory = KeyFactory.getInstance(CryptoAlgorithms.KEY_GEN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
114 | return keyFactory.generatePrivate(keySpec);
115 | }
116 |
117 | /**
118 | * 通过秘钥值(16进制字符串)生成 PrivateKey 对象
119 | *
120 | * @param privateKey 秘钥值
121 | * @return
122 | */
123 | public static PrivateKey privateKeyFromString(String privateKey) throws Exception
124 | {
125 |
126 | return privateKeyFromBigInteger(Numeric.toBigInt(privateKey));
127 | }
128 |
129 | /**
130 | * 通过秘钥值(BigInteger)生成 PrivateKey 对象
131 | *
132 | * @param privateKeyValue 秘钥值
133 | * @return
134 | */
135 | public static PublicKey publicKeyFromPrivate(BigInteger privateKeyValue) throws Exception
136 | {
137 |
138 | ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(CryptoAlgorithms.EC_PARAM_SPEC);
139 | ECPoint point = publicPointFromPrivate(privateKeyValue);
140 | ECPublicKeySpec keySpec = new ECPublicKeySpec(point, ecSpec);
141 | Security.addProvider(new BouncyCastleProvider());
142 | KeyFactory keyFactory = KeyFactory.getInstance(CryptoAlgorithms.KEY_GEN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
143 | return keyFactory.generatePublic(keySpec);
144 | }
145 |
146 | /**
147 | * 通过 byte[] 公钥生成 PublicKey 对象
148 | *
149 | * @param publicKey
150 | * @return
151 | */
152 | public static PublicKey publicKeyFromByte(byte[] publicKey) throws Exception
153 | {
154 |
155 | X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
156 | Security.addProvider(new BouncyCastleProvider());
157 | KeyFactory keyFactory = KeyFactory.getInstance(CryptoAlgorithms.KEY_GEN_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
158 | ECPublicKey pubKey = (ECPublicKey) keyFactory.generatePublic(x509KeySpec);
159 | return pubKey;
160 | }
161 |
162 | /**
163 | * Returns public key point from the given private key.
164 | */
165 | private static ECPoint publicPointFromPrivate(BigInteger privKey)
166 | {
167 | /*
168 | * TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group
169 | * order, but that could change in future versions.
170 | */
171 | if (privKey.bitLength() > CURVE.getN().bitLength()) {
172 | privKey = privKey.mod(CURVE.getN());
173 | }
174 | return new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey);
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/enums/MessageMethod.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.enums;
2 |
3 | /**
4 | * Message methods enum
5 | *
6 | * @author yangjian
7 | */
8 | public enum MessageMethod {
9 |
10 | // transfer coins
11 | SEND("MethodSend", 0);
12 |
13 | private final String key;
14 | private final int value;
15 |
16 | MessageMethod(String key, int value)
17 | {
18 | this.key = key;
19 | this.value = value;
20 | }
21 |
22 | public String getKey()
23 | {
24 | return key;
25 | }
26 |
27 | public int getValue()
28 | {
29 | return value;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/enums/MessageStatus.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.enums;
2 |
3 | /**
4 | * 交易状态枚举
5 | *
6 | * @author yangjian
7 | * @since 18-4-16
8 | */
9 | public enum MessageStatus {
10 |
11 | // 交易已确认,交易成功
12 | SUCCESS("Success", 0),
13 | // 交易待确认
14 | APPENDING("Appending", 1),
15 | INVALID_SIGN("Invalid signature", 2),
16 | INSUFFICIENT_BALANCE("Insufficient balance", 2),
17 | FAIL("Invalid Message", -1);
18 |
19 | private final String key;
20 | private final int value;
21 |
22 | MessageStatus(String key, int value)
23 | {
24 | this.key = key;
25 | this.value = value;
26 | }
27 |
28 | public String getKey()
29 | {
30 | return key;
31 | }
32 |
33 | public int getValue()
34 | {
35 | return value;
36 | }
37 |
38 | public boolean equals(MessageStatus other)
39 | {
40 | return value == other.value;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/exceptions/CipherException.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.exceptions;
2 |
3 | /**
4 | * Cipher exception wrapper.
5 | */
6 | public class CipherException extends Exception {
7 |
8 | public CipherException(String message)
9 | {
10 | super(message);
11 | }
12 |
13 | public CipherException(Throwable cause)
14 | {
15 | super(cause);
16 | }
17 |
18 | public CipherException(String message, Throwable cause)
19 | {
20 | super(message, cause);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/exceptions/MessageDecodingException.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.exceptions;
2 |
3 | /**
4 | * Encoding exception.
5 | */
6 | public class MessageDecodingException extends RuntimeException {
7 | public MessageDecodingException(String message)
8 | {
9 | super(message);
10 | }
11 |
12 | public MessageDecodingException(String message, Throwable cause)
13 | {
14 | super(message, cause);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/exceptions/MessageEncodingException.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.exceptions;
2 |
3 | /**
4 | * Encoding exception.
5 | */
6 | public class MessageEncodingException extends RuntimeException {
7 | public MessageEncodingException(String message)
8 | {
9 | super(message);
10 | }
11 |
12 | public MessageEncodingException(String message, Throwable cause)
13 | {
14 | super(message, cause);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/Account.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import java.io.Serializable;
4 | import java.math.BigDecimal;
5 |
6 | /**
7 | * wallet account
8 | *
9 | * @author yangjian
10 | */
11 | public class Account implements Serializable {
12 |
13 | private String address;
14 | private BigDecimal balance;
15 | private String pubKey;
16 | private long messageNonce;
17 |
18 | public Account(String address, BigDecimal balance, String pubKey, long messageNonce)
19 | {
20 | this.address = address;
21 | this.balance = balance;
22 | this.pubKey = pubKey;
23 | this.messageNonce = messageNonce;
24 | }
25 |
26 | public Account()
27 | {
28 | }
29 |
30 | public String getAddress()
31 | {
32 | return address;
33 | }
34 |
35 | public void setAddress(String address)
36 | {
37 | this.address = address;
38 | }
39 |
40 | public BigDecimal getBalance()
41 | {
42 | return balance;
43 | }
44 |
45 | public void setBalance(BigDecimal balance)
46 | {
47 | this.balance = balance;
48 | }
49 |
50 | public String getPubKey()
51 | {
52 | return pubKey;
53 | }
54 |
55 | public void setPubKey(String pubKey)
56 | {
57 | this.pubKey = pubKey;
58 | }
59 |
60 | public long getMessageNonce()
61 | {
62 | return messageNonce;
63 | }
64 |
65 | public void setMessageNonce(long messageNonce)
66 | {
67 | this.messageNonce = messageNonce;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/Block.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import org.rockyang.blockj.base.crypto.Hash;
4 |
5 | import java.io.Serializable;
6 | import java.util.List;
7 | import java.util.concurrent.CopyOnWriteArrayList;
8 |
9 | /**
10 | * Block
11 | *
12 | * @author yangjian
13 | */
14 | public class Block implements Serializable
15 | {
16 |
17 | private BlockHeader header;
18 | // block message
19 | private List messages;
20 | // public key of miner
21 | private String pubKey;
22 | // signature for current block
23 | private String blockSign;
24 |
25 | public Block(List messages)
26 | {
27 | this.messages = messages;
28 | }
29 |
30 | public Block(BlockHeader blockHeader)
31 | {
32 | this.header = blockHeader;
33 | this.messages = new CopyOnWriteArrayList<>();
34 | }
35 |
36 | public Block()
37 | {
38 | }
39 |
40 | public List getMessages()
41 | {
42 | return messages;
43 | }
44 |
45 | public void addMessage(Message message)
46 | {
47 | messages.add(message);
48 | }
49 |
50 | public void setMessages(List messages)
51 | {
52 | this.messages = messages;
53 | }
54 |
55 | public String getBlockSign()
56 | {
57 | return blockSign;
58 | }
59 |
60 | public void setBlockSign(String blockSign)
61 | {
62 | this.blockSign = blockSign;
63 | }
64 |
65 | public BlockHeader getHeader()
66 | {
67 | return header;
68 | }
69 |
70 | public void setHeader(BlockHeader header)
71 | {
72 | this.header = header;
73 | }
74 |
75 | public String getPubKey()
76 | {
77 | return pubKey;
78 | }
79 |
80 | public void setPubKey(String pubKey)
81 | {
82 | this.pubKey = pubKey;
83 | }
84 |
85 | private String buildMessages()
86 | {
87 | StringBuilder builder = new StringBuilder();
88 | messages.forEach(message -> {
89 | builder.append(message.getCid());
90 | });
91 | return builder.toString();
92 | }
93 |
94 | // generate block content id
95 | public String genCid()
96 | {
97 | return Hash.sha3(header.genCid());
98 | }
99 |
100 | @Override
101 | public String toString()
102 | {
103 | return "Block{" +
104 | "header=" + header +
105 | ", messages=" + messages +
106 | ", blockSign='" + blockSign + '\'' +
107 | '}';
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/BlockHeader.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import org.rockyang.blockj.base.crypto.Hash;
4 |
5 | import java.io.Serializable;
6 | import java.math.BigInteger;
7 |
8 | /**
9 | * Block header
10 | *
11 | * @author yangjian
12 | */
13 | public class BlockHeader implements Serializable {
14 |
15 | private long height;
16 | // pow difficulty
17 | private BigInteger difficulty;
18 | // answer of PoW
19 | private long nonce;
20 | // block created time
21 | private long createTime;
22 | // block timestamp
23 | private long timestamp;
24 | // current block hash value
25 | private String hash;
26 | // previous block hash value
27 | private String previousHash;
28 |
29 | public BlockHeader()
30 | {
31 | }
32 |
33 | public BlockHeader(long height, String previousHash)
34 | {
35 | this.height = height;
36 | this.previousHash = previousHash;
37 | this.createTime = System.currentTimeMillis() / 1000;
38 | }
39 |
40 | public long getHeight()
41 | {
42 | return height;
43 | }
44 |
45 | public void setHeight(long height)
46 | {
47 | this.height = height;
48 | }
49 |
50 | public BigInteger getDifficulty()
51 | {
52 | return difficulty;
53 | }
54 |
55 | public void setDifficulty(BigInteger difficulty)
56 | {
57 | this.difficulty = difficulty;
58 | }
59 |
60 | public long getNonce()
61 | {
62 | return nonce;
63 | }
64 |
65 | public void setNonce(long nonce)
66 | {
67 | this.nonce = nonce;
68 | }
69 |
70 | public long getTimestamp()
71 | {
72 | return timestamp;
73 | }
74 |
75 | public void setTimestamp(long timestamp)
76 | {
77 | this.timestamp = timestamp;
78 | }
79 |
80 | public String getHash()
81 | {
82 | return hash;
83 | }
84 |
85 | public void setHash(String hash)
86 | {
87 | this.hash = hash;
88 | }
89 |
90 | public String getPreviousHash()
91 | {
92 | return previousHash;
93 | }
94 |
95 | public void setPreviousHash(String previousHash)
96 | {
97 | this.previousHash = previousHash;
98 | }
99 |
100 | public long getCreateTime()
101 | {
102 | return createTime;
103 | }
104 |
105 | public void setCreateTime(long createTime)
106 | {
107 | this.createTime = createTime;
108 | }
109 |
110 | @Override
111 | public String toString()
112 | {
113 | // we should ignore signature and timestamp
114 | return "BlockHeader{" +
115 | "height=" + height +
116 | ", difficulty=" + difficulty +
117 | ", nonce=" + nonce +
118 | ", createTime=" + createTime +
119 | ", hash='" + hash + '\'' +
120 | ", previousHash='" + previousHash + '\'' +
121 | '}';
122 | }
123 |
124 | public String genCid()
125 | {
126 | return Hash.sha3("BlockHeader{" +
127 | "height=" + height +
128 | ", difficulty=" + difficulty +
129 | ", nonce=" + nonce +
130 | ", createTime=" + createTime +
131 | ", previousHash='" + previousHash + '\'' +
132 | '}');
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/Message.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import org.rockyang.blockj.base.crypto.Hash;
4 | import org.rockyang.blockj.base.enums.MessageStatus;
5 |
6 | import java.math.BigDecimal;
7 |
8 | /**
9 | * block message
10 | *
11 | * @author yangjian
12 | */
13 | public class Message {
14 | public static final int MSG_VERSION = 1;
15 | private int version;
16 | private String from;
17 | private String to;
18 | private BigDecimal value;
19 | private Long timestamp;
20 | private String pubKey;
21 | // message CID
22 | private String cid;
23 | // massage status
24 | private MessageStatus status = MessageStatus.APPENDING;
25 | private long nonce;
26 | // message parameters
27 | private String params;
28 | // block height
29 | private long height;
30 | private String sign;
31 |
32 | public Message(String from, String to, BigDecimal value, long nonce)
33 | {
34 | this.from = from;
35 | this.to = to;
36 | this.value = value;
37 | this.timestamp = System.currentTimeMillis();
38 | this.version = MSG_VERSION;
39 | this.nonce = nonce;
40 | }
41 |
42 | public Message()
43 | {
44 | this.timestamp = System.currentTimeMillis();
45 | }
46 |
47 | public int getVersion()
48 | {
49 | return version;
50 | }
51 |
52 | public void setVersion(int version)
53 | {
54 | this.version = version;
55 | }
56 |
57 | public String getFrom()
58 | {
59 | return from;
60 | }
61 |
62 | public void setFrom(String from)
63 | {
64 | this.from = from;
65 | }
66 |
67 | public String getTo()
68 | {
69 | return to;
70 | }
71 |
72 | public void setTo(String to)
73 | {
74 | this.to = to;
75 | }
76 |
77 | public BigDecimal getValue()
78 | {
79 | return value;
80 | }
81 |
82 | public void setValue(BigDecimal value)
83 | {
84 | this.value = value;
85 | }
86 |
87 | public Long getTimestamp()
88 | {
89 | return timestamp;
90 | }
91 |
92 | public void setTimestamp(Long timestamp)
93 | {
94 | this.timestamp = timestamp;
95 | }
96 |
97 | public String getPubKey()
98 | {
99 | return pubKey;
100 | }
101 |
102 | public void setPubKey(String pubKey)
103 | {
104 | this.pubKey = pubKey;
105 | }
106 |
107 | public String getCid()
108 | {
109 | return cid;
110 | }
111 |
112 | public void setCid(String cid)
113 | {
114 | this.cid = cid;
115 | }
116 |
117 | public MessageStatus getStatus()
118 | {
119 | return status;
120 | }
121 |
122 | public void setStatus(MessageStatus status)
123 | {
124 | this.status = status;
125 | }
126 |
127 | public long getNonce()
128 | {
129 | return nonce;
130 | }
131 |
132 | public void setNonce(long nonce)
133 | {
134 | this.nonce = nonce;
135 | }
136 |
137 | public String getParams()
138 | {
139 | return params;
140 | }
141 |
142 | public void setParams(String params)
143 | {
144 | this.params = params;
145 | }
146 |
147 | public long getHeight()
148 | {
149 | return height;
150 | }
151 |
152 | public void setHeight(long height)
153 | {
154 | this.height = height;
155 | }
156 |
157 | public String getSign()
158 | {
159 | return sign;
160 | }
161 |
162 | public void setSign(String sign)
163 | {
164 | this.sign = sign;
165 | }
166 |
167 | public String toSigned()
168 | {
169 | return "Message{" +
170 | "from='" + from + '\'' +
171 | ", to='" + to + '\'' +
172 | ", value=" + value +
173 | ", timestamp=" + timestamp +
174 | ", pubKey='" + pubKey + '\'' +
175 | ", nonce=" + nonce +
176 | ", params='" + params + '\'' +
177 | '}';
178 | }
179 |
180 | public String genMsgCid()
181 | {
182 | return Hash.sha3(this.toSigned());
183 | }
184 |
185 | @Override
186 | public String toString()
187 | {
188 | return "Message{" +
189 | "version=" + version +
190 | ", from='" + from + '\'' +
191 | ", to='" + to + '\'' +
192 | ", value=" + value +
193 | ", timestamp=" + timestamp +
194 | ", pubKey='" + pubKey + '\'' +
195 | ", cid='" + cid + '\'' +
196 | ", status=" + status +
197 | ", nonce=" + nonce +
198 | ", params='" + params + '\'' +
199 | ", height=" + height +
200 | ", sign='" + sign + '\'' +
201 | '}';
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/Peer.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import org.rockyang.blockj.base.crypto.Hash;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public class Peer implements Serializable {
11 |
12 | private String id;
13 | private String ip;
14 | private int port;
15 |
16 | public Peer(String ip, int port)
17 | {
18 | this.ip = ip;
19 | this.port = port;
20 | this.id = Hash.sha3(this.toString());
21 | }
22 |
23 | public Peer()
24 | {
25 | this(null, 0);
26 | }
27 |
28 | public String getId()
29 | {
30 | return id;
31 | }
32 |
33 | public void setId(String id)
34 | {
35 | this.id = id;
36 | }
37 |
38 | public String getIp()
39 | {
40 | return ip;
41 | }
42 |
43 | public void setIp(String ip)
44 | {
45 | this.ip = ip;
46 | }
47 |
48 | public int getPort()
49 | {
50 | return port;
51 | }
52 |
53 | public void setPort(int port)
54 | {
55 | this.port = port;
56 | }
57 |
58 | @Override
59 | public String toString()
60 | {
61 | return String.format("%s:%s", getIp(), getPort());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/model/Wallet.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.model;
2 |
3 | import org.rockyang.blockj.base.crypto.ECKeyPair;
4 | import org.rockyang.blockj.base.crypto.Keys;
5 |
6 | import java.io.Serializable;
7 | import java.math.BigDecimal;
8 | import java.security.InvalidAlgorithmParameterException;
9 | import java.security.NoSuchAlgorithmException;
10 | import java.security.NoSuchProviderException;
11 |
12 | /**
13 | * wallet entity
14 | *
15 | * @author yangjian
16 | */
17 | public class Wallet implements Serializable
18 | {
19 |
20 | private String address;
21 | private String pubKey;
22 | private String priKey;
23 | private BigDecimal balance;
24 | private long messageNonce;
25 |
26 | public Wallet() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException
27 | {
28 | this(Keys.createEcKeyPair());
29 | }
30 |
31 | public Wallet(ECKeyPair keyPair)
32 | {
33 | this.priKey = keyPair.exportPrivateKey();
34 | this.pubKey = Keys.publicKeyEncode(keyPair.getPublicKey().getEncoded());
35 | this.address = keyPair.getAddress();
36 | this.balance = BigDecimal.ZERO;
37 | this.messageNonce = 0;
38 | }
39 |
40 | public Wallet(String address, BigDecimal balance)
41 | {
42 | this.address = address;
43 | this.balance = balance;
44 | }
45 |
46 | public String getAddress()
47 | {
48 | return address;
49 | }
50 |
51 | public void setAddress(String address)
52 | {
53 | this.address = address;
54 | }
55 |
56 | public String getPubKey()
57 | {
58 | return pubKey;
59 | }
60 |
61 | public void setPubKey(String pubKey)
62 | {
63 | this.pubKey = pubKey;
64 | }
65 |
66 | public String getPriKey()
67 | {
68 | return priKey;
69 | }
70 |
71 | public void setPriKey(String priKey)
72 | {
73 | this.priKey = priKey;
74 | }
75 |
76 | public BigDecimal getBalance()
77 | {
78 | return balance;
79 | }
80 |
81 | public void setBalance(BigDecimal balance)
82 | {
83 | this.balance = balance;
84 | }
85 |
86 | public long getMessageNonce()
87 | {
88 | return messageNonce;
89 | }
90 |
91 | public void setMessageNonce(long messageNonce)
92 | {
93 | this.messageNonce = messageNonce;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/store/Datastore.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.store;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 |
6 | /**
7 | * 数据库操作接口
8 | *
9 | * @author yangjian
10 | * @since 18-4-10
11 | */
12 | public interface Datastore {
13 |
14 | // save an item into database
15 | boolean put(String key, Object value);
16 |
17 | // get an item from database with the specified key
18 | Optional get(String key);
19 |
20 | // delete an item from database with the specified key
21 | boolean delete(String key);
22 |
23 | // search in database with key prefix
24 | List search(String keyPrefix);
25 |
26 | // close the database
27 | void close();
28 | }
29 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/store/RocksDatastore.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.store;
2 |
3 | import org.rocksdb.*;
4 | import org.rockyang.blockj.base.utils.SerializeUtils;
5 |
6 | import java.io.File;
7 | import java.io.FileNotFoundException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.Optional;
11 |
12 | /**
13 | * RocksDB datastore wrapper
14 | *
15 | * @author yangjian
16 | */
17 | public class RocksDatastore implements Datastore
18 | {
19 |
20 | private RocksDB datastore;
21 |
22 | public RocksDatastore(String repo)
23 | {
24 | // load base dir from
25 | String dataPath = String.format("%s/datastore", repo);
26 | try {
27 | File directory = new File(dataPath);
28 | if (!directory.exists() && !directory.mkdirs()) {
29 | throw new FileNotFoundException(dataPath);
30 | }
31 | datastore = RocksDB.open(new Options().setCreateIfMissing(true), dataPath);
32 | } catch (RocksDBException | FileNotFoundException e) {
33 | //e.printStackTrace();
34 | }
35 | }
36 |
37 | @Override
38 | public boolean put(String key, Object value)
39 | {
40 | try {
41 | datastore.put(key.getBytes(), SerializeUtils.serialize(value));
42 | return true;
43 | } catch (Exception e) {
44 | e.printStackTrace();
45 | return false;
46 | }
47 | }
48 |
49 | @Override
50 | public Optional get(String key)
51 | {
52 | try {
53 | return Optional.of(SerializeUtils.unSerialize(datastore.get(key.getBytes())));
54 | } catch (Exception e) {
55 | //e.printStackTrace();
56 | return Optional.empty();
57 | }
58 | }
59 |
60 | @Override
61 | public boolean delete(String key)
62 | {
63 | try {
64 | datastore.delete(key.getBytes());
65 | return true;
66 | } catch (Exception e) {
67 | //e.printStackTrace();
68 | return false;
69 | }
70 | }
71 |
72 | @Override
73 | @SuppressWarnings("unchecked")
74 | public List search(String keyPrefix)
75 | {
76 | ArrayList list = new ArrayList<>();
77 | RocksIterator iterator = datastore.newIterator(new ReadOptions());
78 | for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
79 | String key = new String(iterator.key());
80 | if (!key.startsWith(keyPrefix)) {
81 | continue;
82 | }
83 | list.add((T) SerializeUtils.unSerialize(iterator.value()));
84 | }
85 | return list;
86 | }
87 |
88 | @Override
89 | public void close()
90 | {
91 | if (datastore != null) {
92 | datastore.close();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/utils/ByteUtils.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.utils;
2 |
3 | import org.apache.commons.lang3.ArrayUtils;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.util.Arrays;
7 | import java.util.stream.Stream;
8 |
9 | /**
10 | * 数组工具
11 | *
12 | * @author yangjian
13 | * @since 18-4-9
14 | */
15 | public class ByteUtils {
16 |
17 | /**
18 | * 两个byte[]数组相加
19 | *
20 | * @param data1
21 | * @param data2
22 | * @return
23 | */
24 | public static byte[] add(byte[] data1, byte[] data2)
25 | {
26 |
27 | byte[] result = new byte[data1.length + data2.length];
28 | System.arraycopy(data1, 0, result, 0, data1.length);
29 | System.arraycopy(data2, 0, result, data1.length, data2.length);
30 |
31 | return result;
32 | }
33 |
34 | /**
35 | * 将多个字节数组合并成一个字节数组
36 | *
37 | * @param bytes
38 | * @return
39 | */
40 | public static byte[] merge(byte[]... bytes)
41 | {
42 | Stream stream = Stream.of();
43 | for (byte[] b : bytes) {
44 | stream = Stream.concat(stream, Arrays.stream(ArrayUtils.toObject(b)));
45 | }
46 | return ArrayUtils.toPrimitive(stream.toArray(Byte[]::new));
47 | }
48 |
49 | /**
50 | * long 类型转 byte[]
51 | *
52 | * @param val
53 | * @return
54 | */
55 | public static byte[] toBytes(long val)
56 | {
57 | return ByteBuffer.allocate(Long.BYTES).putLong(val).array();
58 | }
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/utils/CmdArgsParser.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.utils;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /**
11 | * cli command arguments parser
12 | *
13 | * @author yangjian
14 | */
15 | public class CmdArgsParser
16 | {
17 |
18 | private final List args;
19 | private final Map options;
20 | private static volatile CmdArgsParser instance;
21 |
22 | private CmdArgsParser(String[] args)
23 | {
24 | this.args = new ArrayList<>();
25 | this.options = new HashMap<>(16);
26 | parse(args);
27 | }
28 |
29 | public static synchronized CmdArgsParser getInstance(String[] args)
30 | {
31 | if (instance == null) {
32 | instance = new CmdArgsParser(args);
33 | }
34 | return instance;
35 | }
36 |
37 | public void parse(String[] args)
38 | {
39 | for (String item : args) {
40 | if (StringUtils.isEmpty(item)) {
41 | continue;
42 | }
43 | if (item.indexOf('-') == -1) {
44 | this.args.add(item);
45 | continue;
46 | }
47 | String[] split = StringUtils.split(item, '=');
48 | if (split.length == 2) {
49 | options.put(optionKey(split[0]), split[1]);
50 | } else {
51 | options.put(optionKey(split[0]), "");
52 | }
53 |
54 | }
55 | }
56 |
57 | public String optionKey(String s)
58 | {
59 | int i = 0;
60 | while (i < s.length()) {
61 | if (s.charAt(i) != '-' && !Character.isWhitespace(s.charAt(i))) {
62 | break;
63 | }
64 | i++;
65 | }
66 | return s.substring(i);
67 | }
68 |
69 | public List getArgs()
70 | {
71 | return args;
72 | }
73 |
74 | public String getOption(String key)
75 | {
76 | return options.get(key);
77 | }
78 |
79 | public String getOption(String key, String defaultValue)
80 | {
81 | return options.get(key) == null ? defaultValue : options.get(key);
82 | }
83 |
84 | public Integer getIntOption(String key)
85 | {
86 | String s = getOption(key);
87 | return Integer.parseInt(s);
88 | }
89 |
90 | public Integer getIntOption(String key, Integer defaultValue)
91 | {
92 | String s = getOption(key);
93 | if (s == null) {
94 | return defaultValue;
95 | }
96 | return Integer.parseInt(s);
97 | }
98 |
99 | public Boolean getBoolOption(String key)
100 | {
101 | String s = getOption(key);
102 | return Boolean.parseBoolean(s);
103 | }
104 |
105 | public Boolean getBoolOption(String key, boolean defaultValue)
106 | {
107 | String s = getOption(key);
108 | if (s == null) {
109 | return defaultValue;
110 | }
111 | return Boolean.parseBoolean(s);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/utils/SerializeUtils.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.utils;
2 |
3 | import com.esotericsoftware.kryo.Kryo;
4 | import com.esotericsoftware.kryo.io.Input;
5 | import com.esotericsoftware.kryo.io.Output;
6 |
7 | /**
8 | * Object serialize utils implement with Kryo
9 | */
10 | public class SerializeUtils {
11 |
12 | /**
13 | * 反序列化
14 | *
15 | * @param bytes 对象对应的字节数组
16 | * @return
17 | */
18 | public static Object unSerialize(byte[] bytes)
19 | {
20 | Input input = new Input(bytes);
21 | Object obj = new Kryo().readClassAndObject(input);
22 | input.close();
23 | return obj;
24 | }
25 |
26 | /**
27 | * 序列化
28 | *
29 | * @param object 需要序列化的对象
30 | * @return
31 | */
32 | public static byte[] serialize(Object object)
33 | {
34 | Output output = new Output(4096, -1);
35 | new Kryo().writeClassAndObject(output, object);
36 | byte[] bytes = output.toBytes();
37 | output.close();
38 | return bytes;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/utils/Strings.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.utils;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * String utility functions.
7 | */
8 | public class Strings {
9 |
10 | private Strings()
11 | {
12 | }
13 |
14 | public static String join(List src, String delimiter)
15 | {
16 | return src == null ? null : String.join(delimiter, src.toArray(new String[0]));
17 | }
18 |
19 | public static String capitaliseFirstLetter(String string)
20 | {
21 | if (string == null || string.length() == 0) {
22 | return string;
23 | } else {
24 | return string.substring(0, 1).toUpperCase() + string.substring(1);
25 | }
26 | }
27 |
28 | public static String lowercaseFirstLetter(String string)
29 | {
30 | if (string == null || string.length() == 0) {
31 | return string;
32 | } else {
33 | return string.substring(0, 1).toLowerCase() + string.substring(1);
34 | }
35 | }
36 |
37 | public static String zeros(int n)
38 | {
39 | return repeat('0', n);
40 | }
41 |
42 | public static String repeat(char value, int n)
43 | {
44 | return new String(new char[n]).replace("\0", String.valueOf(value));
45 | }
46 |
47 | public static boolean isEmpty(String s)
48 | {
49 | return s == null || s.length() == 0;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/utils/ThreadUtils.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.utils;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | public class ThreadUtils {
10 |
11 | private static final Logger logger = LoggerFactory.getLogger(ThreadUtils.class);
12 |
13 | public static void niceSleep(int secs)
14 | {
15 | try {
16 | Thread.sleep(secs * 1000L);
17 | } catch (InterruptedException e) {
18 | logger.warn("received interrupt while trying to sleep in mining cycle");
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/vo/JsonVo.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.vo;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 |
5 | /**
6 | * 返回 Json 字符串 VO
7 | *
8 | * @author yangjian
9 | */
10 | @JsonIgnoreProperties(ignoreUnknown = true)
11 | public class JsonVo
12 | {
13 |
14 | public static final int SUCCESS = 0;
15 | public static final int FAIL = 1;
16 | // version
17 | private int version = 1;
18 | // status code
19 | private int code;
20 | // error message
21 | private String message;
22 | private T data;
23 |
24 | public JsonVo()
25 | {
26 | }
27 |
28 | public JsonVo(int code, T data)
29 | {
30 | this.code = code;
31 | this.data = data;
32 | }
33 |
34 | public JsonVo(int code, String message, T data)
35 | {
36 | this.code = code;
37 | this.message = message;
38 | this.data = data;
39 | }
40 |
41 | public JsonVo(int code, String message)
42 | {
43 | this.code = code;
44 | this.message = message;
45 | }
46 |
47 | public int getCode()
48 | {
49 | return code;
50 | }
51 |
52 | public void setCode(int code)
53 | {
54 | this.code = code;
55 | }
56 |
57 | public String getMessage()
58 | {
59 | return message;
60 | }
61 |
62 | public JsonVo setMessage(String message)
63 | {
64 | this.message = message;
65 | return this;
66 | }
67 |
68 | public int getVersion()
69 | {
70 | return version;
71 | }
72 |
73 | public JsonVo setVersion(int version)
74 | {
75 | this.version = version;
76 | return this;
77 | }
78 |
79 | public T getData()
80 | {
81 | return data;
82 | }
83 |
84 | public JsonVo setData(T data)
85 | {
86 | this.data = data;
87 | return this;
88 | }
89 |
90 | public boolean isOK()
91 | {
92 | return code == SUCCESS;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/blockj-base/src/main/java/org/rockyang/blockj/base/vo/MnemonicWallet.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.vo;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 | import org.rockyang.blockj.base.model.Wallet;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * @author yangjian
10 | **/
11 | @JsonIgnoreProperties(ignoreUnknown = true)
12 | public class MnemonicWallet implements Serializable
13 | {
14 | private String mnemonic;
15 | private Wallet wallet;
16 |
17 | public MnemonicWallet()
18 | {
19 | }
20 |
21 | public MnemonicWallet(String mnemonic, Wallet wallet)
22 | {
23 | this.mnemonic = mnemonic;
24 | this.wallet = wallet;
25 | }
26 |
27 | public String getMnemonic()
28 | {
29 | return mnemonic;
30 | }
31 |
32 | public Wallet getWallet()
33 | {
34 | return wallet;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/blockj-base/src/test/java/org/rockyang/blockj/base/store/RocksDbTest.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.base.store;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.Optional;
6 | import java.util.UUID;
7 |
8 | /**
9 | * @author yangjian
10 | */
11 | public class RocksDbTest {
12 |
13 | static final String KEY = "test-data";
14 | private static final Datastore datastore = new RocksDatastore("/tmp/rocksdb");
15 |
16 | @Test
17 | public void put()
18 | {
19 | //put data
20 | String data = UUID.randomUUID().toString();
21 | datastore.put(KEY, data);
22 | //get data by key
23 | Optional o = datastore.get(KEY);
24 | if (o.isPresent()) {
25 | String s = (String) o.get();
26 | System.out.println(s);
27 | ;
28 | assert data.equals(s);
29 | }
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/blockj-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.rockyang
8 | blockj
9 | 2.0.0
10 |
11 |
12 | blockj-client
13 | 2.0.0
14 | jar
15 |
16 | blockj-client
17 | block chain client command implementation
18 |
19 |
20 | 2.9.0
21 | 2.5.0
22 | 3.12.0
23 | 3.8.1
24 | 1.2.17
25 | 4.13.1
26 |
27 |
28 |
29 |
30 |
31 | com.squareup.okhttp3
32 | okhttp
33 | ${okhttp.version}
34 |
35 |
36 | com.squareup.okhttp3
37 | logging-interceptor
38 | ${okhttp.version}
39 |
40 |
41 | com.squareup.retrofit2
42 | converter-scalars
43 | ${converter-scalars.version}
44 |
45 |
46 | com.squareup.retrofit2
47 | retrofit
48 | ${retrofit2.version}
49 |
50 |
51 |
52 | com.squareup.retrofit2
53 | converter-jackson
54 | ${retrofit2.version}
55 |
56 |
57 | org.apache.commons
58 | commons-lang3
59 | ${common-lang.version}
60 |
61 |
62 |
63 | com.alibaba
64 | fastjson
65 | 1.2.57
66 |
67 |
68 |
69 | org.rockyang
70 | blockj-base
71 | 2.0.0
72 |
73 |
74 |
75 | log4j
76 | log4j
77 | ${log4j.version}
78 | test
79 |
80 |
81 |
82 | junit
83 | junit
84 | ${junit.version}
85 | test
86 |
87 |
88 |
89 |
90 |
91 |
92 | maven-assembly-plugin
93 |
94 |
95 | jar-with-dependencies
96 |
97 |
98 |
99 |
100 |
101 |
102 | org.rockyang.blockj.client.BlockClient
103 |
104 |
105 |
106 |
107 |
108 | make-assembly
109 | package
110 |
111 | single
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/BlockClient.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client;
2 |
3 | import org.rockyang.blockj.base.utils.CmdArgsParser;
4 | import org.rockyang.blockj.client.cmd.Chain;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.Net;
7 | import org.rockyang.blockj.client.cmd.Wallet;
8 | import org.rockyang.blockj.client.cmd.utils.Printer;
9 | import org.rockyang.blockj.client.rpc.BlockService;
10 | import org.rockyang.blockj.client.rpc.impl.BlockServiceImpl;
11 |
12 | import java.util.Arrays;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | /**
17 | * client application main class
18 | *
19 | * @author yangjian
20 | */
21 | public class BlockClient
22 | {
23 |
24 | private static final Map commands = new HashMap<>(16);
25 | public static final String VERSION = "1.0.0";
26 | public static final String ROOT_CMD = "./client ";
27 |
28 | public static void main(String[] argsOri)
29 | {
30 | // init BlockService
31 | CmdArgsParser parser = CmdArgsParser.getInstance(argsOri);
32 | String api = parser.getOption("api", "http://127.0.0.1:8001");
33 | Boolean debug = parser.getBoolOption("debug", false);
34 | // BlockServiceMock blockService = new BlockServiceMock();
35 | BlockService blockService = new BlockServiceImpl(api, debug);
36 | addCommand(new Chain(blockService));
37 | addCommand(new Wallet(blockService));
38 | addCommand(new Net(blockService));
39 | String[] args = parser.getArgs().toArray(new String[0]);
40 |
41 | if (args.length == 0 || !commands.containsKey(args[0])) {
42 | showHelp();
43 | return;
44 | }
45 |
46 | // extract args
47 | int index = 0;
48 | for (int i = 0; i < args.length; i++) {
49 | if (!commands.containsKey(args[i])) {
50 | index = i;
51 | break;
52 | }
53 | }
54 |
55 | String[] subArgs = Arrays.copyOfRange(args, index, args.length);
56 | // Arrays.stream(preArgs).forEach(System.out::println);
57 | commands.get(args[0]).init(subArgs);
58 | }
59 |
60 | private static void showHelp()
61 | {
62 | System.out.println("NAME:");
63 | Printer.printTabLine("%s\n\n", "./client - blockchain network client");
64 | System.out.println("USAGE:");
65 | Printer.printTabLine("%s\n\n", "./client command [command options] [arguments...]");
66 | System.out.println("VERSION:");
67 | Printer.printTabLine("%s\n\n", VERSION);
68 | System.out.println("COMMANDS:");
69 | commands.forEach((key, command) -> Printer.printTabLine("%-10s %s\n", command.getName(), command.getUsage()));
70 | System.out.println();
71 | System.out.println("OPTIONS:");
72 | Printer.printTabLine("%-10s %s\n", "--api", "blockchain backend api url (default: http://127.0.0.1:8001)");
73 | Printer.printTabLine("%-10s %s\n", "--version", "print the version (default: false)");
74 | Printer.printTabLine("%-10s %s\n", "--help", "show help (default: false)");
75 | }
76 |
77 | public static void addCommand(Command command)
78 | {
79 | commands.put(command.getName(), command);
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/Chain.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd;
2 |
3 | import org.rockyang.blockj.client.cmd.chain.ChainBlock;
4 | import org.rockyang.blockj.client.cmd.chain.ChainHead;
5 | import org.rockyang.blockj.client.cmd.chain.ChainMessage;
6 | import org.rockyang.blockj.client.cmd.chain.SendMessage;
7 | import org.rockyang.blockj.client.cmd.utils.CliContext;
8 | import org.rockyang.blockj.client.rpc.BlockService;
9 |
10 | /**
11 | * @author yangjian
12 | */
13 | public class Chain extends Command
14 | {
15 |
16 | public Chain(BlockService service)
17 | {
18 | this.name = "chain";
19 | this.fullName = "chain";
20 | this.usage = "Interact with blockchain";
21 | this.blockService = service;
22 | this.addCommand(new ChainHead(service));
23 | this.addCommand(new ChainBlock(service));
24 | this.addCommand(new ChainMessage(service));
25 | this.addCommand(new SendMessage(service));
26 | }
27 |
28 | @Override
29 | public void action(CliContext context)
30 | {
31 | throw new RuntimeException("not implemented");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/Command.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.client.BlockClient;
5 | import org.rockyang.blockj.client.cmd.utils.CliContext;
6 | import org.rockyang.blockj.client.cmd.utils.Printer;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | import java.util.Arrays;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | /**
14 | * Command line abstract class
15 | *
16 | * @author yangjian
17 | */
18 | public abstract class Command
19 | {
20 |
21 | // command
22 | protected String name;
23 | protected String fullName;
24 | // command description
25 | protected String usage;
26 | // command usage
27 | protected String argsUsage;
28 | // command options
29 | protected Map options = new HashMap<>();
30 | protected Map subCommands = new HashMap<>(8);
31 | protected BlockService blockService = null;
32 |
33 | public void init(String[] args)
34 | {
35 | if (args.length == 0) {
36 | return;
37 | }
38 | if (!subCommands.containsKey(args[0])) {
39 | showHelp();
40 | return;
41 | }
42 |
43 | // call the command
44 | Command cmd = subCommands.get(args[0]);
45 | if (cmd.subCommands.size() == 0) {
46 | String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
47 | CliContext context = new CliContext(subArgs);
48 | if (context.getOption("help") != null) {
49 | cmd.showHelp();
50 | return;
51 | }
52 |
53 | if (context.getOption("version") != null) {
54 | System.out.println(BlockClient.VERSION);
55 | return;
56 | }
57 |
58 | cmd.action(context);
59 | return;
60 | }
61 |
62 | // go into the subcommands
63 | String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
64 | cmd.init(subArgs);
65 | }
66 |
67 | abstract public void action(CliContext context);
68 |
69 | public void showHelp()
70 | {
71 | System.out.println("NAME:");
72 | Printer.printTabLine("%s%s - %s\n\n", BlockClient.ROOT_CMD, fullName, usage);
73 | System.out.println("USAGE:");
74 | if (StringUtils.isBlank(argsUsage)) {
75 | Printer.printTabLine("%s%s [command options] [arguments...]\n\n", BlockClient.ROOT_CMD, fullName);
76 | } else {
77 | Printer.printTabLine("%s%s %s\n\n", BlockClient.ROOT_CMD, fullName, argsUsage);
78 | }
79 |
80 | if (subCommands.size() > 0) {
81 | System.out.println("COMMANDS:");
82 | subCommands.forEach((key, cmd) -> Printer.printTabLine("%-10s %s\n", key, cmd.getUsage()));
83 | System.out.println();
84 | }
85 |
86 | if (options.size() > 0) {
87 | System.out.println("OPTIONS:");
88 | for (Map.Entry entry : options.entrySet()) {
89 | Printer.printTabLine("%-10s %s\n", entry.getKey(), entry.getValue());
90 | }
91 | }
92 |
93 |
94 | }
95 |
96 | public String getName()
97 | {
98 | return name;
99 | }
100 |
101 | public String getArgsUsage()
102 | {
103 | return argsUsage;
104 | }
105 |
106 | public String getUsage()
107 | {
108 | return usage;
109 | }
110 |
111 | public void addCommand(Command command)
112 | {
113 | this.subCommands.put(command.getName(), command);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/Net.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd;
2 |
3 | import org.rockyang.blockj.client.cmd.net.NetConnect;
4 | import org.rockyang.blockj.client.cmd.net.NetListen;
5 | import org.rockyang.blockj.client.cmd.net.NetPeers;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | /**
10 | * @author yangjian
11 | */
12 | public class Net extends Command
13 | {
14 |
15 | public Net(BlockService service)
16 | {
17 | this.name = "net";
18 | this.fullName = "net";
19 | this.usage = "Manage P2P Network";
20 | this.blockService = service;
21 | this.addCommand(new NetPeers(service));
22 | this.addCommand(new NetListen(service));
23 | this.addCommand(new NetConnect(service));
24 | }
25 |
26 | @Override
27 | public void action(CliContext context)
28 | {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/Wallet.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd;
2 |
3 | import org.rockyang.blockj.client.cmd.utils.CliContext;
4 | import org.rockyang.blockj.client.cmd.wallet.WalletBalance;
5 | import org.rockyang.blockj.client.cmd.wallet.WalletList;
6 | import org.rockyang.blockj.client.cmd.wallet.WalletNew;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | /**
10 | * @author yangjian
11 | */
12 | public class Wallet extends Command
13 | {
14 |
15 | public Wallet(BlockService service)
16 | {
17 | this.name = "wallet";
18 | this.fullName = "wallet";
19 | this.usage = "Manage wallet";
20 | this.blockService = service;
21 | this.addCommand(new WalletNew(service));
22 | this.addCommand(new WalletList(service));
23 | this.addCommand(new WalletBalance(service));
24 | }
25 |
26 | @Override
27 | public void action(CliContext context)
28 | {
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/chain/ChainBlock.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.chain;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | /**
10 | * @author yangjian
11 | */
12 | public class ChainBlock extends Command
13 | {
14 |
15 | public ChainBlock(BlockService service)
16 | {
17 | this.name = "getBlock";
18 | this.fullName = "chain getBlock";
19 | this.usage = "Get a block and print its details";
20 | this.argsUsage = "[blockIndex]";
21 | this.blockService = service;
22 | }
23 |
24 | @Override
25 | public void action(CliContext context)
26 | {
27 | Long height = context.getLongArg(0);
28 | JsonVo res = blockService.getBlock(height);
29 | if (res.isOK()) {
30 | System.out.println(res.getData());
31 | } else {
32 | System.out.println(res.getMessage());
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/chain/ChainHead.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.chain;
2 |
3 | import org.rockyang.blockj.base.vo.JsonVo;
4 | import org.rockyang.blockj.client.cmd.Command;
5 | import org.rockyang.blockj.client.cmd.utils.CliContext;
6 | import org.rockyang.blockj.client.rpc.BlockService;
7 |
8 | /**
9 | * @author yangjian
10 | */
11 | public class ChainHead extends Command
12 | {
13 |
14 | public ChainHead(BlockService service)
15 | {
16 | this.name = "head";
17 | this.fullName = "chain head";
18 | this.argsUsage = "Print chain head";
19 | this.blockService = service;
20 | }
21 |
22 | @Override
23 | public void action(CliContext context)
24 | {
25 | JsonVo res = blockService.chainHead();
26 | if (res.isOK()) {
27 | System.out.printf("Chain head: %d%n", res.getData());
28 | } else {
29 | System.out.println(res.getMessage());
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/chain/ChainMessage.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.chain;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.vo.JsonVo;
6 | import org.rockyang.blockj.client.cmd.Command;
7 | import org.rockyang.blockj.client.cmd.utils.CliContext;
8 | import org.rockyang.blockj.client.rpc.BlockService;
9 |
10 | /**
11 | * @author yangjian
12 | */
13 | public class ChainMessage extends Command
14 | {
15 |
16 | public ChainMessage(BlockService service)
17 | {
18 | this.name = "getMessage";
19 | this.fullName = "chain getMessage";
20 | this.usage = "Get and print a message by its cid";
21 | this.argsUsage = "[MessageCid]";
22 | this.blockService = service;
23 | }
24 |
25 | @Override
26 | public void action(CliContext context)
27 | {
28 | String cid = context.getArg(0);
29 | if (StringUtils.isBlank(cid)) {
30 | System.out.println("Please input the message Cid.");
31 | }
32 |
33 | JsonVo res = blockService.getMessage(cid);
34 | if (res.isOK()) {
35 | System.out.println(res.getData());
36 | } else {
37 | System.out.println(res.getMessage());
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/chain/SendMessage.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.chain;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | import java.math.BigDecimal;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | public class SendMessage extends Command
15 | {
16 |
17 | public SendMessage(BlockService service)
18 | {
19 | this.name = "send";
20 | this.fullName = "chain send";
21 | this.usage = "Send funds between accounts";
22 | this.argsUsage = "[targetAddress] [amount]";
23 | // 添加选项说明
24 | this.options.put("--from", "optionally specify the account to send funds from");
25 | this.options.put("--param", "specify invocation parameters");
26 | this.blockService = service;
27 | }
28 |
29 | @Override
30 | public void action(CliContext context)
31 | {
32 | String from = context.getOption("from");
33 | String to = context.getArg(0);
34 | BigDecimal value = context.getBigDecimalArg(1);
35 | String param = context.getOption("param");
36 |
37 | if (StringUtils.isBlank(to)) {
38 | System.out.println("Must pass to address");
39 | return;
40 | }
41 |
42 | if (value == null || value.equals(BigDecimal.ZERO)) {
43 | System.out.println("Invalid send value");
44 | return;
45 | }
46 |
47 | JsonVo res = blockService.sendMessage(from, to, value, param);
48 | if (res.isOK()) {
49 | System.out.printf("Send message, CID: %s\n", res.getData());
50 | } else {
51 | System.out.println(res.getMessage());
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/net/NetConnect.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.net;
2 |
3 | import org.rockyang.blockj.client.cmd.Command;
4 | import org.rockyang.blockj.client.cmd.utils.CliContext;
5 | import org.rockyang.blockj.client.rpc.BlockService;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public class NetConnect extends Command
11 | {
12 |
13 | public NetConnect(BlockService service)
14 | {
15 | this.name = "connect";
16 | this.usage = "Connect to a peer";
17 | this.argsUsage = "[peerAddress]";
18 | this.blockService = service;
19 | }
20 |
21 | @Override
22 | public void action(CliContext context)
23 | {
24 | throw new RuntimeException("Not implemented");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/net/NetListen.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.net;
2 |
3 | import org.rockyang.blockj.base.vo.JsonVo;
4 | import org.rockyang.blockj.client.cmd.Command;
5 | import org.rockyang.blockj.client.cmd.utils.CliContext;
6 | import org.rockyang.blockj.client.rpc.BlockService;
7 |
8 | /**
9 | * @author yangjian
10 | */
11 | public class NetListen extends Command
12 | {
13 |
14 | public NetListen(BlockService service)
15 | {
16 | this.name = "listen";
17 | this.usage = "List listen addresses";
18 | this.blockService = service;
19 | }
20 |
21 | @Override
22 | public void action(CliContext context)
23 | {
24 | JsonVo res = blockService.netListen();
25 | if (res.isOK()) {
26 | System.out.println(res.getData());
27 | } else {
28 | System.out.println(res.getMessage());
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/net/NetPeers.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.net;
2 |
3 | import org.rockyang.blockj.base.model.Peer;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | public class NetPeers extends Command
15 | {
16 |
17 | public NetPeers(BlockService service)
18 | {
19 | this.name = "peers";
20 | this.usage = "Print peers";
21 | this.blockService = service;
22 | }
23 |
24 | @Override
25 | public void action(CliContext context)
26 | {
27 | JsonVo> res = blockService.netPeers();
28 | if (res.isOK()) {
29 | List peers = res.getData();
30 | for (Peer peer : peers) {
31 | System.out.println(peer);
32 | }
33 | } else {
34 | System.out.println(res.getMessage());
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/utils/CliContext.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.utils;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.utils.CmdArgsParser;
5 |
6 | import java.math.BigDecimal;
7 |
8 | /**
9 | * @author yangjian
10 | */
11 | public class CliContext
12 | {
13 | private String[] args;
14 | private final CmdArgsParser parser;
15 |
16 | public CliContext(String[] args)
17 | {
18 | this.args = args;
19 | this.parser = CmdArgsParser.getInstance(args);
20 | }
21 |
22 | public String[] getArgs()
23 | {
24 | return args;
25 | }
26 |
27 | public void setArgs(String[] args)
28 | {
29 | this.args = args;
30 | }
31 |
32 | public String getArg(int index)
33 | {
34 | return getArgs()[index];
35 | }
36 |
37 | public String getOption(String key)
38 | {
39 | return parser.getOption(key);
40 | }
41 |
42 | public String getOption(String key, String defaultValue)
43 | {
44 | return parser.getOption(key, defaultValue);
45 | }
46 |
47 | public Integer getInt(String key)
48 | {
49 | return parser.getIntOption(key);
50 | }
51 |
52 | public Integer getIntArg(int index)
53 | {
54 | return Integer.parseInt(getArg(index));
55 | }
56 |
57 | public Long getLongArg(int index)
58 | {
59 | return Long.parseLong(getArg(index));
60 | }
61 |
62 | public Integer getInt(String key, Integer defaultValue)
63 | {
64 | return parser.getIntOption(key, defaultValue);
65 | }
66 |
67 | public Boolean getBool(String key)
68 | {
69 | return parser.getBoolOption(key);
70 | }
71 |
72 | public Boolean getBoolArg(int index)
73 | {
74 | return Boolean.getBoolean(getArg(index));
75 | }
76 |
77 | public Boolean getBool(String key, boolean defaultValue)
78 | {
79 | return parser.getBoolOption(key, defaultValue);
80 | }
81 |
82 | public BigDecimal getBigDecimal(String key)
83 | {
84 | String option = getOption(key);
85 | if (StringUtils.isBlank(option)) {
86 | return null;
87 | }
88 |
89 | return BigDecimal.valueOf(Long.parseLong(option));
90 | }
91 |
92 | public BigDecimal getBigDecimalArg(int index)
93 | {
94 | return BigDecimal.valueOf(Long.parseLong(getArg(index)));
95 | }
96 |
97 | public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
98 | {
99 | String option = getOption(key);
100 | if (StringUtils.isBlank(option)) {
101 | return defaultValue;
102 | }
103 |
104 | try {
105 | return BigDecimal.valueOf(Long.parseLong(option));
106 | } catch (Exception e) {
107 | return defaultValue;
108 | }
109 | }
110 |
111 | public Long getLong(String key)
112 | {
113 | String option = getOption(key);
114 | if (StringUtils.isBlank(option)) {
115 | return null;
116 | }
117 |
118 | return Long.valueOf(option);
119 | }
120 |
121 | public Long getLong(String key, Long defaultValue)
122 | {
123 | String option = getOption(key);
124 | if (StringUtils.isBlank(option)) {
125 | return null;
126 | }
127 |
128 | return Long.valueOf(option);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/utils/Printer.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.utils;
2 |
3 | /**
4 | * @author yangjian
5 | */
6 | public class Printer {
7 |
8 | public static void printTabLine(String format, Object... args)
9 | {
10 | System.out.printf("%2s", "");
11 | System.out.printf(format, args);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/wallet/WalletBalance.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.wallet;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | import java.math.BigDecimal;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | public class WalletBalance extends Command
15 | {
16 |
17 | public WalletBalance(BlockService service)
18 | {
19 | this.name = "balance";
20 | this.fullName = "wallet balance";
21 | this.usage = "Get account balance";
22 | this.argsUsage = "[address]";
23 | this.blockService = service;
24 | }
25 |
26 | @Override
27 | public void action(CliContext context)
28 | {
29 | String address = context.getArg(0);
30 | if (StringUtils.isBlank(address)) {
31 | System.out.println("Please input address");
32 | return;
33 | }
34 |
35 | JsonVo res = blockService.getBalance(address);
36 | if (res.isOK()) {
37 | System.out.printf("%-45s%-15s\n", "Address", "Balance");
38 | System.out.printf("%-45s%-15s\n", address, res.getData());
39 | } else {
40 | System.out.println(res.getMessage());
41 | }
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/wallet/WalletList.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.wallet;
2 |
3 | import org.rockyang.blockj.base.model.Wallet;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.client.cmd.Command;
6 | import org.rockyang.blockj.client.cmd.utils.CliContext;
7 | import org.rockyang.blockj.client.rpc.BlockService;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | public class WalletList extends Command
15 | {
16 |
17 | public WalletList(BlockService service)
18 | {
19 | this.name = "list";
20 | this.usage = "List wallet address";
21 | this.argsUsage = "wallet list";
22 | this.blockService = service;
23 | }
24 |
25 | @Override
26 | public void action(CliContext context)
27 | {
28 | JsonVo> res = blockService.walletList();
29 | if (res.isOK()) {
30 | List wallets = res.getData();
31 | System.out.printf("%-45s%-15s%-5s\n", "Address", "Balance", "Nonce");
32 | for (Wallet wallet : wallets) {
33 | System.out.printf("%-45s%-15s%-5s\n", wallet.getAddress(), wallet.getBalance(), wallet.getMessageNonce());
34 | }
35 | } else {
36 | System.out.println("No wallet on this node");
37 | }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/cmd/wallet/WalletNew.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.cmd.wallet;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.model.Wallet;
5 | import org.rockyang.blockj.base.vo.JsonVo;
6 | import org.rockyang.blockj.base.vo.MnemonicWallet;
7 | import org.rockyang.blockj.client.cmd.Command;
8 | import org.rockyang.blockj.client.cmd.utils.CliContext;
9 | import org.rockyang.blockj.client.rpc.BlockService;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | public class WalletNew extends Command
15 | {
16 |
17 | public WalletNew(BlockService service)
18 | {
19 | this.name = "new";
20 | this.usage = "Generate a new key";
21 | this.blockService = service;
22 | }
23 |
24 | @Override
25 | public void action(CliContext context)
26 | {
27 | Boolean mnemonic = context.getBool("mnemonic");
28 | JsonVo res;
29 | if (mnemonic) {
30 | String password = context.getArg(0);
31 | if (StringUtils.isBlank(password)) {
32 | System.out.println("Mnemonic wallet need a password");
33 | return;
34 | }
35 | res = blockService.newMnemonicWallet(password);
36 | } else {
37 | res = blockService.newWallet();
38 | }
39 | if (res.isOK()) {
40 | if (mnemonic) {
41 | MnemonicWallet wallet = (MnemonicWallet) res.getData();
42 | System.out.printf("Mnemonic words: %s%n", wallet.getMnemonic());
43 | System.out.printf("Address: %s%n", wallet.getWallet().getAddress());
44 | } else {
45 | Wallet wallet = (Wallet) res.getData();
46 | System.out.println(wallet.getAddress());
47 | }
48 | } else {
49 | System.out.println(res.getMessage());
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/exception/ApiError.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.exception;
2 |
3 |
4 | /**
5 | * @author yangjian
6 | */
7 | public class ApiError {
8 |
9 | private String message;
10 |
11 | private String code;
12 |
13 | private Error error;
14 |
15 | public String getMessage() {
16 | return message;
17 | }
18 |
19 | public void setMessage(String message) {
20 | this.message = message;
21 | }
22 |
23 | public String getCode() {
24 | return code;
25 | }
26 |
27 | public void setCode(String code) {
28 | this.code = code;
29 | }
30 |
31 | public Error getError() {
32 | return error;
33 | }
34 |
35 | public void setError(Error error) {
36 | this.error = error;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/exception/ApiException.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.exception;
2 |
3 | /**
4 | * API exception
5 | * @author yangjian
6 | */
7 | public class ApiException extends RuntimeException {
8 |
9 | private ApiError error;
10 |
11 | public ApiException(ApiError apiError) {
12 | this.error = apiError;
13 | }
14 |
15 | public ApiException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public ApiError getError() {
20 | return error;
21 | }
22 |
23 | public void setError(ApiError error) {
24 | this.error = error;
25 | }
26 |
27 | @Override
28 | public String getMessage() {
29 | if (error != null) {
30 | return error.getMessage();
31 | }
32 | return super.getMessage();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/exception/Error.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.exception;
2 |
3 | /**
4 | * @author yangjian
5 | */
6 | public class Error {
7 |
8 | private String code;
9 |
10 | private String name;
11 |
12 | private String what;
13 |
14 | private ErrorDetails[] details;
15 |
16 | private Error() {
17 |
18 | }
19 |
20 | public String getCode() {
21 | return code;
22 | }
23 |
24 | public void setCode(String code) {
25 | this.code = code;
26 | }
27 |
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | public void setName(String name) {
33 | this.name = name;
34 | }
35 |
36 | public String getWhat() {
37 | return what;
38 | }
39 |
40 | public void setWhat(String what) {
41 | this.what = what;
42 | }
43 |
44 | public ErrorDetails[] getDetails() {
45 | return details;
46 | }
47 |
48 | public void setDetails(ErrorDetails[] details) {
49 | this.details = details;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/exception/ErrorDetails.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.exception;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | /**
6 | * @author yangjian
7 | */
8 | public class ErrorDetails {
9 |
10 | private String message;
11 |
12 | private String file;
13 |
14 | private Integer lineNumber;
15 |
16 | private String method;
17 |
18 | private ErrorDetails() {
19 |
20 | }
21 |
22 | public String getMessage() {
23 | return message;
24 | }
25 |
26 | public void setMessage(String message) {
27 | this.message = message;
28 | }
29 |
30 | public String getFile() {
31 | return file;
32 | }
33 |
34 | public void setFile(String file) {
35 | this.file = file;
36 | }
37 |
38 | public Integer getLineNumber() {
39 | return lineNumber;
40 | }
41 |
42 | @JsonProperty("line_number")
43 | public void setLineNumber(Integer lineNumber) {
44 | this.lineNumber = lineNumber;
45 | }
46 |
47 | public String getMethod() {
48 | return method;
49 | }
50 |
51 | public void setMethod(String method) {
52 | this.method = method;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/rpc/BlockRpcService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.rpc;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.model.Wallet;
6 | import org.rockyang.blockj.base.vo.JsonVo;
7 | import org.rockyang.blockj.base.vo.MnemonicWallet;
8 | import retrofit2.Call;
9 | import retrofit2.http.*;
10 |
11 | import java.math.BigDecimal;
12 |
13 | /**
14 | * blockj RPC service interface
15 | *
16 | * @author yangjian
17 | */
18 | public interface BlockRpcService
19 | {
20 |
21 | @GET("/wallet/new")
22 | Call> newWallet();
23 |
24 | @FormUrlEncoded
25 | @POST("/wallet/new/mnemonic")
26 | Call> newMnemonicWallet(@Field("password") String password);
27 |
28 | @GET("/wallet/list")
29 | Call walletList();
30 |
31 | @GET("/wallet/balance/{address}")
32 | Call> getBalance(@Path("address") String address);
33 |
34 | @FormUrlEncoded
35 | @POST("/message/send")
36 | Call> sendMessage(
37 | @Field("to") String to,
38 | @Field("from") String from,
39 | @Field("value") BigDecimal value,
40 | @Field("data") String data);
41 |
42 | @GET("/message/get/{cid}")
43 | Call> getMessage(@Path("cid") String cid);
44 |
45 | @GET("/chain/head")
46 | Call> chainHead();
47 |
48 | @GET("/chain/block/{height}")
49 | Call> getBlock(@Path("height") Long height);
50 |
51 | @GET("/net/peers")
52 | Call netPeers();
53 |
54 | @GET("/net/listen")
55 | Call> netListen();
56 |
57 | @GET("/net/connect")
58 | Call> netConnect(@Field("address") String address);
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/rpc/BlockService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.rpc;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.model.Peer;
6 | import org.rockyang.blockj.base.model.Wallet;
7 | import org.rockyang.blockj.base.vo.JsonVo;
8 | import org.rockyang.blockj.base.vo.MnemonicWallet;
9 |
10 | import java.math.BigDecimal;
11 | import java.util.List;
12 |
13 | /**
14 | * @author yangjian
15 | */
16 | public interface BlockService
17 | {
18 |
19 | JsonVo newWallet();
20 |
21 | JsonVo newMnemonicWallet(String password);
22 |
23 | JsonVo> walletList();
24 |
25 | JsonVo getBalance(String address);
26 |
27 | JsonVo sendMessage(String from, String to, BigDecimal value, String param);
28 |
29 | JsonVo getMessage(String cid);
30 |
31 | JsonVo chainHead();
32 |
33 | JsonVo getBlock(Long height);
34 |
35 | JsonVo> netPeers();
36 |
37 | JsonVo netListen();
38 |
39 | JsonVo netConnect(String peerAddress);
40 | }
41 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/rpc/impl/BlockServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.rpc.impl;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import okhttp3.OkHttpClient;
5 | import okhttp3.logging.HttpLoggingInterceptor;
6 | import org.rockyang.blockj.base.model.Block;
7 | import org.rockyang.blockj.base.model.Message;
8 | import org.rockyang.blockj.base.model.Peer;
9 | import org.rockyang.blockj.base.model.Wallet;
10 | import org.rockyang.blockj.base.vo.JsonVo;
11 | import org.rockyang.blockj.base.vo.MnemonicWallet;
12 | import org.rockyang.blockj.client.exception.ApiError;
13 | import org.rockyang.blockj.client.exception.ApiException;
14 | import org.rockyang.blockj.client.rpc.BlockRpcService;
15 | import org.rockyang.blockj.client.rpc.BlockService;
16 | import retrofit2.Call;
17 | import retrofit2.Response;
18 | import retrofit2.Retrofit;
19 | import retrofit2.converter.jackson.JacksonConverterFactory;
20 |
21 | import java.io.IOException;
22 | import java.lang.annotation.Annotation;
23 | import java.math.BigDecimal;
24 | import java.util.List;
25 |
26 | /**
27 | * @author yangjian
28 | */
29 | public class BlockServiceImpl implements BlockService
30 | {
31 |
32 | private static final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
33 |
34 | private static final Retrofit.Builder builder = new Retrofit.Builder()
35 | .addConverterFactory(JacksonConverterFactory.create());
36 |
37 | private static Retrofit retrofit;
38 |
39 | private static BlockRpcService rpcService;
40 |
41 | public BlockServiceImpl(String baseUrl, boolean debug)
42 | {
43 | // open debug log model
44 | if (debug) {
45 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
46 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
47 | httpClient.addInterceptor(loggingInterceptor);
48 | }
49 |
50 | builder.baseUrl(baseUrl);
51 | builder.client(httpClient.build());
52 | builder.addConverterFactory(JacksonConverterFactory.create());
53 | retrofit = builder.build();
54 | rpcService = retrofit.create(BlockRpcService.class);
55 | }
56 |
57 | /**
58 | * Invoke the remote API Synchronously
59 | */
60 | public static T executeSync(Call call)
61 | {
62 | try {
63 | Response response = call.execute();
64 | if (response.isSuccessful()) {
65 | return response.body();
66 | } else {
67 | ApiError apiError = getApiError(response);
68 | throw new ApiException(apiError);
69 | }
70 | } catch (IOException e) {
71 | throw new ApiException(e);
72 | }
73 | }
74 |
75 | /**
76 | * get api error message
77 | */
78 | private static ApiError getApiError(Response> response) throws IOException, ApiException
79 | {
80 | assert response.errorBody() != null;
81 | return (ApiError) retrofit.responseBodyConverter(ApiError.class, new Annotation[0]).convert(response.errorBody());
82 | }
83 |
84 | // create a new wallet
85 | public JsonVo newWallet()
86 | {
87 | return executeSync(rpcService.newWallet());
88 | }
89 |
90 | public JsonVo newMnemonicWallet(String password)
91 | {
92 | return executeSync(rpcService.newMnemonicWallet(password));
93 | }
94 |
95 | // get wallets list
96 | public JsonVo> walletList()
97 | {
98 | JsonVo res = executeSync(rpcService.walletList());
99 | if (res.isOK()) {
100 | String s = JSON.toJSONString(res.getData());
101 | List wallets = JSON.parseArray(s, Wallet.class);
102 | return new JsonVo<>(res.getCode(), wallets);
103 | } else {
104 | return new JsonVo<>(res.getCode(), res.getMessage());
105 | }
106 | }
107 |
108 | public JsonVo getBalance(String address)
109 | {
110 | return executeSync(rpcService.getBalance(address));
111 | }
112 |
113 | public JsonVo sendMessage(String from, String to, BigDecimal value, String param)
114 | {
115 | return executeSync(rpcService.sendMessage(to, from, value, param));
116 | }
117 |
118 | public JsonVo getMessage(String cid)
119 | {
120 | return executeSync(rpcService.getMessage(cid));
121 | }
122 |
123 | public JsonVo chainHead()
124 | {
125 | return executeSync(rpcService.chainHead());
126 | }
127 |
128 | @Override
129 | public JsonVo getBlock(Long height)
130 | {
131 | return executeSync(rpcService.getBlock(height));
132 | }
133 |
134 | @Override
135 | public JsonVo> netPeers()
136 | {
137 | JsonVo res = executeSync(rpcService.netPeers());
138 | if (res.isOK()) {
139 | String s = JSON.toJSONString(res.getData());
140 | List peers = JSON.parseArray(s, Peer.class);
141 | return new JsonVo<>(res.getCode(), peers);
142 | } else {
143 | return new JsonVo<>(res.getCode(), res.getMessage());
144 | }
145 | }
146 |
147 | @Override
148 | public JsonVo netListen()
149 | {
150 | return executeSync(rpcService.netListen());
151 | }
152 |
153 | @Override
154 | public JsonVo netConnect(String peerAddress)
155 | {
156 | return executeSync(rpcService.netConnect(peerAddress));
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/blockj-client/src/main/java/org/rockyang/blockj/client/rpc/impl/BlockServiceMock.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.rpc.impl;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.model.Peer;
6 | import org.rockyang.blockj.base.model.Wallet;
7 | import org.rockyang.blockj.base.vo.JsonVo;
8 | import org.rockyang.blockj.base.vo.MnemonicWallet;
9 | import org.rockyang.blockj.client.rpc.BlockService;
10 |
11 | import java.math.BigDecimal;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * mock implements for blockjService
17 | *
18 | * @author yangjian
19 | */
20 | public class BlockServiceMock implements BlockService
21 | {
22 | @Override
23 | public JsonVo newWallet()
24 | {
25 | return new JsonVo<>(JsonVo.SUCCESS, new Wallet("t16oicympjfdwwtkzwve6pwgigdw3xbz76k4t66va", BigDecimal.ZERO));
26 | }
27 |
28 | @Override
29 | public JsonVo newMnemonicWallet(String password)
30 | {
31 | return null;
32 | }
33 |
34 | @Override
35 | public JsonVo> walletList()
36 | {
37 | List list = new ArrayList<>(3);
38 | list.add(new Wallet("t16oicympjfdwwtkzwve6pwgigdw3xbz76k4t66va", new BigDecimal("33124.6542")));
39 | list.add(new Wallet("t1arfcunbc767a56rvvalmbcdzooc4blqgukctffq", new BigDecimal("1000.3245")));
40 | list.add(new Wallet("t1wbh64v24vd6gjtwgu5vpybuvzukx4hoxdmmtzmy", new BigDecimal("8675.0976")));
41 | return new JsonVo<>(JsonVo.SUCCESS, list);
42 | }
43 |
44 | @Override
45 | public JsonVo getBalance(String address)
46 | {
47 | return new JsonVo<>(JsonVo.SUCCESS, BigDecimal.ZERO);
48 | }
49 |
50 | @Override
51 | public JsonVo sendMessage(String from, String to, BigDecimal value, String param)
52 | {
53 | return new JsonVo<>(JsonVo.SUCCESS, new Message().getCid());
54 | }
55 |
56 | @Override
57 | public JsonVo getMessage(String cid)
58 | {
59 | return new JsonVo<>(JsonVo.SUCCESS, new Message());
60 | }
61 |
62 | @Override
63 | public JsonVo chainHead()
64 | {
65 | return new JsonVo<>(JsonVo.SUCCESS, Long.valueOf("12345678"));
66 | }
67 |
68 | @Override
69 | public JsonVo> netPeers()
70 | {
71 | return null;
72 | }
73 |
74 | @Override
75 | public JsonVo netListen()
76 | {
77 | return null;
78 | }
79 |
80 | @Override
81 | public JsonVo netConnect(String peerAddress)
82 | {
83 | return null;
84 | }
85 |
86 | @Override
87 | public JsonVo getBlock(Long height)
88 | {
89 | return null;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/blockj-client/src/test/java/org/rockyang/blockj/client/test/BaseTester.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.test;
2 |
3 | import org.junit.Before;
4 | import org.rockyang.blockj.client.rpc.impl.BlockServiceImpl;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | public abstract class BaseTester {
10 |
11 | protected BlockServiceImpl serviceWrapper;
12 |
13 | @Before
14 | public void init()
15 | {
16 | serviceWrapper = new BlockServiceImpl("http://127.0.0.1:8001", true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/blockj-client/src/test/java/org/rockyang/blockj/client/test/ChainTest.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.test;
2 |
3 | import org.junit.Test;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | public class ChainTest extends BaseTester
10 | {
11 |
12 | @Test
13 | public void chainHead()
14 | {
15 | JsonVo res = serviceWrapper.chainHead();
16 | System.out.printf("Chain head: %d\n", res.getData());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/blockj-client/src/test/java/org/rockyang/blockj/client/test/MessageTest.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.test;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * @author yangjian
7 | */
8 | public class MessageTest extends BaseTester {
9 |
10 | @Test
11 | public void sendMessage()
12 | {
13 |
14 | }
15 |
16 | @Test
17 | public void getMessage()
18 | {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/blockj-client/src/test/java/org/rockyang/blockj/client/test/TestApp.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.test;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * @author yangjian
7 | **/
8 | public class TestApp
9 | {
10 | @Test
11 | public void test()
12 | {
13 | String s = "true";
14 | System.out.println(Boolean.parseBoolean(s));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/blockj-client/src/test/java/org/rockyang/blockj/client/test/WalletTest.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.client.test;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * @author yangjian
7 | */
8 | public class WalletTest extends BaseTester
9 | {
10 |
11 |
12 | @Test
13 | public void newWallet()
14 | {
15 |
16 | }
17 |
18 | @Test
19 | public void walletList()
20 | {
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/blockj-client/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootLogger = INFO,CONSOLE
2 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
3 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
4 | log4j.appender.CONSOLE.layout.ConversionPattern=[%p] %d{yyyy-MM-dd HH:mm} %-5l - %m%n
--------------------------------------------------------------------------------
/blockj-miner/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 2.7.5
10 |
11 |
12 |
13 | org.rockyang
14 | blockj-miner
15 | 2.0.0
16 | jar
17 |
18 | blockj-miner
19 | miner and chain implementation for blockj
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 8
25 |
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-web
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-test
37 | test
38 |
39 |
40 |
41 | com.google.guava
42 | guava
43 | 32.0.0-jre
44 |
45 |
46 |
47 | org.apache.commons
48 | commons-lang3
49 | 3.7
50 |
51 |
52 |
53 | com.alibaba
54 | fastjson
55 | 1.2.57
56 |
57 |
58 |
59 | org.rockyang
60 | blockj-base
61 | ${project.version}
62 |
63 |
64 |
65 |
66 | net.dreamlu
67 | tio-core
68 | 3.8.4.v20220920-RELEASE
69 |
70 |
71 |
72 | junit
73 | junit
74 | test
75 |
76 |
77 |
78 |
79 |
80 |
81 | org.springframework.boot
82 | spring-boot-maven-plugin
83 | 2.6.5
84 |
85 | true
86 | true
87 |
88 |
89 |
90 |
91 |
92 | org.apache.maven.plugins
93 | maven-compiler-plugin
94 | 3.10.1
95 |
96 | 8
97 | 8
98 | true
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/Application.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | @SpringBootApplication
10 | public class Application {
11 | public static void main(String[] args) throws Exception
12 | {
13 | AppRunner runner = new AppRunner(args);
14 | if (!runner.preRun()) {
15 | runner.cleanRepo();
16 | System.exit(0);
17 | }
18 | SpringApplication.run(Application.class, args);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/BlockPool.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.utils.ThreadUtils;
5 | import org.rockyang.blockj.miner.Miner;
6 | import org.rockyang.blockj.service.BlockService;
7 | import org.rockyang.blockj.vo.Result;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.stereotype.Component;
11 |
12 | import javax.annotation.PostConstruct;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Map;
16 | import java.util.concurrent.ConcurrentHashMap;
17 | import java.util.concurrent.atomic.AtomicLong;
18 |
19 | /**
20 | * blocks pool
21 | *
22 | * when mined a new block, push it to the block pool,
23 | * after it confirmed then move it to the chain store.
24 | *
25 | * @author yangjian
26 | */
27 | @Component
28 | public class BlockPool {
29 |
30 | private final static Logger logger = LoggerFactory.getLogger(BlockPool.class);
31 | private final Map blocks = new ConcurrentHashMap<>();
32 | private final AtomicLong head = new AtomicLong();
33 | private final BlockService blockService;
34 |
35 | public BlockPool(BlockService blockService)
36 | {
37 | this.blockService = blockService;
38 | }
39 |
40 | @PostConstruct
41 | public void run()
42 | {
43 | new Thread(() -> {
44 | while (true) {
45 | long head = blockService.chainHead();
46 | Block headBlock = blockService.getBlockByHeight(head);
47 | if (headBlock == null) {
48 | logger.info("Can not find the head block");
49 | ThreadUtils.niceSleep(1);
50 | continue;
51 | }
52 | for (Block block : getBlocks()) {
53 | long diff = block.getHeader().getHeight() - head;
54 | long now = System.currentTimeMillis() / 1000;
55 | if (now < headBlock.getHeader().getTimestamp() + diff * Miner.BLOCK_DELAY_SECS) {
56 | continue;
57 | }
58 | block.getHeader().setTimestamp(headBlock.getHeader().getTimestamp() + diff * Miner.BLOCK_DELAY_SECS);
59 | try {
60 | Result result = blockService.checkBlock(block);
61 | if (result.isOk()) {
62 | blockService.markBlockAsValidated(block);
63 | } else {
64 | logger.warn("Invalid block, height: {}, message: {}", block.getHeader().getHeight(), result.getMessage());
65 | }
66 | } catch (Exception e) {
67 | e.printStackTrace();
68 | } finally {
69 | removeBlock(block.getHeader().getHeight());
70 | }
71 | }
72 | }
73 | }).start();
74 | }
75 |
76 | public void putBlock(Block block)
77 | {
78 | if (hasBlock(block)) {
79 | return;
80 | }
81 |
82 | blocks.put(block.getHeader().getHeight(), block);
83 | // update the chain head
84 | while (true) {
85 | long h = head.get();
86 | if (block.getHeader().getHeight() > h) {
87 | if (head.compareAndSet(h, block.getHeader().getHeight())) {
88 | break;
89 | }
90 | }
91 | }
92 | }
93 |
94 | public boolean hasBlock(Block block)
95 | {
96 | return blocks.containsKey(block.getHeader().getHeight());
97 | }
98 |
99 |
100 | public List getBlocks()
101 | {
102 | return new ArrayList<>(blocks.values());
103 | }
104 |
105 | public long getHead()
106 | {
107 | return head.get();
108 | }
109 |
110 | // remove message from the message pool
111 | public void removeBlock(Long height)
112 | {
113 | blocks.remove(height);
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/Chain.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.model.BlockHeader;
5 | import org.rockyang.blockj.base.model.Message;
6 | import org.rockyang.blockj.base.utils.ThreadUtils;
7 | import org.rockyang.blockj.chain.event.NewBlockEvent;
8 | import org.rockyang.blockj.conf.ApplicationContextProvider;
9 | import org.rockyang.blockj.conf.MinerConfig;
10 | import org.rockyang.blockj.miner.Miner;
11 | import org.rockyang.blockj.service.BlockService;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import org.springframework.beans.factory.annotation.Qualifier;
15 | import org.springframework.stereotype.Component;
16 |
17 | import javax.annotation.PostConstruct;
18 |
19 | /**
20 | * @author yangjian
21 | */
22 | @Component
23 | public class Chain
24 | {
25 |
26 | private static final Logger logger = LoggerFactory.getLogger(Chain.class);
27 |
28 | @Qualifier(value = "powMiner")
29 | private final Miner miner;
30 | private final MessagePool messagePool;
31 | private final BlockPool blockPool;
32 | private final BlockService blockService;
33 | private final MinerConfig minerConfig;
34 |
35 | public Chain(Miner miner, MessagePool messagePool, BlockPool blockPool, BlockService blockService, MinerConfig minerConfig)
36 | {
37 | this.miner = miner;
38 | this.messagePool = messagePool;
39 | this.blockPool = blockPool;
40 | this.blockService = blockService;
41 | this.minerConfig = minerConfig;
42 | }
43 |
44 | @PostConstruct
45 | public void run()
46 | {
47 | new Thread(() -> {
48 | if (!minerConfig.isEnabledMining()) {
49 | return;
50 | }
51 | logger.info("blockj Miner started");
52 | while (true) {
53 | // get the chain head
54 | long head = blockPool.getHead();
55 | if (head <= 0) {
56 | long chainHead = blockService.chainHead();
57 | if (chainHead < 0) {
58 | logger.info("This miner is not initialized, initialize it with 'blockj init' command");
59 | return;
60 | }
61 | head = chainHead;
62 | }
63 | // @TODO: fill the blocks of null round, ONLY the genesis miner allow to do this.
64 | // Maybe it is a case?
65 |
66 | Block preBlock = blockService.getBlockByHeight(head);
67 | if (preBlock == null) {
68 | ThreadUtils.niceSleep(3);
69 | continue;
70 | }
71 | BlockHeader preBlockHeader = preBlock.getHeader();
72 | // check if it's the time for a new round
73 | long now = System.currentTimeMillis() / 1000;
74 | if (now - preBlockHeader.getTimestamp() <= Miner.BLOCK_DELAY_SECS) {
75 | ThreadUtils.niceSleep(3);
76 | continue;
77 | }
78 |
79 | try {
80 | Block block = miner.mineOne(preBlock);
81 |
82 | logger.info("Mined a new block, Height: {}", block.getHeader().getHeight());
83 | // package the messages in block from message pool
84 | // @TODO: Should we limit the number of messages in each block?
85 | // @TODO: Should we sort the message by message nonce?
86 | for (Message message : messagePool.getMessages()) {
87 | block.addMessage(message);
88 | // remove from message pool
89 | messagePool.removeMessage(message.getCid());
90 | }
91 | // put block to pool
92 | blockPool.putBlock(block);
93 | // broadcast the block
94 | ApplicationContextProvider.publishEvent(new NewBlockEvent(block));
95 |
96 | } catch (Exception e) {
97 | ThreadUtils.niceSleep(3);
98 | e.printStackTrace();
99 | }
100 |
101 | }
102 | }).start();
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/MessagePool.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain;
2 |
3 | import com.google.common.base.Objects;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.rockyang.blockj.base.model.Message;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.List;
9 | import java.util.concurrent.CopyOnWriteArrayList;
10 |
11 | /**
12 | * message pool
13 | *
14 | * @author yangjian
15 | */
16 | @Component
17 | public class MessagePool {
18 |
19 | private final List messages = new CopyOnWriteArrayList<>();
20 |
21 | public void pendingMessage(Message message)
22 | {
23 |
24 | // check if the message is already in the message pool
25 | for (Message msg : this.messages) {
26 | if (Objects.equal(msg.getCid(), message.getCid())) {
27 | return;
28 | }
29 | }
30 | this.messages.add(message);
31 | }
32 |
33 | public List getMessages()
34 | {
35 | return messages;
36 | }
37 |
38 | // remove message from the message pool
39 | public void removeMessage(String cid)
40 | {
41 | messages.removeIf(message -> Objects.equal(message.getCid(), cid));
42 | }
43 |
44 | public boolean hasMessage(Message message)
45 | {
46 | for (Message msg : this.messages) {
47 | if (StringUtils.equals(msg.getCid(), message.getCid())) {
48 | return true;
49 | }
50 | }
51 | return false;
52 | }
53 |
54 | public Message getMessage(String cid)
55 | {
56 | if (StringUtils.isEmpty(cid)) {
57 | return null;
58 | }
59 |
60 | for (Message msg : this.messages) {
61 | if (msg.getCid().equals(cid)) {
62 | return msg;
63 | }
64 | }
65 | return null;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/event/NewBlockEvent.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.event;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.springframework.context.ApplicationEvent;
5 |
6 | /**
7 | * This event will be fired when receive a new block
8 | *
9 | * @author yangjian
10 | */
11 | public class NewBlockEvent extends ApplicationEvent {
12 |
13 | public NewBlockEvent(Block block)
14 | {
15 | super(block);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/event/NewMessageEvent.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.event;
2 |
3 | import org.rockyang.blockj.base.model.Message;
4 | import org.springframework.context.ApplicationEvent;
5 |
6 | /**
7 | * This event will be fired when receive a new message
8 | *
9 | * @author yangjian
10 | */
11 | public class NewMessageEvent extends ApplicationEvent {
12 |
13 | public NewMessageEvent(Message message)
14 | {
15 | super(message);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/event/NewPeerEvent.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.event;
2 |
3 | import org.rockyang.blockj.base.model.Peer;
4 | import org.springframework.context.ApplicationEvent;
5 |
6 | /**
7 | * This event will be fired when a new node connected
8 | *
9 | * @author yangjian
10 | */
11 | public class NewPeerEvent extends ApplicationEvent {
12 |
13 | public NewPeerEvent(Peer peer)
14 | {
15 | super(peer);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/event/SyncBlockEvent.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.event;
2 |
3 | import org.springframework.context.ApplicationEvent;
4 |
5 | /**
6 | * This event will be fired when receive a request to synchronize the next block
7 | * @author yangjian
8 | */
9 | public class SyncBlockEvent extends ApplicationEvent {
10 |
11 | public SyncBlockEvent(long height) {
12 | super(height);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/listener/BlockEventListener.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.listener;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.utils.SerializeUtils;
5 | import org.rockyang.blockj.chain.event.NewBlockEvent;
6 | import org.rockyang.blockj.chain.event.SyncBlockEvent;
7 | import org.rockyang.blockj.net.base.MessagePacket;
8 | import org.rockyang.blockj.net.base.MessagePacketType;
9 | import org.rockyang.blockj.net.client.P2pClient;
10 | import org.rockyang.blockj.service.BlockService;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import org.springframework.context.event.EventListener;
14 | import org.springframework.stereotype.Component;
15 |
16 | /**
17 | * @author yangjian
18 | */
19 | @Component
20 | public class BlockEventListener {
21 |
22 | private static final Logger logger = LoggerFactory.getLogger(BlockEventListener.class);
23 |
24 | private final P2pClient client;
25 | private final BlockService blockService;
26 |
27 | public BlockEventListener(P2pClient p2pClient, BlockService blockService)
28 | {
29 | this.client = p2pClient;
30 | this.blockService = blockService;
31 | }
32 |
33 | // @EventListener(ApplicationReadyEvent.class)
34 | // public void appReady()
35 | // {
36 | // ApplicationContextProvider.publishEvent(new SyncBlockEvent(0));
37 | // }
38 |
39 | // mine a new block event
40 | @EventListener(NewBlockEvent.class)
41 |
42 | public void newBlock(NewBlockEvent event)
43 | {
44 | Block block = (Block) event.getSource();
45 | logger.info("NewBlockEvent: start to broadcast block {}", block.getHeader().getHeight());
46 | MessagePacket messagePacket = new MessagePacket();
47 | messagePacket.setType(MessagePacketType.REQ_NEW_BLOCK);
48 | messagePacket.setBody(SerializeUtils.serialize(block));
49 | client.sendGroup(messagePacket);
50 | }
51 |
52 | // sync the specified height block
53 | @EventListener(SyncBlockEvent.class)
54 | public void syncBlock(SyncBlockEvent event)
55 | {
56 | long height = (long) event.getSource();
57 | if (height == 0) {
58 | long head = blockService.chainHead();
59 | if (head > 0) {
60 | height = head + 1;
61 | }
62 | }
63 | logger.info("SyncBlockEvent: start to sync block {}", height);
64 | MessagePacket messagePacket = new MessagePacket();
65 | messagePacket.setType(MessagePacketType.REQ_BLOCK_SYNC);
66 | messagePacket.setBody(SerializeUtils.serialize(height));
67 | // @TODO: maybe we should not to send all peers to fetch block
68 | // @TODO: it's a waste of network, we can choose some well-synchronized nodes ONLY.
69 | client.sendGroup(messagePacket);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/listener/MessageEventListener.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.listener;
2 |
3 | import org.rockyang.blockj.base.model.Message;
4 | import org.rockyang.blockj.base.utils.SerializeUtils;
5 | import org.rockyang.blockj.chain.event.NewMessageEvent;
6 | import org.rockyang.blockj.net.base.MessagePacket;
7 | import org.rockyang.blockj.net.base.MessagePacketType;
8 | import org.rockyang.blockj.net.client.P2pClient;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.context.event.EventListener;
12 | import org.springframework.stereotype.Component;
13 |
14 | /**
15 | * @author yangjian
16 | */
17 | @Component
18 | public class MessageEventListener {
19 |
20 | private final P2pClient client;
21 | private static final Logger logger = LoggerFactory.getLogger(MessageEventListener.class);
22 |
23 | public MessageEventListener(P2pClient client)
24 | {
25 | this.client = client;
26 | }
27 |
28 | @EventListener(NewMessageEvent.class)
29 | public void broadCastMessage(NewMessageEvent event)
30 | {
31 | Message message = (Message) event.getSource();
32 | logger.info("NewMessageEvent: start to broadcast new message, cid: {}", message.getCid());
33 | MessagePacket messagePacket = new MessagePacket();
34 | messagePacket.setType(MessagePacketType.REQ_NEW_MESSAGE);
35 | messagePacket.setBody(SerializeUtils.serialize(message));
36 | client.sendGroup(messagePacket);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/listener/PeerEventListener.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.listener;
2 |
3 | import org.rockyang.blockj.base.model.Peer;
4 | import org.rockyang.blockj.base.utils.SerializeUtils;
5 | import org.rockyang.blockj.chain.event.NewPeerEvent;
6 | import org.rockyang.blockj.net.base.MessagePacket;
7 | import org.rockyang.blockj.net.base.MessagePacketType;
8 | import org.rockyang.blockj.net.client.P2pClient;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.context.event.EventListener;
12 | import org.springframework.stereotype.Component;
13 |
14 | /**
15 | * @author yangjian
16 | */
17 | @Component
18 | public class PeerEventListener {
19 | private static final Logger logger = LoggerFactory.getLogger(PeerEventListener.class);
20 |
21 | private final P2pClient client;
22 |
23 | public PeerEventListener(P2pClient client)
24 | {
25 | this.client = client;
26 | }
27 |
28 | // when a new peer connected, notify all peers to connect the new peer
29 | @EventListener(NewPeerEvent.class)
30 | public void newPeerConnected(NewPeerEvent event)
31 | {
32 | Peer peer = (Peer) event.getSource();
33 | logger.info("NewPeerEvent: new peer connected {}", peer);
34 | MessagePacket packet = new MessagePacket();
35 | packet.setType(MessagePacketType.REQ_NEW_PEER);
36 | packet.setBody(SerializeUtils.serialize(peer));
37 | // broadcast peer
38 | client.sendGroup(packet);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/sync/ClientHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.sync;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.utils.SerializeUtils;
5 | import org.rockyang.blockj.chain.BlockPool;
6 | import org.rockyang.blockj.chain.MessagePool;
7 | import org.rockyang.blockj.chain.event.SyncBlockEvent;
8 | import org.rockyang.blockj.conf.ApplicationContextProvider;
9 | import org.rockyang.blockj.service.BlockService;
10 | import org.rockyang.blockj.vo.PacketVo;
11 | import org.rockyang.blockj.vo.Result;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import org.springframework.stereotype.Component;
15 |
16 | /**
17 | * message request handler
18 | * this handler will process the message response from the server
19 | *
20 | * @author yangjian
21 | */
22 | @Component
23 | public class ClientHandler {
24 | private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
25 |
26 | private final BlockService blockService;
27 | private final MessagePool messagePool;
28 | private final BlockPool blockPool;
29 |
30 | public ClientHandler(BlockService blockService,
31 | MessagePool messagePool,
32 | BlockPool blockPool)
33 | {
34 | this.blockService = blockService;
35 | this.messagePool = messagePool;
36 | this.blockPool = blockPool;
37 | }
38 |
39 | public void syncBlock(byte[] body) throws Exception
40 | {
41 | PacketVo packetVo = (PacketVo) SerializeUtils.unSerialize(body);
42 | if (!packetVo.isSuccess()) {
43 | logger.warn("failed to sync block, {}", packetVo.getMessage());
44 | // @TODO: retry it later?
45 | return;
46 | }
47 | if (packetVo.getItem() == null) {
48 | logger.info("chain sync complete");
49 | return;
50 | }
51 | Block block = (Block) packetVo.getItem();
52 | if (blockService.isBlockValidated(block.getHeader().getHeight())) {
53 | logger.info("block {} is already validate, skip it.", block.getHeader().getHeight());
54 | // sync the next block
55 | ApplicationContextProvider.publishEvent(new SyncBlockEvent(block.getHeader().getHeight() + 1));
56 | return;
57 | }
58 |
59 | Result result = blockService.checkBlock(block);
60 | if (result.isOk()) {
61 | blockService.markBlockAsValidated(block);
62 | logger.info("sync block successfully, height: {}", block.getHeader().getHeight());
63 | ApplicationContextProvider.publishEvent(new SyncBlockEvent(block.getHeader().getHeight() + 1));
64 | } else {
65 | logger.warn("Invalid block, height: {}, message: {}", block.getHeader().getHeight(), result.getMessage());
66 | }
67 | }
68 |
69 | // new block confirm
70 | public void newBlock(byte[] body)
71 | {
72 | PacketVo packetVo = (PacketVo) SerializeUtils.unSerialize(body);
73 | Block block = (Block) packetVo.getItem();
74 |
75 | // if confirm failed, remove the block from pool
76 | if (!packetVo.isSuccess()) {
77 | logger.info("block confirm failed, {}", packetVo.getMessage());
78 | blockPool.removeBlock(block.getHeader().getHeight());
79 | }
80 | }
81 |
82 | // new message validation
83 | public void newMessage(byte[] body)
84 | {
85 | PacketVo packetVo = (PacketVo) SerializeUtils.unSerialize(body);
86 | String msgCid = (String) packetVo.getItem();
87 | if (!packetVo.isSuccess()) {
88 | logger.error("message {} confirm failed, drop it", msgCid);
89 | // remove message from message pool
90 | messagePool.removeMessage(msgCid);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/chain/sync/ServerHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.chain.sync;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.model.Peer;
6 | import org.rockyang.blockj.base.utils.SerializeUtils;
7 | import org.rockyang.blockj.chain.BlockPool;
8 | import org.rockyang.blockj.chain.MessagePool;
9 | import org.rockyang.blockj.chain.event.NewBlockEvent;
10 | import org.rockyang.blockj.chain.event.NewMessageEvent;
11 | import org.rockyang.blockj.chain.event.NewPeerEvent;
12 | import org.rockyang.blockj.conf.ApplicationContextProvider;
13 | import org.rockyang.blockj.net.base.MessagePacket;
14 | import org.rockyang.blockj.net.base.MessagePacketType;
15 | import org.rockyang.blockj.net.client.P2pClient;
16 | import org.rockyang.blockj.service.BlockService;
17 | import org.rockyang.blockj.service.MessageService;
18 | import org.rockyang.blockj.service.PeerService;
19 | import org.rockyang.blockj.vo.PacketVo;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import org.springframework.stereotype.Component;
23 | import org.tio.core.Node;
24 |
25 | /**
26 | * message response handler
27 | * reply the message that send from the other client
28 | *
29 | * @author yangjian
30 | */
31 | @Component
32 | public class ServerHandler {
33 |
34 | private static final Logger logger = LoggerFactory.getLogger(ServerHandler.class);
35 |
36 | private final BlockService blockService;
37 | private final MessagePool messagePool;
38 | private final BlockPool blockPool;
39 | private final MessageService messageService;
40 | private final PeerService peerService;
41 | private final P2pClient client;
42 |
43 | public ServerHandler(BlockService blockService,
44 | MessagePool messagePool,
45 | BlockPool blockPool,
46 | MessageService messageService,
47 | PeerService peerService,
48 | P2pClient client)
49 | {
50 | this.blockService = blockService;
51 | this.messagePool = messagePool;
52 | this.blockPool = blockPool;
53 | this.messageService = messageService;
54 | this.peerService = peerService;
55 | this.client = client;
56 | }
57 |
58 | // new message validation
59 | public synchronized MessagePacket newMessage(byte[] body)
60 | {
61 | PacketVo packetVo = new PacketVo();
62 | MessagePacket resPacket = new MessagePacket();
63 | Message message = (Message) SerializeUtils.unSerialize(body);
64 | logger.info("receive a new message, {}", message);
65 |
66 | packetVo.setItem(message.getCid());
67 | // validate the message
68 | if (!messagePool.hasMessage(message) && messageService.validateMessage(message)) {
69 | packetVo.setSuccess(true);
70 | // put message into message pool
71 | messagePool.pendingMessage(message);
72 | // broadcast message to other peers
73 | ApplicationContextProvider.publishEvent(new NewMessageEvent(message));
74 | return buildPacket(MessagePacketType.RES_NEW_MESSAGE, message.getCid(), true, null);
75 | } else {
76 | logger.info("failed to validate the message, invalid signature, {}", message);
77 | return buildPacket(MessagePacketType.RES_NEW_MESSAGE, message.getCid(), false, "Invalid message signature");
78 | }
79 | }
80 |
81 | public synchronized MessagePacket syncBlock(byte[] body)
82 | {
83 | long height = (long) SerializeUtils.unSerialize(body);
84 | logger.info("receive a block sync request, height: {}", height);
85 | Block block = blockService.getBlockByHeight(height);
86 | String message = null;
87 | if (block == null) {
88 | message = String.format("block not exists, height: %d", height);
89 |
90 | }
91 | return buildPacket(MessagePacketType.RES_BLOCK_SYNC, block, true, message);
92 | }
93 |
94 | /**
95 | * new block event handler
96 | */
97 | public synchronized MessagePacket newBlock(byte[] body)
98 | {
99 | Block block = (Block) SerializeUtils.unSerialize(body);
100 | logger.info("receive new block confirm request, height: {}", block.getHeader().getHeight());
101 | if (blockService.isBlockValidated(block.getHeader().getHeight())) {
102 | logger.info("block exists {}, {}", block.getHeader().getHeight(), block.getHeader().getHash());
103 | return buildPacket(MessagePacketType.RES_NEW_BLOCK, block, false, "block exists");
104 | }
105 | if (!blockPool.hasBlock(block)) {
106 | // put it to block pool
107 | blockPool.putBlock(block);
108 |
109 | // if we receive this block for the first time,
110 | // we need to forward it to the other nodes
111 | ApplicationContextProvider.publishEvent(new NewBlockEvent(block));
112 | }
113 | return buildPacket(MessagePacketType.RES_NEW_BLOCK, block, true, null);
114 | }
115 |
116 | public synchronized MessagePacket newPeer(byte[] body) throws Exception
117 | {
118 | Peer peer = (Peer) SerializeUtils.unSerialize(body);
119 | if (!peerService.hasPeer(peer)) {
120 | // store peer
121 | peerService.addPeer(peer);
122 | }
123 | // try to connect peer
124 | if (client.connect(new Node(peer.getIp(), peer.getPort()))) {
125 | // fire new peer connected event
126 | ApplicationContextProvider.publishEvent(new NewPeerEvent(peer));
127 | }
128 | return null;
129 | }
130 |
131 | private MessagePacket buildPacket(byte type, Object data, boolean status, String message)
132 | {
133 | MessagePacket resPacket = new MessagePacket();
134 | PacketVo packetVo = new PacketVo(data, status);
135 | packetVo.setMessage(message);
136 | resPacket.setType(type);
137 | resPacket.setBody(SerializeUtils.serialize(packetVo));
138 | return resPacket;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/conf/AppConfig.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.conf;
2 |
3 | import org.rockyang.blockj.base.store.Datastore;
4 | import org.rockyang.blockj.base.store.RocksDatastore;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | /**
9 | * @author yangjian
10 | */
11 | @Configuration
12 | public class AppConfig {
13 |
14 | @Bean(value = "datastore")
15 | public Datastore getDataStore(MinerConfig minerConfig)
16 | {
17 | return new RocksDatastore(minerConfig.getRepo());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/conf/ApplicationContextProvider.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.conf;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.beans.factory.support.DefaultListableBeanFactory;
5 | import org.springframework.context.ApplicationContext;
6 | import org.springframework.context.ApplicationContextAware;
7 | import org.springframework.context.ApplicationEvent;
8 | import org.springframework.context.ConfigurableApplicationContext;
9 | import org.springframework.stereotype.Component;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | @Component
15 | public class ApplicationContextProvider implements ApplicationContextAware {
16 |
17 | private static ApplicationContext context;
18 |
19 | @Override
20 | public void setApplicationContext(ApplicationContext applicationContext)
21 | throws BeansException
22 | {
23 | context = applicationContext;
24 | }
25 |
26 | public static void publishEvent(ApplicationEvent event)
27 | {
28 | context.publishEvent(event);
29 | }
30 |
31 | public static Object registerSingletonBean(String beanName, Object singletonObject)
32 | {
33 |
34 | // convert applicationContext to ConfigurableApplicationContext
35 | ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context;
36 | //get BeanFactory
37 | DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
38 | // register bean
39 | defaultListableBeanFactory.registerSingleton(beanName, singletonObject);
40 | // get registered bean.
41 | return configurableApplicationContext.getBean(beanName);
42 | }
43 |
44 | public static ApplicationContext getApplicationContext()
45 | {
46 | return context;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/conf/MinerConfig.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.conf;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | @Component
10 | public class MinerConfig {
11 | @Value("${blockj.repo}")
12 | private String repo;
13 | @Value("${blockj.enable-mining}")
14 | private boolean enabledMining;
15 |
16 | public boolean isEnabledMining()
17 | {
18 | return enabledMining;
19 | }
20 |
21 | public void setEnabledMining(boolean enabledMining)
22 | {
23 | this.enabledMining = enabledMining;
24 | }
25 |
26 | public String getRepo()
27 | {
28 | if (repo != null) {
29 | return repo;
30 | }
31 | return System.getProperty("blockj_PATH");
32 | }
33 |
34 | public void setRepo(String repo)
35 | {
36 | this.repo = repo;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/miner/Miner.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.miner;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 |
5 | import java.math.BigDecimal;
6 |
7 | /**
8 | * Miner interface
9 | *
10 | * @author yangjian
11 | */
12 | public interface Miner {
13 |
14 | BigDecimal MINING_REWARD = BigDecimal.valueOf(50);
15 | BigDecimal GENESIS_ACCOUNT_BALANCE = BigDecimal.valueOf(10000);
16 | // total supply coins number
17 | BigDecimal TOTAL_SUPPLY = BigDecimal.valueOf(21000000);
18 | // mining a new block every 10s
19 | int BLOCK_DELAY_SECS = 10;
20 | String REWARD_ADDR = "B099";
21 |
22 | // mined a new block
23 | Block mineOne(Block preBlock) throws Exception;
24 |
25 | // check if a block is valid
26 | boolean validateBlock(Block block);
27 | }
28 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/miner/pow/PowMiner.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.miner.pow;
2 |
3 | import org.rockyang.blockj.base.crypto.Sign;
4 | import org.rockyang.blockj.base.model.Block;
5 | import org.rockyang.blockj.base.model.BlockHeader;
6 | import org.rockyang.blockj.base.model.Message;
7 | import org.rockyang.blockj.base.model.Wallet;
8 | import org.rockyang.blockj.service.AccountService;
9 | import org.rockyang.blockj.service.WalletService;
10 | import org.rockyang.blockj.miner.Miner;
11 | import org.springframework.stereotype.Component;
12 |
13 | /**
14 | * The PoW algorithm implements
15 | *
16 | * @author yangjian
17 | */
18 | @Component(value = "powMiner")
19 | public class PowMiner implements Miner {
20 |
21 | // the genesis block nonce value
22 | public static final Long GENESIS_BLOCK_NONCE = 100000L;
23 | private final WalletService walletService;
24 | private final AccountService accountService;
25 |
26 | public PowMiner(WalletService walletService, AccountService accountService)
27 | {
28 | this.walletService = walletService;
29 | this.accountService = accountService;
30 | }
31 |
32 | @Override
33 | public Block mineOne(Block preBlock) throws Exception
34 | {
35 | // fetch the miner key
36 | Wallet minerKey = walletService.getMinerWallet();
37 | if (minerKey == null) {
38 | throw new RuntimeException("No miner address set.");
39 | }
40 | BlockHeader preBlockHeader = preBlock.getHeader();
41 | // check if it's the time for new round
42 | if (System.currentTimeMillis() - preBlockHeader.getTimestamp() <= Miner.BLOCK_DELAY_SECS * 1000L) {
43 | return null;
44 | }
45 | BlockHeader newBlockHeader = new BlockHeader(preBlockHeader.getHeight() + 1, preBlockHeader.getHash());
46 | ProofOfWork proofOfWork = ProofOfWork.newProofOfWork(newBlockHeader);
47 | // run the proof of work, and get the result
48 | PowResult result = proofOfWork.run();
49 | newBlockHeader.setDifficulty(result.getTarget());
50 | newBlockHeader.setNonce(result.getNonce());
51 | newBlockHeader.setHash(result.getHash());
52 |
53 | Block newBlock = new Block(newBlockHeader);
54 | // create a message for miner reward
55 | Message message = new Message();
56 | message.setFrom(Miner.REWARD_ADDR);
57 | message.setTo(minerKey.getAddress());
58 | message.setParams("Miner Reward.");
59 | message.setCid(message.genMsgCid());
60 | message.setPubKey(minerKey.getPubKey());
61 | message.setValue(Miner.MINING_REWARD);
62 |
63 | // sign the message
64 | String sign = Sign.sign(minerKey.getPriKey(), message.toSigned());
65 | message.setSign(sign);
66 | newBlock.addMessage(message);
67 |
68 | // sign the block
69 | newBlock.setPubKey(minerKey.getPubKey());
70 | String blockSig = Sign.sign(minerKey.getPriKey(), newBlock.genCid());
71 | newBlock.setBlockSign(blockSig);
72 |
73 | return newBlock;
74 | }
75 |
76 | // // create the genesis block
77 | // public Block createGenesisBlock() throws Exception
78 | // {
79 | // // create the default wallet
80 | // Wallet wallet = new Wallet();
81 | // walletService.setMinerWallet(wallet);
82 | // // init the reward address balance
83 | // Account rewardAccount = new Account(Miner.REWARD_ADDR, Miner.TOTAL_SUPPLY, null, 0);
84 | // accountService.setAccount(rewardAccount);
85 | //
86 | // // create the genesis message
87 | // Message message = new Message();
88 | // message.setFrom(Miner.REWARD_ADDR);
89 | // message.setTo(wallet.getAddress());
90 | // message.setParams("Miner Reward.");
91 | // message.setCid(message.genMsgCid());
92 | // message.setPubKey(wallet.getPubKey());
93 | // message.setValue(Miner.GENESIS_ACCOUNT_BALANCE);
94 | // // sign the message
95 | // String sign = Sign.sign(wallet.getPriKey(), message.toSigned());
96 | // message.setSign(sign);
97 | //
98 | // BlockHeader header = new BlockHeader(0, null);
99 | // header.setNonce(PowMiner.GENESIS_BLOCK_NONCE);
100 | // header.setDifficulty(ProofOfWork.getTarget());
101 | // header.setHash(header.genCid());
102 | //
103 | // Block block = new Block(header);
104 | // block.addMessage(message);
105 | //
106 | // // sign the block
107 | // block.setPubKey(wallet.getPubKey());
108 | // String blockSig = Sign.sign(wallet.getPriKey(), block.genCid());
109 | // block.setBlockSign(blockSig);
110 | //
111 | // return block;
112 | // }
113 |
114 | @Override
115 | public boolean validateBlock(Block block)
116 | {
117 | ProofOfWork proofOfWork = ProofOfWork.newProofOfWork(block.getHeader());
118 | return proofOfWork.validate();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/miner/pow/PowResult.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.miner.pow;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * PoW 计算结果
7 | * @author yangjian
8 | */
9 | public class PowResult {
10 |
11 | // pow value
12 | private final long nonce;
13 | // hash of the new block
14 | private final String hash;
15 | // pow target
16 | private final BigInteger target;
17 |
18 | public PowResult(long nonce, String hash, BigInteger target) {
19 | this.nonce = nonce;
20 | this.hash = hash;
21 | this.target = target;
22 | }
23 |
24 | public long getNonce() {
25 | return nonce;
26 | }
27 |
28 | public String getHash() {
29 | return hash;
30 | }
31 |
32 | public BigInteger getTarget() {
33 | return target;
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return "PowResult{" +
39 | "nonce=" + nonce +
40 | ", hash='" + hash + '\'' +
41 | ", target=" + target +
42 | '}';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/miner/pow/ProofOfWork.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.miner.pow;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.crypto.Hash;
5 | import org.rockyang.blockj.base.model.BlockHeader;
6 | import org.rockyang.blockj.base.utils.ByteUtils;
7 | import org.rockyang.blockj.base.utils.Numeric;
8 |
9 | import java.math.BigInteger;
10 |
11 | /**
12 | * 工作量证明实现
13 | *
14 | * @author yangjian
15 | */
16 | public class ProofOfWork {
17 |
18 | // 难度目标位, target=24 时大约 30 秒出一个区块
19 | public static final int TARGET_BITS = 12;
20 |
21 | private final BlockHeader blockHeader;
22 |
23 | // 难度目标值
24 | private final BigInteger target;
25 |
26 | /**
27 | * 创建新的工作量证明,设定难度目标值
28 | * 对1进行移位运算,将1向左移动 (256 - TARGET_BITS) 位,得到我们的难度目标值
29 | */
30 | public static ProofOfWork newProofOfWork(BlockHeader blockHeader)
31 | {
32 | BigInteger targetValue = BigInteger.valueOf(1).shiftLeft((256 - TARGET_BITS));
33 | return new ProofOfWork(blockHeader, targetValue);
34 | }
35 |
36 | private ProofOfWork(BlockHeader blockHeader, BigInteger target)
37 | {
38 | this.blockHeader = blockHeader;
39 | this.target = target;
40 | }
41 |
42 | // 运行工作量证明,开始挖矿,找到小于难度目标值的Hash
43 | public PowResult run()
44 | {
45 | long nonce = 0;
46 | String shaHex = "";
47 | while (nonce < Long.MAX_VALUE) {
48 | byte[] data = this.prepareData(nonce);
49 | shaHex = Hash.sha256Hex(data);
50 | if (new BigInteger(shaHex, 16).compareTo(this.target) < 0) {
51 | break;
52 | } else {
53 | nonce++;
54 | }
55 | }
56 | return new PowResult(nonce, shaHex, this.target);
57 | }
58 |
59 | // validate the pow result
60 | public boolean validate()
61 | {
62 | byte[] data = this.prepareData(this.blockHeader.getNonce());
63 | return new BigInteger(Hash.sha256Hex(data), 16).compareTo(this.target) < 0;
64 | }
65 |
66 | // 准备数据
67 | // 注意:在准备区块数据时,一定要从原始数据类型转化为byte[],不能直接从字符串进行转换
68 | private byte[] prepareData(long nonce)
69 | {
70 | byte[] prevBlockHashBytes = {};
71 | if (StringUtils.isNotBlank(this.blockHeader.getPreviousHash())) {
72 | //这里要去掉 hash 值的 0x 前缀, 否则会抛出异常
73 | String prevHash = Numeric.cleanHexPrefix(this.blockHeader.getPreviousHash());
74 | prevBlockHashBytes = new BigInteger(prevHash, 16).toByteArray();
75 | }
76 |
77 | return ByteUtils.merge(
78 | prevBlockHashBytes,
79 | ByteUtils.toBytes(this.blockHeader.getCreateTime()),
80 | ByteUtils.toBytes(TARGET_BITS),
81 | ByteUtils.toBytes(nonce)
82 | );
83 | }
84 |
85 | public static BigInteger getTarget()
86 | {
87 | return BigInteger.valueOf(1).shiftLeft((256 - TARGET_BITS));
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/base/BaseHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.base;
2 |
3 | import org.tio.core.ChannelContext;
4 | import org.tio.core.TioConfig;
5 | import org.tio.core.exception.TioDecodeException;
6 | import org.tio.core.intf.Packet;
7 | import org.tio.core.intf.TioHandler;
8 |
9 | import java.nio.ByteBuffer;
10 |
11 | /**
12 | * Tio handler 抽象类, 消息编码,解码的通用实现
13 | *
14 | * @author yangjian
15 | */
16 | public abstract class BaseHandler implements TioHandler
17 | {
18 |
19 | /**
20 | * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
21 | * 总的消息结构:消息头 + 消息类别 + 消息体
22 | * 消息头结构: 4个字节,存储消息体的长度
23 | * 消息类别: 1 个字节, 存储类别,S => 字符串, B => 区块, T => 交易
24 | * 消息体结构: 对象的json串的byte[]
25 | */
26 | public MessagePacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws TioDecodeException
27 | {
28 |
29 | //收到的数据组不了业务包,则返回null以告诉框架数据不够
30 | if (readableLength < MessagePacket.HEADER_LENGTH) {
31 | return null;
32 | }
33 | //读取消息类别
34 | byte messageType = buffer.get();
35 | //读取消息体的长度
36 | int bodyLength = buffer.getInt();
37 |
38 | //数据不正确,则抛出AioDecodeException异常
39 | if (bodyLength < 0) {
40 | throw new TioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
41 | }
42 | //计算本次需要的数据长度
43 | int neededLength = MessagePacket.HEADER_LENGTH + bodyLength;
44 | //收到的数据是否足够组包
45 | int isDataEnough = readableLength - neededLength;
46 | // 不够消息体长度(剩下的buffer组不了消息体)
47 | if (isDataEnough < 0) {
48 | return null;
49 | } else //组包成功
50 | {
51 | MessagePacket imPacket = new MessagePacket();
52 | imPacket.setType(messageType);
53 | if (bodyLength > 0) {
54 | byte[] dst = new byte[bodyLength];
55 | buffer.get(dst);
56 | imPacket.setBody(dst);
57 | }
58 | return imPacket;
59 | }
60 | }
61 |
62 | /**
63 | * 编码:把业务消息包编码为可以发送的ByteBuffer
64 | * 总的消息结构:消息头 + 消息类别 + 消息体
65 | * 消息头结构: 4个字节,存储消息体的长度
66 | * 消息类别: 1 个字节, 存储类别,S => 字符串, B => 区块, T => 交易
67 | * 消息体结构: 对象的json串的byte[]
68 | */
69 | public ByteBuffer encode(Packet packet, TioConfig config, ChannelContext channelContext)
70 | {
71 |
72 | MessagePacket messagePacket = (MessagePacket) packet;
73 | byte[] body = messagePacket.getBody();
74 | int bodyLen = 0;
75 | if (body != null) {
76 | bodyLen = body.length;
77 | }
78 |
79 | //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
80 | int allLen = MessagePacket.HEADER_LENGTH + bodyLen;
81 | //创建一个新的bytebuffer
82 | ByteBuffer buffer = ByteBuffer.allocate(allLen);
83 | //设置字节序
84 | buffer.order(config.getByteOrder());
85 |
86 | //写入消息类型
87 | buffer.put(messagePacket.getType());
88 | //写入消息头----消息头的内容就是消息体的长度
89 | buffer.putInt(bodyLen);
90 |
91 | //写入消息体
92 | if (body != null) {
93 | buffer.put(body);
94 | }
95 | return buffer;
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/base/MessagePacket.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.base;
2 |
3 | import org.tio.core.intf.Packet;
4 |
5 | /**
6 | * 网络消息数据包
7 | * @author yangjian
8 | */
9 | public class MessagePacket extends Packet {
10 |
11 | // defined the length of message header
12 | public static final int HEADER_LENGTH = 5;
13 | // say hello message
14 | public static final String HELLO_MESSAGE = "Hello blockj.";
15 |
16 | // message type, defined in class MessagePacketType
17 | private byte type;
18 |
19 | private byte[] body;
20 |
21 | public MessagePacket(byte[] body) {
22 | this.body = body;
23 | }
24 |
25 | public MessagePacket() {
26 | }
27 |
28 | public MessagePacket(byte type) {
29 | this.type = type;
30 | }
31 |
32 | public byte getType() {
33 | return type;
34 | }
35 |
36 | public void setType(byte type) {
37 | this.type = type;
38 | }
39 |
40 | public byte[] getBody() {
41 | return body;
42 | }
43 |
44 | public void setBody(byte[] body) {
45 | this.body = body;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/base/MessagePacketType.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.base;
2 |
3 | /**
4 | * Packet 消息类别, 请求为正数,响应为负数
5 | *
6 | * @author yangjian
7 | */
8 | public interface MessagePacketType {
9 |
10 | // hello message
11 | byte HELLO_MESSAGE = 0;
12 |
13 | // new block message request
14 | byte REQ_NEW_BLOCK = 1;
15 |
16 | // new block confirm
17 | byte RES_NEW_BLOCK = -1;
18 |
19 | // new message request
20 | byte REQ_NEW_MESSAGE = 2;
21 |
22 | // new message confirm
23 | byte RES_NEW_MESSAGE = -2;
24 |
25 | // block sync request
26 | byte REQ_BLOCK_SYNC = 3;
27 |
28 | // block sync response
29 | byte RES_BLOCK_SYNC = -3;
30 |
31 | // new peer connected request
32 | byte REQ_NEW_PEER = 4;
33 |
34 | // new peer connected response
35 | byte RES_NEW_PEER = -4;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/client/P2pClient.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.client;
2 |
3 | import org.apache.commons.codec.binary.StringUtils;
4 | import org.rockyang.blockj.base.model.Peer;
5 | import org.rockyang.blockj.base.utils.SerializeUtils;
6 | import org.rockyang.blockj.net.base.MessagePacket;
7 | import org.rockyang.blockj.net.base.MessagePacketType;
8 | import org.rockyang.blockj.net.conf.NetConfig;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.stereotype.Component;
12 | import org.tio.client.ClientChannelContext;
13 | import org.tio.client.ReconnConf;
14 | import org.tio.client.TioClient;
15 | import org.tio.client.TioClientConfig;
16 | import org.tio.core.Node;
17 | import org.tio.core.Tio;
18 |
19 | import javax.annotation.PostConstruct;
20 |
21 | /**
22 | * Tio client starter
23 | *
24 | * @author yangjian
25 | */
26 | @Component
27 | public class P2pClient {
28 |
29 | private static final Logger logger = LoggerFactory.getLogger(P2pClient.class);
30 |
31 | private TioClient client;
32 | private final NetConfig netConfig;
33 | private final TioClientConfig clientConfig;
34 |
35 | public P2pClient(NetConfig netConfig, P2pClientHandler clientHandler, P2pClientListener clientListener)
36 | {
37 | // set the auto reconnect
38 | ReconnConf reconnConf = new ReconnConf(5000L, 20);
39 | // init client config
40 | TioClientConfig clientConfig = new TioClientConfig(clientHandler, clientListener, reconnConf);
41 | // disable heartbeat from tio framework
42 | clientConfig.setHeartbeatTimeout(0);
43 | this.clientConfig = clientConfig;
44 | this.netConfig = netConfig;
45 | }
46 |
47 | @PostConstruct
48 | public void run() throws Exception
49 | {
50 | this.client = new TioClient(clientConfig);
51 | // try to connect the genesis node
52 | connect(new Node(netConfig.getGenesisAddress(), netConfig.getGenesisPort()));
53 | }
54 |
55 | public void sendGroup(MessagePacket messagePacket)
56 | {
57 | if (NetConfig.SERVERS.size() > 0) {
58 | Tio.sendToGroup(clientConfig, NetConfig.NODE_GROUP_NAME, messagePacket);
59 | }
60 | }
61 |
62 | // connect a new node
63 | public boolean connect(Node node) throws Exception
64 | {
65 | if (StringUtils.equals(node.getIp(), netConfig.getServerAddress()) && node.getPort() == netConfig.getServerPort()) {
66 | logger.info("skip self connections, {}", node.toString());
67 | return false;
68 | }
69 |
70 | if (NetConfig.SERVERS.containsKey(node)) {
71 | return false;
72 | }
73 |
74 | NetConfig.SERVERS.put(node, true);
75 | ClientChannelContext channelContext = client.connect(node);
76 |
77 | // send self server connection info
78 | Peer server = new Peer(netConfig.getServerAddress(), netConfig.getServerPort());
79 | MessagePacket packet = new MessagePacket();
80 | packet.setType(MessagePacketType.REQ_NEW_PEER);
81 | packet.setBody(SerializeUtils.serialize(server));
82 | Tio.send(channelContext, packet);
83 | return true;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/client/P2pClientHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.client;
2 |
3 | import org.rockyang.blockj.chain.sync.ClientHandler;
4 | import org.rockyang.blockj.net.base.BaseHandler;
5 | import org.rockyang.blockj.net.base.MessagePacket;
6 | import org.rockyang.blockj.net.base.MessagePacketType;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.stereotype.Component;
10 | import org.tio.client.intf.TioClientHandler;
11 | import org.tio.core.ChannelContext;
12 | import org.tio.core.intf.Packet;
13 |
14 | /**
15 | * @author yangjian
16 | */
17 | @Component
18 | public class P2pClientHandler extends BaseHandler implements TioClientHandler {
19 |
20 | private static final Logger logger = LoggerFactory.getLogger(P2pClientHandler.class);
21 | private final ClientHandler handler;
22 |
23 | public P2pClientHandler(ClientHandler handler)
24 | {
25 | this.handler = handler;
26 | }
27 |
28 | // message handler
29 | @Override
30 | public void handler(Packet packet, ChannelContext channelContext) throws Exception
31 | {
32 | MessagePacket messagePacket = (MessagePacket) packet;
33 | byte[] body = messagePacket.getBody();
34 | byte type = messagePacket.getType();
35 | if (body == null) {
36 | logger.debug("Invalid message, client: {}, drop it.", channelContext.getClientNode());
37 | return;
38 | }
39 |
40 | switch (type) {
41 | case MessagePacketType.RES_NEW_MESSAGE:
42 | handler.newMessage(body);
43 | break;
44 | case MessagePacketType.RES_BLOCK_SYNC:
45 | handler.syncBlock(body);
46 | break;
47 | case MessagePacketType.RES_NEW_BLOCK:
48 | handler.newBlock(body);
49 | break;
50 | }
51 | }
52 |
53 | public Packet heartbeatPacket(ChannelContext context)
54 | {
55 | // disable heartbeat from tio framework
56 | return null;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/client/P2pClientListener.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.client;
2 |
3 | import org.rockyang.blockj.net.conf.NetConfig;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.stereotype.Component;
7 | import org.tio.client.intf.TioClientListener;
8 | import org.tio.core.ChannelContext;
9 | import org.tio.core.Tio;
10 | import org.tio.core.intf.Packet;
11 |
12 | /**
13 | * @author yangjian
14 | */
15 | @Component
16 | public class P2pClientListener implements TioClientListener {
17 |
18 | private static final Logger logger = LoggerFactory.getLogger(P2pClientListener.class);
19 |
20 | @Override
21 | public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect)
22 | {
23 | if (isConnected) {
24 | logger.info("Connect server {} successfully", channelContext.getServerNode());
25 | // bind peer to group
26 | Tio.bindGroup(channelContext, NetConfig.NODE_GROUP_NAME);
27 | }
28 | }
29 |
30 | @Override
31 | public void onAfterDecoded(ChannelContext channelContext, Packet packet, int i)
32 | {
33 |
34 | }
35 |
36 | @Override
37 | public void onAfterReceivedBytes(ChannelContext channelContext, int i)
38 | {
39 |
40 | }
41 |
42 |
43 | @Override
44 | public void onAfterSent(ChannelContext channelContext, Packet packet, boolean b)
45 | {
46 |
47 | }
48 |
49 | @Override
50 | public void onAfterHandled(ChannelContext channelContext, Packet packet, long l)
51 | {
52 |
53 | }
54 |
55 | @Override
56 | public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String s, boolean b)
57 | {
58 | Tio.unbindGroup(NetConfig.NODE_GROUP_NAME, channelContext);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/conf/NetConfig.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.conf;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.stereotype.Component;
5 | import org.tio.core.Node;
6 |
7 | import java.util.Map;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | /**
11 | * @author yangjian
12 | */
13 | @Component
14 | public class NetConfig {
15 |
16 | public static final String SERVER_NAME = "blockj-tio-server";
17 | public static final String NODE_GROUP_NAME = "blockj-nodes";
18 | public static final Map SERVERS = new ConcurrentHashMap<>(16);
19 |
20 | @Value("${p2p.address}")
21 | private String serverAddress;
22 | @Value("${p2p.port}")
23 | private int serverPort;
24 | @Value("${genesis.address}")
25 | private String genesisAddress;
26 | @Value("${genesis.port}")
27 | private int genesisPort;
28 |
29 | public String getServerAddress()
30 | {
31 | return serverAddress;
32 | }
33 |
34 | public void setServerAddress(String serverAddress)
35 | {
36 | this.serverAddress = serverAddress;
37 | }
38 |
39 | public int getServerPort()
40 | {
41 | return serverPort;
42 | }
43 |
44 | public void setServerPort(int serverPort)
45 | {
46 | this.serverPort = serverPort;
47 | }
48 |
49 | public String getGenesisAddress()
50 | {
51 | return genesisAddress;
52 | }
53 |
54 | public void setGenesisAddress(String genesisAddress)
55 | {
56 | this.genesisAddress = genesisAddress;
57 | }
58 |
59 | public int getGenesisPort()
60 | {
61 | return genesisPort;
62 | }
63 |
64 | public void setGenesisPort(int genesisPort)
65 | {
66 | this.genesisPort = genesisPort;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/server/P2pServer.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.server;
2 |
3 | import org.rockyang.blockj.net.conf.NetConfig;
4 | import org.springframework.stereotype.Component;
5 | import org.tio.server.TioServer;
6 | import org.tio.server.TioServerConfig;
7 |
8 | import javax.annotation.PostConstruct;
9 | import java.io.IOException;
10 |
11 | /**
12 | * Tio server starter
13 | *
14 | * @author yangjian
15 | */
16 | @Component
17 | public class P2pServer {
18 |
19 | private final TioServerConfig serverConfig;
20 | private final NetConfig netConfig;
21 |
22 | public P2pServer(NetConfig netConfig, P2pServerHandler serverHandler, P2pServerListener serverListener)
23 | {
24 | TioServerConfig serverConfig = new TioServerConfig(NetConfig.SERVER_NAME, serverHandler, serverListener);
25 | // disable heartbeat from tio framework
26 | serverConfig.setHeartbeatTimeout(0);
27 | this.serverConfig = serverConfig;
28 | this.netConfig = netConfig;
29 | }
30 |
31 | @PostConstruct
32 | public void start() throws IOException
33 | {
34 | TioServer server = new TioServer(serverConfig);
35 | server.start(netConfig.getServerAddress(), netConfig.getServerPort());
36 | }
37 | }
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/server/P2pServerHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.server;
2 |
3 | import org.rockyang.blockj.base.utils.SerializeUtils;
4 | import org.rockyang.blockj.chain.sync.ServerHandler;
5 | import org.rockyang.blockj.net.base.BaseHandler;
6 | import org.rockyang.blockj.net.base.MessagePacket;
7 | import org.rockyang.blockj.net.base.MessagePacketType;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.stereotype.Component;
11 | import org.tio.core.ChannelContext;
12 | import org.tio.core.Tio;
13 | import org.tio.core.intf.Packet;
14 | import org.tio.server.intf.TioServerHandler;
15 |
16 | /**
17 | * @author yangjian
18 | */
19 | @Component
20 | public class P2pServerHandler extends BaseHandler implements TioServerHandler {
21 |
22 | private static final Logger logger = LoggerFactory.getLogger(P2pServerHandler.class);
23 |
24 | private final ServerHandler handler;
25 |
26 | public P2pServerHandler(ServerHandler response)
27 | {
28 | this.handler = response;
29 | }
30 |
31 | // message handler for socket server
32 | @Override
33 | public void handler(Packet packet, ChannelContext channelContext) throws Exception
34 | {
35 | MessagePacket messagePacket = (MessagePacket) packet;
36 | byte type = messagePacket.getType();
37 | byte[] body = messagePacket.getBody();
38 | if (body == null) {
39 | logger.debug("Invalid message, client: {}, drop it.", channelContext.getClientNode());
40 | return;
41 | }
42 |
43 | MessagePacket resPacket = null;
44 | switch (type) {
45 | case MessagePacketType.HELLO_MESSAGE:
46 | logger.info("hello message: {}", SerializeUtils.unSerialize(body));
47 | break;
48 | case MessagePacketType.REQ_BLOCK_SYNC:
49 | resPacket = handler.syncBlock(body);
50 | break;
51 | case MessagePacketType.REQ_NEW_BLOCK:
52 | resPacket = handler.newBlock(body);
53 | break;
54 | case MessagePacketType.REQ_NEW_MESSAGE:
55 | resPacket = handler.newMessage(body);
56 | break;
57 | case MessagePacketType.REQ_NEW_PEER:
58 | resPacket = handler.newPeer(body);
59 | break;
60 | }
61 |
62 | if (resPacket != null) {
63 | Tio.send(channelContext, resPacket);
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/net/server/P2pServerListener.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.net.server;
2 |
3 | import org.rockyang.blockj.chain.event.SyncBlockEvent;
4 | import org.rockyang.blockj.conf.ApplicationContextProvider;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.stereotype.Component;
8 | import org.tio.core.ChannelContext;
9 | import org.tio.core.intf.Packet;
10 | import org.tio.server.intf.TioServerListener;
11 |
12 | /**
13 | * @author yangjian
14 | */
15 | @Component
16 | public class P2pServerListener implements TioServerListener {
17 |
18 | private static final Logger logger = LoggerFactory.getLogger(P2pServerListener.class);
19 |
20 | @Override
21 | public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect)
22 | {
23 | if (isConnected) {
24 | logger.info("New client connected: {}", channelContext.getClientNode());
25 | // start to sync block
26 | ApplicationContextProvider.publishEvent(new SyncBlockEvent(0));
27 | }
28 | }
29 |
30 | @Override
31 | public void onAfterDecoded(ChannelContext channelContext, Packet packet, int i)
32 | {
33 |
34 | }
35 |
36 | @Override
37 | public void onAfterReceivedBytes(ChannelContext channelContext, int i)
38 | {
39 |
40 | }
41 |
42 | @Override
43 | public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess)
44 | {
45 |
46 | }
47 |
48 | @Override
49 | public void onAfterHandled(ChannelContext channelContext, Packet packet, long l)
50 | {
51 |
52 | }
53 |
54 | @Override
55 | public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove)
56 | {
57 |
58 | }
59 |
60 | @Override
61 | public boolean onHeartbeatTimeout(ChannelContext channelContext, Long aLong, int i)
62 | {
63 | return true;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/AccountService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service;
2 |
3 | import org.rockyang.blockj.base.model.Account;
4 |
5 | import java.math.BigDecimal;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public interface AccountService {
11 | String ACCOUNT_PREFIX = "/accounts/";
12 |
13 | BigDecimal getBalance(String address);
14 |
15 | void addBalance(String address, BigDecimal value);
16 |
17 | void subBalance(String address, BigDecimal value);
18 |
19 | void addMessageNonce(String address, long value);
20 |
21 | boolean setAccount(Account account);
22 |
23 | Account getAccount(String address);
24 | }
25 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/BlockService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.vo.Result;
5 |
6 | /**
7 | * @author yangjian
8 | */
9 | public interface BlockService {
10 |
11 | String CHAIN_HEAD_KEY = "/block/head";
12 | String BLOCK_PREFIX = "/blocks/";
13 | String BLOCK_HEIGHT_PREFIX = "/blocks/height/";
14 | String BLOCK_MESSAGE_PREFIX = "/block/message/";
15 |
16 | // get chain head block hash
17 | long chainHead();
18 |
19 | // set chain head block hash
20 | boolean setChainHead(long height);
21 |
22 | // store the block
23 | boolean addBlock(Block block);
24 |
25 | // remove block from store
26 | boolean deleteBlock(String blockHash);
27 |
28 | // get block with the specified block hash
29 | Block getBlock(String blockHash);
30 |
31 | // get block by height index
32 | Block getBlockByHeight(long height);
33 |
34 | // save block and execute messages in block
35 | boolean markBlockAsValidated(Block block);
36 |
37 | // delete block and reverse the messages
38 | boolean unmarkBlockAsValidated(String blockHash);
39 |
40 | // check if the block is validated
41 | boolean isBlockValidated(long height);
42 |
43 | boolean isBlockValidated(String blockHash);
44 |
45 | Result checkBlock(Block block) throws Exception;
46 | }
47 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/MessageService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service;
2 |
3 | import org.rockyang.blockj.base.model.Message;
4 |
5 | import java.math.BigDecimal;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public interface MessageService {
11 | String MESSAGE_PREFIX = "/messages/";
12 |
13 | boolean addMessage(Message message);
14 |
15 | // get message with the specified message Cid
16 | Message getMessage(String cid);
17 |
18 | boolean validateMessage(Message message);
19 |
20 | // send a message and return the message Cid
21 | String sendMessage(String from, String to, BigDecimal value, String param) throws Exception;
22 | }
23 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/PeerService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service;
2 |
3 | import org.rockyang.blockj.base.model.Peer;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public interface PeerService {
11 | String PEER_PREFIX = "/peers/";
12 |
13 | boolean addPeer(Peer peer);
14 |
15 | List getPeers();
16 |
17 | boolean removePeer(Peer peer);
18 |
19 | boolean hasPeer(Peer peer);
20 | }
21 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/WalletService.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service;
2 |
3 | import org.rockyang.blockj.base.model.Wallet;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public interface WalletService
11 | {
12 | String WALLET_PREFIX = "/wallets/";
13 | String MINER_ADDR_KEY = "/wallet/miner";
14 | String DEFAULT_ADDR_KEY = "/wallet/default";
15 |
16 | boolean addWallet(Wallet wallet);
17 |
18 | List getWallets();
19 |
20 | Wallet getWallet(String address);
21 |
22 | Wallet getMinerWallet();
23 |
24 | boolean setMinerWallet(Wallet wallet);
25 |
26 | Wallet getDefaultWallet();
27 |
28 | boolean setDefaultWallet(String address);
29 |
30 | boolean deleteWallet(String address);
31 | }
32 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/impl/AccountServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service.impl;
2 |
3 | import org.rockyang.blockj.base.model.Account;
4 | import org.rockyang.blockj.base.store.Datastore;
5 | import org.rockyang.blockj.service.AccountService;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.math.BigDecimal;
9 | import java.util.Optional;
10 | import java.util.concurrent.locks.Lock;
11 | import java.util.concurrent.locks.ReentrantReadWriteLock;
12 |
13 | /**
14 | * @author yangjian
15 | */
16 | @Service
17 | public class AccountServiceImpl implements AccountService {
18 |
19 | private final Datastore datastore;
20 |
21 | private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
22 | private final Lock readLock = rwl.readLock();
23 | private final Lock writeLock = rwl.writeLock();
24 |
25 | public AccountServiceImpl(Datastore datastore)
26 | {
27 | this.datastore = datastore;
28 | }
29 |
30 | @Override
31 | public BigDecimal getBalance(String address)
32 | {
33 | readLock.lock();
34 | Optional o = datastore.get(ACCOUNT_PREFIX + address);
35 | readLock.unlock();
36 | return o.map(a -> ((Account) a).getBalance()).orElse(BigDecimal.ZERO);
37 | }
38 |
39 | @Override
40 | public void addBalance(String address, BigDecimal value)
41 | {
42 | writeLock.lock();
43 | try {
44 | Account account = getAccount(address);
45 | if (account == null) {
46 | return;
47 | }
48 | account.setBalance(account.getBalance().add(value));
49 | datastore.put(ACCOUNT_PREFIX + account.getAddress(), account);
50 | } finally {
51 | writeLock.unlock();
52 | }
53 | }
54 |
55 | @Override
56 | public void subBalance(String address, BigDecimal value)
57 | {
58 | writeLock.lock();
59 | try {
60 | Account account = getAccount(address);
61 | if (account == null) {
62 | return;
63 | }
64 | account.setBalance(account.getBalance().subtract(value));
65 | datastore.put(ACCOUNT_PREFIX + account.getAddress(), account);
66 | } finally {
67 | writeLock.unlock();
68 | }
69 | }
70 |
71 | @Override
72 | public void addMessageNonce(String address, long value)
73 | {
74 | writeLock.lock();
75 | Account account = getAccount(address);
76 | account.setMessageNonce(account.getMessageNonce() + value);
77 | setAccount(account);
78 | writeLock.unlock();
79 | }
80 |
81 | @Override
82 | public boolean setAccount(Account account)
83 | {
84 | writeLock.lock();
85 | try {
86 | return datastore.put(ACCOUNT_PREFIX + account.getAddress(), account);
87 | } finally {
88 | writeLock.unlock();
89 | }
90 | }
91 |
92 | @Override
93 | public Account getAccount(String address)
94 | {
95 | readLock.lock();
96 | Optional o = datastore.get(ACCOUNT_PREFIX + address);
97 | readLock.unlock();
98 | return (Account) o.orElse(null);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/impl/MessageServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service.impl;
2 |
3 | import com.google.common.base.Preconditions;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.rockyang.blockj.base.crypto.Keys;
6 | import org.rockyang.blockj.base.crypto.Sign;
7 | import org.rockyang.blockj.base.enums.MessageStatus;
8 | import org.rockyang.blockj.base.model.Account;
9 | import org.rockyang.blockj.base.model.Message;
10 | import org.rockyang.blockj.base.model.Wallet;
11 | import org.rockyang.blockj.base.store.Datastore;
12 | import org.rockyang.blockj.chain.MessagePool;
13 | import org.rockyang.blockj.chain.event.NewMessageEvent;
14 | import org.rockyang.blockj.conf.ApplicationContextProvider;
15 | import org.rockyang.blockj.miner.Miner;
16 | import org.rockyang.blockj.service.AccountService;
17 | import org.rockyang.blockj.service.BlockService;
18 | import org.rockyang.blockj.service.MessageService;
19 | import org.rockyang.blockj.service.WalletService;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import org.springframework.stereotype.Service;
23 |
24 | import java.math.BigDecimal;
25 |
26 | /**
27 | * @author yangjian
28 | */
29 | @Service
30 | public class MessageServiceImpl implements MessageService
31 | {
32 |
33 | private final static Logger logger = LoggerFactory.getLogger(BlockService.class);
34 |
35 | private final Datastore datastore;
36 | private final AccountService accountService;
37 | private final WalletService walletService;
38 | private final MessagePool messagePool;
39 |
40 | public MessageServiceImpl(Datastore datastore,
41 | AccountService accountService,
42 | WalletService walletService,
43 | MessagePool messagePool)
44 | {
45 | this.datastore = datastore;
46 | this.accountService = accountService;
47 | this.walletService = walletService;
48 | this.messagePool = messagePool;
49 | }
50 |
51 | @Override
52 | public boolean addMessage(Message message)
53 | {
54 | return datastore.put(MESSAGE_PREFIX + message.getCid(), message);
55 | }
56 |
57 | @Override
58 | public Message getMessage(String cid)
59 | {
60 | return (Message) datastore.get(MESSAGE_PREFIX + cid).orElse(null);
61 | }
62 |
63 | @Override
64 | public synchronized boolean validateMessage(Message message)
65 | {
66 | Account recipient = accountService.getAccount(message.getTo());
67 |
68 | // init the new address
69 | if (recipient == null) {
70 | logger.info("save a new address: {}", message.getTo());
71 | recipient = new Account(message.getTo(), BigDecimal.ZERO, null, 0);
72 | accountService.setAccount(recipient);
73 | }
74 | // mining reward message
75 | if (message.getFrom().equals(Miner.REWARD_ADDR)) {
76 | accountService.setAccount(new Account(message.getFrom(), Miner.TOTAL_SUPPLY, null, 0));
77 | return true;
78 | }
79 |
80 | // transfer balance
81 | Account sender = accountService.getAccount(message.getFrom());
82 | if (sender == null) {
83 | logger.info("Keys not exists {}", message.getFrom());
84 | message.setStatus(MessageStatus.FAIL);
85 | return false;
86 | }
87 |
88 | // init the public key?
89 | if (StringUtils.isEmpty(sender.getPubKey())) {
90 | sender.setPubKey(message.getPubKey());
91 | accountService.setAccount(sender);
92 | }
93 |
94 | // check the sign
95 | try {
96 | boolean verify = Sign.verify(Keys.publicKeyDecode(message.getPubKey()), message.getSign(), message.toSigned());
97 | if (!verify) {
98 | message.setStatus(MessageStatus.INVALID_SIGN);
99 | return false;
100 | }
101 | } catch (Exception e) {
102 | message.setStatus(MessageStatus.INVALID_SIGN);
103 | return false;
104 | }
105 |
106 | // check if the account had enough balance
107 | if (sender.getBalance().compareTo(message.getValue()) < 0) {
108 | message.setStatus(MessageStatus.INSUFFICIENT_BALANCE);
109 | return false;
110 | }
111 | return true;
112 | }
113 |
114 | public synchronized String sendMessage(String from, String to, BigDecimal value, String param) throws Exception
115 | {
116 |
117 | // check the address
118 | Preconditions.checkArgument(to.startsWith("0x"), "Invalid format for recipient: " + from);
119 | Preconditions.checkArgument(!StringUtils.equals(from, to), "The sender and recipient is the same address");
120 |
121 | BigDecimal balance = accountService.getBalance(from);
122 | if (balance.compareTo(value) <= 0) {
123 | throw new RuntimeException("insufficient balance of the sender");
124 | }
125 | // load the wallet key for from address
126 | Wallet senderKeys = Preconditions.checkNotNull(walletService.getWallet(from), "local keys not exists: " + from);
127 | Account sender = Preconditions.checkNotNull(accountService.getAccount(from), "keys not exists: " + from);
128 |
129 | // build the message
130 | Message message = new Message(from, to, value, sender.getMessageNonce() + 1);
131 | message.setStatus(MessageStatus.APPENDING);
132 | message.setParams(param);
133 | message.setPubKey(senderKeys.getPubKey());
134 | message.setStatus(MessageStatus.APPENDING);
135 | message.setCid(message.genMsgCid());
136 |
137 | if (messagePool.hasMessage(message)) {
138 | throw new RuntimeException("message is exists, do not resend it");
139 | }
140 |
141 | // @TODO: check the wallet nonce > message nonce
142 |
143 | // sign the message
144 | String sign = Sign.sign(senderKeys.getPriKey(), message.toSigned());
145 | message.setSign(sign);
146 |
147 | // check the signature
148 | if (!Sign.verify(Keys.publicKeyDecode(message.getPubKey()), sign, message.toSigned())) {
149 | throw new RuntimeException("signature verification failed.");
150 | }
151 | // appending to message pool
152 | messagePool.pendingMessage(message);
153 |
154 | ApplicationContextProvider.publishEvent(new NewMessageEvent(message));
155 | return message.getCid();
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/impl/PeerServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service.impl;
2 |
3 | import org.rockyang.blockj.base.model.Peer;
4 | import org.rockyang.blockj.base.store.Datastore;
5 | import org.rockyang.blockj.service.PeerService;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * @author yangjian
12 | */
13 | @Service
14 | public class PeerServiceImpl implements PeerService {
15 |
16 | private final Datastore datastore;
17 |
18 | public PeerServiceImpl(Datastore datastore)
19 | {
20 | this.datastore = datastore;
21 | }
22 |
23 | @Override
24 | public boolean addPeer(Peer peer)
25 | {
26 | return datastore.put(PEER_PREFIX + peer.toString(), peer);
27 | }
28 |
29 | @Override
30 | public List getPeers()
31 | {
32 | return datastore.search(PEER_PREFIX);
33 | }
34 |
35 | @Override
36 | public boolean removePeer(Peer peer)
37 | {
38 | return datastore.delete(peer.toString());
39 | }
40 |
41 | @Override
42 | public boolean hasPeer(Peer peer)
43 | {
44 | return datastore.get(PEER_PREFIX + peer.toString()).isPresent();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/service/impl/WalletServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.service.impl;
2 |
3 | import org.rockyang.blockj.base.model.Wallet;
4 | import org.rockyang.blockj.base.store.Datastore;
5 | import org.rockyang.blockj.service.WalletService;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 |
11 | /**
12 | * @author yangjian
13 | */
14 | @Service
15 | public class WalletServiceImpl implements WalletService
16 | {
17 |
18 | private final Datastore datastore;
19 |
20 | public WalletServiceImpl(Datastore datastore)
21 | {
22 | this.datastore = datastore;
23 | }
24 |
25 | @Override
26 | public boolean addWallet(Wallet wallet)
27 | {
28 | return datastore.put(WALLET_PREFIX + wallet.getAddress(), wallet);
29 | }
30 |
31 | @Override
32 | public List getWallets()
33 | {
34 | return datastore.search(WALLET_PREFIX);
35 | }
36 |
37 | @Override
38 | public Wallet getWallet(String address)
39 | {
40 | return (Wallet) datastore.get(WALLET_PREFIX + address).orElse(null);
41 | }
42 |
43 | @Override
44 | public Wallet getMinerWallet()
45 | {
46 | Optional o = datastore.get(MINER_ADDR_KEY);
47 | return o.map(address -> getWallet(String.valueOf(address))).orElse(null);
48 | }
49 |
50 | @Override
51 | public boolean setMinerWallet(Wallet wallet)
52 | {
53 | if (!addWallet(wallet)) {
54 | return false;
55 | }
56 | return datastore.put(MINER_ADDR_KEY, wallet.getAddress());
57 | }
58 |
59 | @Override
60 | public Wallet getDefaultWallet()
61 | {
62 | return (Wallet) datastore.get(DEFAULT_ADDR_KEY).orElse(null);
63 | }
64 |
65 | @Override
66 | public boolean setDefaultWallet(String address)
67 | {
68 | return datastore.put(DEFAULT_ADDR_KEY, address);
69 | }
70 |
71 | @Override
72 | public boolean deleteWallet(String address)
73 | {
74 | return datastore.delete(WALLET_PREFIX + address);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/vo/PacketVo.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.vo;
2 |
3 | /**
4 | * P2P packet data transfer VO
5 | * @author yangjian
6 | */
7 | public class PacketVo {
8 |
9 | private Object item;
10 | // operation result
11 | private boolean success = false;
12 | // error message
13 | private String message;
14 |
15 | public PacketVo() {
16 | }
17 |
18 | public PacketVo(Object item, boolean status) {
19 | this.item = item;
20 | this.success = status;
21 | }
22 |
23 | public Object getItem() {
24 | return item;
25 | }
26 |
27 | public void setItem(Object item) {
28 | this.item = item;
29 | }
30 |
31 | public boolean isSuccess() {
32 | return success;
33 | }
34 |
35 | public void setSuccess(boolean success) {
36 | this.success = success;
37 | }
38 |
39 | public String getMessage() {
40 | return message;
41 | }
42 |
43 | public void setMessage(String message) {
44 | this.message = message;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/vo/Result.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.vo;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * @author yangjian
7 | */
8 | public class Result implements Serializable {
9 |
10 | private boolean ok;
11 | private String Message;
12 | public static final Result OK = new Result(true);
13 |
14 | public Result()
15 | {
16 | }
17 |
18 | public Result(boolean ok)
19 | {
20 | this.ok = ok;
21 | }
22 |
23 | public Result(boolean ok, String message)
24 | {
25 | this.ok = ok;
26 | Message = message;
27 | }
28 |
29 | public boolean isOk()
30 | {
31 | return ok;
32 | }
33 |
34 | public void setOk(boolean ok)
35 | {
36 | this.ok = ok;
37 | }
38 |
39 | public String getMessage()
40 | {
41 | return Message;
42 | }
43 |
44 | public void setMessage(String message)
45 | {
46 | Message = message;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/web/controller/ChainController.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.web.controller;
2 |
3 | import org.rockyang.blockj.base.model.Block;
4 | import org.rockyang.blockj.base.vo.JsonVo;
5 | import org.rockyang.blockj.service.BlockService;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | /**
12 | * chain api handler
13 | */
14 | @RestController
15 | @RequestMapping("/chain")
16 | public class ChainController
17 | {
18 | private final BlockService blockService;
19 |
20 | public ChainController(BlockService blockService)
21 | {
22 | this.blockService = blockService;
23 | }
24 |
25 | @GetMapping("/head")
26 | public JsonVo head()
27 | {
28 | long head = blockService.chainHead();
29 | if (head < 0) {
30 | return new JsonVo(JsonVo.FAIL, "Invalid chain head found");
31 | }
32 |
33 | return new JsonVo<>(JsonVo.SUCCESS, head);
34 | }
35 |
36 | @GetMapping("/block/{height}")
37 | public JsonVo getBlock(@PathVariable Long height)
38 | {
39 | if (height <= 0) {
40 | return new JsonVo(JsonVo.FAIL, "Invalid block height");
41 | }
42 | Block block = blockService.getBlockByHeight(height);
43 |
44 | if (block == null) {
45 | return new JsonVo(JsonVo.FAIL, "Block not found");
46 | }
47 |
48 | return new JsonVo<>(JsonVo.SUCCESS, block);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/web/controller/MessageController.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.web.controller;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.rockyang.blockj.base.model.Message;
5 | import org.rockyang.blockj.base.model.Wallet;
6 | import org.rockyang.blockj.base.vo.JsonVo;
7 | import org.rockyang.blockj.chain.MessagePool;
8 | import org.rockyang.blockj.service.MessageService;
9 | import org.rockyang.blockj.service.WalletService;
10 | import org.springframework.web.bind.annotation.*;
11 |
12 | import java.math.BigDecimal;
13 |
14 | /**
15 | * @author yangjian
16 | */
17 | @RestController
18 | @RequestMapping("/message")
19 | public class MessageController
20 | {
21 |
22 | private final MessageService messageService;
23 | private final MessagePool messagePool;
24 | private final WalletService walletService;
25 |
26 | public MessageController(
27 | MessageService messageService,
28 | MessagePool messagePool,
29 | WalletService walletService)
30 | {
31 | this.messageService = messageService;
32 | this.messagePool = messagePool;
33 | this.walletService = walletService;
34 | }
35 |
36 | @GetMapping("/get/{cid}")
37 | public JsonVo getMessage(@PathVariable String cid)
38 | {
39 | if (StringUtils.isBlank(cid)) {
40 | return new JsonVo(JsonVo.FAIL, "Must pass message cid");
41 | }
42 |
43 | // 1. search message in leveldb
44 | // 2. search message in message pool
45 | Message message = messageService.getMessage(cid);
46 | if (message == null) {
47 | message = messagePool.getMessage(cid);
48 | }
49 |
50 | if (message == null) {
51 | return new JsonVo(JsonVo.FAIL, "No message found");
52 | } else {
53 | return new JsonVo<>(JsonVo.SUCCESS, message);
54 | }
55 | }
56 |
57 | @PostMapping("/send")
58 | public JsonVo sendMessage(
59 | @RequestParam("from") String from,
60 | @RequestParam("to") String to,
61 | @RequestParam("value") BigDecimal value,
62 | @RequestParam(value = "param", required = false) String param
63 | ) throws Exception
64 | {
65 | // 如果没有传入 from 地址,则使用默认的钱包地址
66 | if (StringUtils.isBlank(from)) {
67 | Wallet defaultWallet = walletService.getDefaultWallet();
68 | if (defaultWallet == null) {
69 | return new JsonVo(JsonVo.FAIL, "default wallet is not set, you need to pass the from address");
70 | }
71 | from = defaultWallet.getAddress();
72 | }
73 |
74 | if (StringUtils.isBlank(to)) {
75 | new JsonVo(JsonVo.FAIL, "must pass the to address");
76 | }
77 |
78 | if (value.compareTo(BigDecimal.ZERO) <= 0) {
79 | return new JsonVo(JsonVo.FAIL, "the value of send amount must > 0");
80 | }
81 |
82 | String cid = messageService.sendMessage(from, to, value, param);
83 | return new JsonVo<>(JsonVo.SUCCESS, "", cid);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/web/controller/NetController.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.web.controller;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.rockyang.blockj.base.model.Peer;
6 | import org.rockyang.blockj.base.vo.JsonVo;
7 | import org.rockyang.blockj.chain.event.NewPeerEvent;
8 | import org.rockyang.blockj.conf.ApplicationContextProvider;
9 | import org.rockyang.blockj.net.client.P2pClient;
10 | import org.rockyang.blockj.net.conf.NetConfig;
11 | import org.rockyang.blockj.service.PeerService;
12 | import org.springframework.web.bind.annotation.*;
13 | import org.tio.core.Node;
14 |
15 | import java.util.List;
16 |
17 | /**
18 | * chain api handler
19 | */
20 | @RestController
21 | @RequestMapping("/net")
22 | public class NetController
23 | {
24 | private final PeerService peerService;
25 | private final NetConfig netConfig;
26 | private final P2pClient client;
27 |
28 | public NetController(PeerService peerService, NetConfig netConfig, P2pClient client)
29 | {
30 | this.peerService = peerService;
31 | this.netConfig = netConfig;
32 | this.client = client;
33 | }
34 |
35 | @GetMapping("/peers")
36 | public JsonVo peers()
37 | {
38 | List peers = peerService.getPeers();
39 | if (peers == null || peers.size() == 0) {
40 | return new JsonVo<>(JsonVo.FAIL, "No peer found.");
41 | } else {
42 | return new JsonVo<>(JsonVo.SUCCESS, peers);
43 | }
44 |
45 | }
46 |
47 | @GetMapping("/listen")
48 | public JsonVo listen()
49 | {
50 | String listen = String.format("%s:%d", netConfig.getServerAddress(), netConfig.getServerPort());
51 | return new JsonVo<>(JsonVo.SUCCESS, "", listen);
52 | }
53 |
54 | @PostMapping("/connect")
55 | public JsonVo connect(@RequestBody JSONObject params) throws Exception
56 | {
57 | String address = params.getString("address");
58 | if (StringUtils.isBlank(address)) {
59 | return new JsonVo(JsonVo.FAIL, "Invalid peer address");
60 | }
61 |
62 | String[] split = address.split(":");
63 | if (split.length != 2) {
64 | return new JsonVo(JsonVo.FAIL, "Invalid format for peer address");
65 | }
66 |
67 | String ip = split[0];
68 | int port = Integer.parseInt(split[1]);
69 | Peer peer = new Peer(ip, port);
70 | if (peerService.hasPeer(peer)) {
71 | return new JsonVo(JsonVo.FAIL, String.format("Peer %s is already connected", peer));
72 | }
73 |
74 | // store peer
75 | peerService.addPeer(peer);
76 | // try to connect peer
77 | if (client.connect(new Node(peer.getIp(), peer.getPort()))) {
78 | // fire new peer connected event
79 | ApplicationContextProvider.publishEvent(new NewPeerEvent(peer));
80 | }
81 |
82 | return new JsonVo<>(JsonVo.SUCCESS, "", String.format("Connected peer %s successfully", peer));
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/web/controller/WalletController.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.web.controller;
2 |
3 | import org.rockyang.blockj.base.crypto.ECKeyPair;
4 | import org.rockyang.blockj.base.crypto.MnemonicUtils;
5 | import org.rockyang.blockj.base.crypto.SecureRandomUtils;
6 | import org.rockyang.blockj.base.model.Account;
7 | import org.rockyang.blockj.base.model.Wallet;
8 | import org.rockyang.blockj.base.vo.JsonVo;
9 | import org.rockyang.blockj.base.vo.MnemonicWallet;
10 | import org.rockyang.blockj.service.AccountService;
11 | import org.rockyang.blockj.service.WalletService;
12 | import org.springframework.web.bind.annotation.*;
13 |
14 | import java.math.BigDecimal;
15 | import java.util.List;
16 |
17 | import static org.rockyang.blockj.base.crypto.Hash.sha256;
18 |
19 | /**
20 | * @author yangjian
21 | */
22 | @RestController
23 | @RequestMapping("/wallet")
24 | public class WalletController
25 | {
26 |
27 | private final WalletService walletService;
28 | private final AccountService accountService;
29 |
30 | public WalletController(WalletService walletService, AccountService accountService)
31 | {
32 | this.walletService = walletService;
33 | this.accountService = accountService;
34 | }
35 |
36 | @RequestMapping("/new")
37 | public JsonVo newWallet() throws Exception
38 | {
39 | Wallet wallet = new Wallet();
40 | walletService.addWallet(wallet);
41 | return new JsonVo<>(JsonVo.SUCCESS, wallet);
42 | }
43 |
44 | @PostMapping("/new/mnemonic")
45 | public JsonVo newMnemonicWallet(@RequestParam("password") String password) throws Exception
46 | {
47 | byte[] initialEntropy = new byte[16];
48 | SecureRandomUtils.secureRandom().nextBytes(initialEntropy);
49 |
50 | String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
51 | byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
52 | ECKeyPair privateKey = ECKeyPair.create(sha256(seed));
53 |
54 | Wallet wallet = new Wallet(privateKey);
55 | walletService.addWallet(wallet);
56 |
57 | return new JsonVo<>(JsonVo.SUCCESS, new MnemonicWallet(mnemonic, wallet));
58 | }
59 |
60 | @RequestMapping("/list")
61 | public JsonVo walletList()
62 | {
63 | // @Note: we only return the local wallet infos
64 | // should not to export the private key
65 | List wallets = walletService.getWallets();
66 | if (wallets.size() == 0) {
67 | return new JsonVo(JsonVo.FAIL, "No wallet found");
68 | }
69 |
70 | for (Wallet w : wallets) {
71 | Account account = accountService.getAccount(w.getAddress());
72 | if (account != null) {
73 | w.setBalance(account.getBalance());
74 | w.setMessageNonce(account.getMessageNonce());
75 | }
76 | }
77 | return new JsonVo<>(JsonVo.SUCCESS, wallets);
78 | }
79 |
80 | @RequestMapping("/balance/{address}")
81 | public JsonVo walletBalance(@PathVariable String address)
82 | {
83 | BigDecimal balance = accountService.getBalance(address);
84 | return new JsonVo<>(JsonVo.SUCCESS, balance);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/blockj-miner/src/main/java/org/rockyang/blockj/web/handler/AppExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj.web.handler;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.web.bind.annotation.ControllerAdvice;
6 | import org.springframework.web.bind.annotation.ExceptionHandler;
7 |
8 |
9 | /**
10 | * global exception catch handler
11 | *
12 | * @author yangjian
13 | */
14 | @ControllerAdvice
15 | public class AppExceptionHandler {
16 |
17 | private final static Logger logger = LoggerFactory.getLogger(AppExceptionHandler.class);
18 |
19 | @ExceptionHandler(Exception.class)
20 | public void handle(Exception e)
21 | {
22 | // @TODO: use different handler with the different exception
23 | logger.error("Something is wrong, ", e);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/blockj-miner/src/test/java/org/rockyang/jblock/TempTest.java:
--------------------------------------------------------------------------------
1 | package org.rockyang.blockj;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.concurrent.locks.ReentrantLock;
6 |
7 | /**
8 | * @author yangjian
9 | */
10 | public class TempTest {
11 |
12 | private final ReentrantLock lock = new ReentrantLock();
13 |
14 | @Test
15 | public void run() throws InterruptedException
16 | {
17 | boolean b = true;
18 | try {
19 | lock.lock();
20 | if (b) {
21 | System.out.println("returned");
22 | return;
23 | }
24 | } finally {
25 | System.out.println("release");
26 | lock.unlock();
27 | }
28 | }
29 |
30 | public synchronized void m1() throws InterruptedException
31 | {
32 | System.out.println(Thread.currentThread().getName() + " enter method 1");
33 | Thread.sleep(5000);
34 | System.out.println(Thread.currentThread().getName() + " leave method 1");
35 | }
36 |
37 | public void m2() throws InterruptedException
38 | {
39 | lock.lock();
40 | System.out.println(Thread.currentThread().getName() + " enter method2");
41 | Thread.sleep(5000);
42 | System.out.println(Thread.currentThread().getName() + " leave method2");
43 | lock.unlock();
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/client:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | java -jar blockj-client/target/blockj-client-2.0.0-jar-with-dependencies.jar $*
4 |
--------------------------------------------------------------------------------
/miner:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #set -x
3 |
4 | repo=""
5 | for arg in `echo $*|awk '{print $0}'`
6 | do
7 | if [[ $arg == --repo* ]];
8 | then
9 | repo=`echo $arg|awk -F '=' '{print $2}'`
10 | fi
11 | done
12 |
13 | if [[ -z $repo ]]; then
14 | echo "must pass --repo arg"
15 | exit 1
16 | fi
17 |
18 | java -jar ./blockj-miner/target/blockj-miner-2.0.0.jar --spring.config.location=file:$repo/node.properties $*
19 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.6.5
9 |
10 |
11 |
12 | org.rockyang
13 | blockj
14 | 2.0.0
15 | pom
16 |
17 | blockj
18 | block chain implementation for Java
19 |
20 | https://gitee.com/blackfox/blockj
21 |
22 |
23 | The Apache Software License, Version 2.0
24 | https://www.apache.org/licenses/LICENSE-2.0.txt
25 |
26 |
27 |
28 |
29 | blockj-miner
30 | blockj-client
31 | blockj-base
32 |
33 |
34 |
35 |
36 | RockYang
37 | yangjian102621@gmail.com
38 |
39 |
40 |
41 |
42 | scm:https://gitee.com/blackfox/blockj.git
43 | scm:https://gitee.com/blackfox/blockj.git
44 | https://gitee.com/blackfox/blockj.git
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.apache.maven.plugins
52 | maven-compiler-plugin
53 | 3.10.1
54 |
55 | 8
56 | 8
57 | true
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------