├── .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 | --------------------------------------------------------------------------------