PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/bitherj/src/main/java/net/bither/bitherj/core/BlockChain.java

https://gitlab.com/Ltaimao/bitherj
Java | 356 lines | 260 code | 46 blank | 50 comment | 70 complexity | 902fb97d7987ba2dcf77ffe30de2a2a6 MD5 | raw file
  1. /*
  2. * Copyright 2014 http://Bither.net
  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. package net.bither.bitherj.core;
  17. import net.bither.bitherj.db.AbstractDb;
  18. import net.bither.bitherj.exception.VerificationException;
  19. import net.bither.bitherj.utils.Utils;
  20. import org.slf4j.Logger;
  21. import org.slf4j.LoggerFactory;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. public class BlockChain {
  27. private static BlockChain uniqueInstance = new BlockChain();
  28. private static final Logger log = LoggerFactory.getLogger(BlockChain.class);
  29. protected HashMap<byte[], Block> singleBlocks;
  30. protected Block lastBlock;
  31. protected Block lastOrphanBlock;
  32. BlockChain() {
  33. AbstractDb.blockProvider.cleanOldBlock();
  34. this.singleBlocks = new HashMap<byte[], Block>();
  35. this.lastBlock = AbstractDb.blockProvider.getLastBlock();
  36. this.lastOrphanBlock = AbstractDb.blockProvider.getLastOrphanBlock();
  37. }
  38. public static BlockChain getInstance() {
  39. return uniqueInstance;
  40. }
  41. public void addSPVBlock(Block block) {
  42. // only none block need add spv block
  43. if (this.getBlockCount() == 0) {
  44. block.setMain(true);
  45. this.addBlock(block);
  46. this.lastBlock = block;
  47. }
  48. }
  49. public void addBlocks(List<Block> blocks) {
  50. AbstractDb.blockProvider.addBlocks(blocks);
  51. }
  52. public Block getLastBlock() {
  53. return this.lastBlock;
  54. }
  55. public Block getBlock(byte[] blockHash) {
  56. return AbstractDb.blockProvider.getBlock(blockHash);
  57. }
  58. public int getBlockCount() {
  59. return AbstractDb.blockProvider.getBlockCount();
  60. }
  61. public List<byte[]> getBlockLocatorArray() {
  62. // append 10 most recent block hashes, descending, then continue appending, doubling the step back each time,
  63. // finishing with the genesis block (top, -1, -2, -3, -4, -5, -6, -7, -8, -9, -11, -15, -23, -39, -71, -135, ..., 0)
  64. ArrayList<byte[]> locators = new ArrayList<byte[]>();
  65. int step = 1, start = 0;
  66. Block b = this.lastBlock;
  67. while (b != null && b.getBlockNo() > 0) {
  68. locators.add(b.getBlockHash());
  69. if (++start >= 10) step *= 2;
  70. for (int i = 0; b != null && i < step; i++) {
  71. b = AbstractDb.blockProvider.getMainChainBlock(b.getBlockPrev());
  72. }
  73. }
  74. locators.add(BitherjSettings.GENESIS_BLOCK_HASH);
  75. return locators;
  76. }
  77. public boolean rollbackBlock(int blockNo) {
  78. log.warn("block chain roll back to " + blockNo);
  79. if (blockNo > this.lastBlock.getBlockNo())
  80. return false;
  81. int delta = this.lastBlock.getBlockNo() - blockNo;
  82. if (delta >= BitherjSettings.BLOCK_DIFFICULTY_INTERVAL || delta >= this.getBlockCount())
  83. return false;
  84. List<Block> blocks = AbstractDb.blockProvider.getBlocksFrom(blockNo);
  85. // DDLogWarn(@"roll back block from %d to %d", self.lastBlock.height, blockNo);
  86. for (Block block : blocks) {
  87. AbstractDb.blockProvider.removeBlock(block.getBlockHash());
  88. if (block.isMain()) {
  89. AbstractDb.txProvider.unConfirmTxByBlockNo(block.getBlockNo());
  90. }
  91. }
  92. this.lastBlock = AbstractDb.blockProvider.getLastBlock();
  93. return true;
  94. }
  95. public int relayedBlockHeadersForMainChain(List<Block> blocks) {
  96. if (blocks == null || blocks.size() == 0) {
  97. return 0;
  98. }
  99. ArrayList<Block> blocksToAdd = new ArrayList<Block>();
  100. Block prev = getLastBlock();
  101. if (prev == null) {
  102. log.warn("pre block is null");
  103. return 0;
  104. }
  105. for (int i = 0; i < blocks.size(); i++) {
  106. Block block = blocks.get(i);
  107. if (!Arrays.equals(prev.getBlockHash(), block.getBlockPrev())) {
  108. Block alreadyIn = getBlock(block.getBlockHash());
  109. if (alreadyIn != null) {
  110. log.debug("Block is already in, No." + alreadyIn.getBlockNo());
  111. continue;
  112. } else {
  113. this.singleBlocks.put(block.getBlockHash(), block);
  114. break;
  115. }
  116. }
  117. block.setBlockNo(prev.getBlockNo() + 1);
  118. try {
  119. block.verifyDifficultyFromPreviousBlock(prev);
  120. } catch (Exception e) {
  121. e.printStackTrace();
  122. break;
  123. }
  124. block.setMain(true);
  125. blocksToAdd.add(block);
  126. prev = block;
  127. }
  128. if (blocksToAdd.size() > 0) {
  129. addBlocks(blocksToAdd);
  130. lastBlock = blocksToAdd.get(blocksToAdd.size() - 1);
  131. }
  132. return blocksToAdd.size();
  133. }
  134. /*
  135. * if result is true, means the block is in main chain, if result is false, means the block is single
  136. * or orphan.
  137. * */
  138. public boolean relayedBlock(Block block) throws VerificationException {
  139. Block prev = AbstractDb.blockProvider.getBlock(block.getBlockPrev());
  140. if (prev == null) {
  141. log.debug("prev block is null, prev hash is : " + Utils.hashToString(block.getBlockPrev()));
  142. // DDLogDebug(@"%@:%d relayed orphan block %@, previous %@, last block is %@, height %d", peer.host, peer.port,
  143. // block.blockHash, block.prevBlock, self.lastBlock.blockHash, self.lastBlock.height);
  144. // ignore orphans older than one week ago
  145. // if (block.blockTime - NSTimeIntervalSince1970 < [NSDate timeIntervalSinceReferenceDate] - ONE_WEEK) return;
  146. this.singleBlocks.put(block.getBlockPrev(), block);
  147. return false;
  148. // // call get blocks, unless we already did with the previous block, or we're still downloading the chain
  149. // if (self.lastBlock.height >= peer.lastBlock && ![self.lastOrphan.blockHash isEqual:block.prevBlock]) {
  150. // DDLogDebug(@"%@:%d calling getblocks", peer.host, peer.port);
  151. // [peer sendGetBlocksMessageWithLocators:[self blockLocatorArray] andHashStop:nil];
  152. // }
  153. }
  154. block.setBlockNo(prev.getBlockNo() + 1);
  155. //TODO
  156. // int transitionTime = 0;
  157. // // hit a difficulty transition, find previous transition time
  158. // if ((block.getBlockNo() % BitherjSettings.BLOCK_DIFFICULTY_INTERVAL) == 0) {
  159. // Block b = block;
  160. // for (int i = 0; b != null && i < BitherjSettings.BLOCK_DIFFICULTY_INTERVAL; i++) {
  161. // b = DbHelper.blockProvider.getBlock(b.getBlockPrev());
  162. // }
  163. // transitionTime = b.getBlockTime();
  164. // }
  165. // verify block difficulty
  166. block.verifyDifficultyFromPreviousBlock(prev);
  167. // if (!block.verifyDifficultyFromPreviousBlock(prev)) {
  168. // callback(block, NO);
  169. // return;
  170. // }
  171. boolean result = false;
  172. if (Arrays.equals(block.getBlockPrev(), this.lastBlock.getBlockHash())) {
  173. this.extendMainChain(block);
  174. result = true;
  175. } else if (this.inMainChain(block)) {
  176. result = true;
  177. } else {
  178. if (block.getBlockNo() <= BitherjSettings.BITCOIN_REFERENCE_BLOCK_HEIGHT) {
  179. log.debug("block is too old");
  180. return false;
  181. }
  182. if (block.getBlockNo() <= this.lastBlock.getBlockNo()) {
  183. this.addOrphan(block);
  184. log.debug("block is orphan");
  185. return false;
  186. }
  187. if (block.getBlockNo() > this.lastBlock.getBlockNo()) {
  188. Block b = this.getSameParent(block, this.lastBlock);
  189. this.rollbackBlock(b.getBlockNo());
  190. log.debug("roll back block from" + b.getBlockNo());
  191. }
  192. }
  193. if (!result)
  194. log.debug("block is not in main chain");
  195. return result;
  196. }
  197. public int relayedBlocks(List<Block> blocks) throws VerificationException {
  198. if (blocks == null || blocks.size() == 0) {
  199. return 0;
  200. }
  201. Block prev = null;
  202. Block first = blocks.get(0);
  203. int rollbackBlockNo = 0;
  204. if (Arrays.equals(first.getBlockPrev(), this.getLastBlock().getBlockHash())) {
  205. prev = this.getLastBlock();
  206. } else if (AbstractDb.blockProvider.getMainChainBlock(first.getBlockPrev()) != null) {
  207. prev = this.getSameParent(first, this.getLastBlock());
  208. rollbackBlockNo = prev.getBlockNo();
  209. }
  210. if (prev == null) {
  211. return 0;
  212. }
  213. for (Block block : blocks) {
  214. if (!Arrays.equals(block.getBlockPrev(), prev.getBlockHash())) {
  215. return 0;
  216. }
  217. block.setBlockNo(prev.getBlockNo() + 1);
  218. try {
  219. int transitionTime = 0;
  220. if (block.getBlockNo() % BitherjSettings.BLOCK_DIFFICULTY_INTERVAL == 0) {
  221. // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
  222. // two weeks after the initial block chain download.
  223. long now = System.currentTimeMillis();
  224. Block cursor = first;
  225. for (int i = 0; i < BitherjSettings.BLOCK_DIFFICULTY_INTERVAL - block.getBlockNo() + first.getBlockNo(); i++) {
  226. if (cursor == null) {
  227. // This should never happen. If it does, it means we are following an incorrect or busted chain.
  228. throw new VerificationException(
  229. "Difficulty transition point but we did not find a way back to the genesis block.");
  230. }
  231. cursor = getBlock(cursor.getBlockPrev());
  232. }
  233. long elapsed = System.currentTimeMillis() - now;
  234. if (elapsed > 50)
  235. log.info("Difficulty transition traversal took {}msec", elapsed);
  236. transitionTime = cursor.getBlockTime();
  237. }
  238. block.verifyDifficultyFromPreviousBlock(prev, transitionTime);
  239. } catch (Exception e) {
  240. e.printStackTrace();
  241. return 0;
  242. }
  243. block.setMain(true);
  244. prev = block;
  245. }
  246. if (rollbackBlockNo > 0) {
  247. this.rollbackBlock(rollbackBlockNo);
  248. }
  249. this.addBlocks(blocks);
  250. for (Block block : blocks) {
  251. AbstractDb.txProvider.confirmTx(block.getBlockNo(), block.getTxHashes());
  252. }
  253. this.lastBlock = blocks.get(blocks.size() - 1);
  254. return blocks.size();
  255. }
  256. private void extendMainChain(Block block) {
  257. if (Arrays.equals(block.getBlockPrev(), this.lastBlock.getBlockHash())) {
  258. block.setMain(true);
  259. this.addBlock(block);
  260. this.lastBlock = block;
  261. }
  262. }
  263. private boolean inMainChain(Block block) {
  264. Block b = this.lastBlock;
  265. while (b != null && b.getBlockNo() > block.getBlockNo()) {
  266. b = AbstractDb.blockProvider.getBlock(b.getBlockPrev());
  267. }
  268. return b != null && Arrays.equals(b.getBlockHash(), block.getBlockHash());
  269. }
  270. private void addBlock(Block block) {
  271. AbstractDb.blockProvider.addBlock(block);
  272. }
  273. private void addOrphan(Block block) {
  274. block.setMain(false);
  275. this.addBlock(block);
  276. this.lastOrphanBlock = block;
  277. }
  278. private Block getSameParent(Block block1, Block block2) {
  279. Block b1 = block1;
  280. Block b2 = block2;
  281. while (b1 != null && b2 != null && !Arrays.equals(b1.getBlockHash(), b2.getBlockHash())) {
  282. b1 = AbstractDb.blockProvider.getBlock(b1.getBlockPrev());
  283. if (b1.getBlockNo() < b2.getBlockNo()) {
  284. b2 = AbstractDb.blockProvider.getBlock(b2.getBlockPrev());
  285. }
  286. }
  287. return b1;
  288. }
  289. private void forkMainChain(Block forkStartBlock, Block lastBlock) {
  290. Block b = this.lastBlock;
  291. Block next = lastBlock;
  292. while (!Arrays.equals(b.getBlockHash(), forkStartBlock.getBlockHash())) {
  293. next = AbstractDb.blockProvider.getOrphanBlockByPrevHash(b.getBlockPrev());
  294. AbstractDb.blockProvider.updateBlock(b.getBlockHash(), false);
  295. b = AbstractDb.blockProvider.getMainChainBlock(b.getBlockPrev());
  296. this.lastBlock = b;
  297. }
  298. b = next;
  299. AbstractDb.blockProvider.updateBlock(next.getBlockHash(), true);
  300. this.lastBlock = next;
  301. while (!Arrays.equals(b.getBlockHash(), lastBlock.getBlockPrev())) {
  302. AbstractDb.blockProvider.updateBlock(b.getBlockHash(), true);
  303. this.lastBlock = b;
  304. b = AbstractDb.blockProvider.getOrphanBlockByPrevHash(b.getBlockHash());
  305. }
  306. lastBlock.setMain(true);
  307. this.addBlock(lastBlock);
  308. this.lastBlock = lastBlock;
  309. }
  310. }