*/
private String currentBlockHash;
/**
-
构造函数
-
@param currentBlockHash
*/
public BlockchainIterator(String currentBlockHash) {
this.currentBlockHash = currentBlockHash;
}
/**
-
判断是否有下一个区块
-
@return
*/
public boolean hashNext() {
if (ByteUtils.ZERO_HASH.equals(currentBlockHash)) {
return false;
}
Block lastBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
if (lastBlock == null) {
return false;
}
// 如果是创世区块
if (ByteUtils.ZERO_HASH.equals(lastBlock.getPrevBlockHash())) {
return true;
}
return RocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash()) != null;
}
/**
-
迭代获取区块
-
@return
*/
public Block next() {
Block currentBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
if (currentBlock != null) {
this.currentBlockHash = currentBlock.getPrevBlockHash();
return currentBlock;
}
return null;
}
}
/**
-
添加方法,用于获取迭代器实例
-
@return
*/
public BlockchainIterator getBlockchainIterator() {
return new BlockchainIterator(lastBlockHash);
}
/**
-
打包交易,进行挖矿
-
@param transactions
*/
public void mineBlock(List transactions) throws Exception {
String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
Block lastBlock = RocksDBUtils.getInstance().getBlock(lastBlockHash);
if (lastBlockHash == null) {
throw new Exception("ERROR: Fail to get last block hash ! ");
}
Block block = Block.newBlock(lastBlockHash, transactions,lastBlock.getHeight()+1);
this.addBlock(block);
}
/**
-
从交易输入中查询区块链中所有已被花费了的交易输出
-
@param address 钱包地址
-
@return 交易ID以及对应的交易输出下标地址
-
@throws Exception
*/
private Map<String, int[]> getAllSpentTXOs(String address) {
// 定义TxId ——> spentOutIndex[],存储交易ID与已被花费的交易输出数组索引值
Map<String, int[]> spentTXOs = new HashMap<>();
for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {
Block block = blockchainIterator.next();
for (Transaction transaction : block.getTransactions()) {
// 如果是 coinbase 交易,直接跳过,因为它不存在引用前一个区块的交易输出
if (transaction.isCoinbase()) {
continue;
}
for (TXInput txInput : transaction.getInputs()) {
if (txInput.canUnlockOutputWith(address)) {
String inTxId = Hex.encodeHexString(txInput.getTxId());
int[] spentOutIndexArray = spentTXOs.get(inTxId);
if (spentOutIndexArray == null) {
spentTXOs.put(inTxId, new int[]{txInput.getTxOutputIndex()});
} else {
spentOutIndexArray = ArrayUtils.add(spentOutIndexArray, txInput.getTxOutputIndex());
spentTXOs.put(inTxId, spentOutIndexArray);
}
}
}
}
}
return spentTXOs;
}
/**
-
查找钱包地址对应的所有未花费的交易
-
@param address 钱包地址
-
@return
*/
private Transaction[] findUnspentTransactions(String address) throws Exception {
Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(address);
Transaction[] unspentTxs = {};
// 再次遍历所有区块中的交易输出
for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {
Block block = blockchainIterator.next();
for (Transaction transaction : block.getTransactions()) {
String txId = Hex.encodeHexString(transaction.getTxId());
int[] spentOutIndexArray = allSpentTXOs.get(txId);
for (int outIndex = 0; outIndex < transaction.getOutputs().length; outIndex++) {
if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) {
continue;
}
// 保存不存在 allSpentTXOs 中的交易
if (transaction.getOutputs()[outIndex].canBeUnlockedWith(address)) {
unspentTxs = ArrayUtils.add(unspentTxs, transaction);
}
}
}
}
return unspentTxs;
}
/**
-
查找钱包地址对应的所有UTXO
-
@param address 钱包地址
-
@return
*/
public TXOutput[] findUTXO(String address) throws Exception {
Transaction[] unspentTxs = this.findUnspentTransactions(address);
TXOutput[] utxos = {};
if (unspentTxs == null || unspentTxs.length == 0) {
return utxos;
}
for (Transaction tx : unspentTxs) {
for (TXOutput txOutput : tx.getOutputs()) {
if (txOutput.canBeUnlockedWith(address)) {
utxos = ArrayUtils.add(utxos, txOutput);
}
}
}
return utxos;
}
/**
-
寻找能够花费的交易
-
@param address 钱包地址
-
@param amount 花费金额
*/
public SpendableOutputResult findSpendableOutputs(String address, int amount) throws Exception {
Transaction[] unspentTXs = this.findUnspentTransactions(address);
int accumulated = 0;
Map<String, int[]> unspentOuts = new HashMap<>();
for (Transaction tx : unspentTXs) {
String txId = Hex.encodeHexString(tx.getTxId());
for (int outId = 0; outId < tx.getOutputs().length; outId++) {
TXOutput txOutput = tx.getOutputs()[outId];
if (txOutput.canBeUnlockedWith(address) && accumulated < amount) {
accumulated += txOutput.getValue();
int[] outIds = unspentOuts.get(txId);
if (outIds == null) {
outIds = new int[]{outId};
} else {
outIds = ArrayUtils.add(outIds, outId);
}
unspentOuts.put(txId, outIds);
if (accumulated >= amount) {
break;
}
}
}
}
return new SpendableOutputResult(accumulated, unspentOuts);
}
/**
-
从 DB 从恢复区块链数据
-
@return
-
@throws Exception
*/
public static Blockchain initBlockchainFromDB() throws Exception {
String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
if (lastBlockHash == null) {
throw new Exception("ERROR: Fail to init blockchain from db. ");
}
return new Blockchain(lastBlockHash);
}
}
创建ProofOfWork.java
(工作量证明)
工作量证明是经过困难的工作,将数据放入区块链中,这样别人就不太可能取修改区块链中的区块,想要修改其中一个块,必须经过大量计算,还要计算这个块之后的块
这里将计算难度设置为16,就是根据nonce计算出来的hash前16位必须为0,这样才能比目标值小,这样添加的区块才能被认可
package com.example.blockchain;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigInteger;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
-
工作量证明
-
@author hanru
*/
@Data
@AllArgsConstructor
public class ProofOfWork {
/**
-
难度目标位
-
0000 0000 0000 0000 1001 0001 0000 … 0001
-
256位Hash里面前面至少有16个零
*/
public static final int TARGET_BITS = 16;
/**
- 要验证的区块
*/
private Block block;
/**
- 难度目标值
*/
private BigInteger target;
/**
-
创建新的工作量证明对象
-
对1进行移位运算,将1向左移动 (256 - TARGET_BITS) 位,得到我们的难度目标值
-
@param block
-
@return
*/
public static ProofOfWork newProofOfWork(Block block) {
/*
1.创建一个BigInteger的数值1.
0000000…00001
2.左移256-bits位
以8 bit为例
0000 0001
0010 0000
8-6
*/
BigInteger targetValue = BigInteger.ONE.shiftLeft((256 - TARGET_BITS));
return new ProofOfWork(block, targetValue);
}
/**
-
运行工作量证明,开始挖矿,找到小于难度目标值的Hash
-
@return
*/
public PowResult run() {
long nonce = 0;
String shaHex = “”;
// System.out.printf(“开始进行挖矿:%s \n”, this.getBlock().getData());
System.out.printf(“开始进行挖矿: \n”);
long startTime = System.currentTimeMillis();
while (nonce < Long.MAX_VALUE) {
byte[] data = this.prepareData(nonce);
shaHex = DigestUtils.sha256Hex(data);
System.out.printf(“\r%d: %s”,nonce,shaHex);
if (new BigInteger(shaHex, 16).compareTo(this.target) == -1) {
System.out.println();
System.out.printf(“耗时 Time: %s seconds \n”, (float) (System.currentTimeMillis() - startTime) / 1000);
System.out.printf(“当前区块Hash: %s \n\n”, shaHex);
break;
} else {
nonce++;
}
}
return new PowResult(nonce, shaHex);
}
/**
-
根据block的数据,以及nonce,生成一个byte数组
-
注意:在准备区块数据时,一定要从原始数据类型转化为byte[],不能直接从字符串进行转换
-
@param nonce
-
@return
*/
private byte[] prepareData(long nonce) {
byte[] prevBlockHashBytes = {};
if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) {
prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray();
}
return ByteUtils.merge(
prevBlockHashBytes,
// this.getBlock().getData().getBytes(),
this.getBlock().hashTransaction(),
ByteUtils.toBytes(this.getBlock().getTimeStamp()),
ByteUtils.toBytes(TARGET_BITS),
ByteUtils.toBytes(nonce)
);
}
/**
-
验证区块是否有效
-
@return
*/
public boolean validate() {
byte[] data = this.prepareData(this.getBlock().getNonce());
return new BigInteger(DigestUtils.sha256Hex(data), 16).compareTo(this.target) == -1;
}
}
创建TXInput.java(交易中的输入)
package com.example.blockchain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TXInput {
/**
- txId是前一次交易的ID
*/
private byte[] txId;
/**
- 交易输出索引
*/
private int txOutputIndex;
/**
- 解锁脚本
*/
private String scriptSig;
/**
-
判断解锁数据是否能够解锁交易输出
-
@param unlockingData
-
@return
*/
public boolean canUnlockOutputWith(String unlockingData) {
return this.getScriptSig().endsWith(unlockingData);
}
}
创建TXOutput.java(交易中的输出)
package com.example.blockchain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
- @author hanru
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TXOutput {
/**
- 数值金额
*/
private int value;
/**
- 锁定脚本
*/
private String scriptPubKey;
/**
-
判断解锁数据是否能够解锁交易输出
-
@param unlockingData
-
@return
*/
public boolean canBeUnlockedWith(String unlockingData) {
return this.getScriptPubKey().endsWith(unlockingData);
}
}
创建 Transaction.java
package com.example.blockchain;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Iterator;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Transaction {
private static final int SUBSIDY = 10;
/**
- 交易的Hash
*/
private byte[] txId;
/**
- 交易输入
*/
private TXInput[] inputs;
/**
- 交易输出
*/
private TXOutput[] outputs;
/**
- 设置交易ID
*/
private void setTxId() {
this.setTxId(DigestUtils.sha256(SerializeUtils.serialize(this)));
}
/**
-
创建CoinBase交易
-
@param to 收账的钱包地址
-
@param data 解锁脚本数据
-
@return
*/
public static Transaction newCoinbaseTX(String to, String data) {
if (StringUtils.isBlank(data)) {
data = String.format(“Reward to ‘%s’”, to);
}
// 创建交易输入
TXInput txInput = new TXInput(new byte[]{}, -1, data);
// 创建交易输出
TXOutput txOutput = new TXOutput(SUBSIDY, to);
// 创建交易
Transaction tx = new Transaction(null, new TXInput[]{txInput}, new TXOutput[]{txOutput});
// 设置交易ID
tx.setTxId();
return tx;
}
/**
-
从 from 向 to 支付一定的 amount 的金额
-
@param from 支付钱包地址
-
@param to 收款钱包地址
-
@param amount 交易金额
-
@param blockchain 区块链
-
@return
*/
public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {
SpendableOutputResult result = blockchain.findSpendableOutputs(from, amount);
int accumulated = result.getAccumulated();
Map<String, int[]> unspentOuts = result.getUnspentOuts();
if (accumulated < amount) {
throw new Exception(“ERROR: Not enough funds”);
}
Iterator<Map.Entry<String, int[]>> iterator = unspentOuts.entrySet().iterator();
TXInput[] txInputs = {};
while (iterator.hasNext()) {
Map.Entry<String, int[]> entry = iterator.next();
String txIdStr = entry.getKey();
int[] outIdxs = entry.getValue();
byte[] txId = Hex.decodeHex(txIdStr.toCharArray());
for (int outIndex : outIdxs) {
txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, from));
}
}
TXOutput[] txOutput = {};
txOutput = ArrayUtils.add(txOutput, new TXOutput(amount, to));
if (accumulated > amount) {
txOutput = ArrayUtils.add(txOutput, new TXOutput((accumulated - amount), from));
}
Transaction newTx = new Transaction(null, txInputs, txOutput);
newTx.setTxId();
return newTx;
}
/**
-
是否为 Coinbase 交易
-
@return
*/
public boolean isCoinbase() {
return this.getInputs().length == 1
&& this.getInputs()[0].getTxId().length == 0
&& this.getInputs()[0].getTxOutputIndex() == -1;
}
}
区块持久化存储
创建SerializeUtils.java
该类将原来的类型转换成byte[]类型,这样可以将其存入数据库中,当我们需要从数据库读取数据,再将其反序列化获得指定object
package com.example.blockchain;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
- 序列化工具类
*/
public class SerializeUtils {
/**
-
序列化
-
@param object 需要序列化的对象
-
@return
*/
public static byte[] serialize(Object object) {
Output output = new Output(4096, -1);
new Kryo().writeClassAndObject(output, object);
byte[] bytes = output.toBytes();
output.close();
return bytes;
}
/**
-
反序列化
-
@param bytes 对象对应的字节数组
-
@return
*/
public static Object deserialize(byte[] bytes) {
Input input = new Input(bytes);
Object obj = new Kryo().readClassAndObject(input);
input.close();
return obj;
}
}
原文是使用RocksDB来存储的
package com.example.blockchain;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import java.util.HashMap;
import java.util.Map;
/**
- 数据库存储的工具类
*/
public class RocksDBUtils {
/**
- 区块链数据文件
*/
private static final String DB_FILE = “blockchain.db”;
/**
- 区块桶前缀
*/
private static final String BLOCKS_BUCKET_KEY = “blocks”;
/**
- 最新一个区块的hash
*/
private static final String LAST_BLOCK_KEY = “l”;
private volatile static RocksDBUtils instance;
/**
-
获取RocksDBUtils的单例
-
@return
*/
public static RocksDBUtils getInstance() {
if (instance == null) {
synchronized (RocksDBUtils.class) {
if (instance == null) {
instance = new RocksDBUtils();
}
}
}
return instance;
}
private RocksDBUtils() {
openDB();
initBlockBucket();
}
private RocksDB db;
/**
- block buckets
*/
private Map<String, byte[]> blocksBucket;
/**
- 打开数据库
*/
private void openDB() {
try {
db = RocksDB.open(DB_FILE);
} catch (RocksDBException e) {
throw new RuntimeException("打开数据库失败。。 ! ", e);
}
}
/**
- 初始化 blocks 数据桶
*/
private void initBlockBucket() {
try {
//
byte[] blockBucketKey = SerializeUtils.serialize(BLOCKS_BUCKET_KEY);
byte[] blockBucketBytes = db.get(blockBucketKey);
if (blockBucketBytes != null) {
blocksBucket = (Map) SerializeUtils.deserialize(blockBucketBytes);
} else {
blocksBucket = new HashMap<>();
db.put(blockBucketKey, SerializeUtils.serialize(blocksBucket));
}
} catch (RocksDBException e) {
throw new RuntimeException("初始化block的bucket失败。。! ", e);
}
}
/**
-
保存区块
-
@param block
*/
public void putBlock(Block block) {
try {
blocksBucket.put(block.getHash(), SerializeUtils.serialize(block));
db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
} catch (RocksDBException e) {
throw new RuntimeException("存储区块失败。。 ", e);
}
}
/**
-
查询区块
-
@param blockHash
-
@return
*/
public Block getBlock(String blockHash) {
return (Block) SerializeUtils.deserialize(blocksBucket.get(blockHash));
}
/**
-
保存最新一个区块的Hash值
-
@param tipBlockHash
*/
public void putLastBlockHash(String tipBlockHash) {
try {
blocksBucket.put(LAST_BLOCK_KEY, SerializeUtils.serialize(tipBlockHash));
db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
} catch (RocksDBException e) {
throw new RuntimeException("数据库存储最新区块hash失败。。 ", e);
}
}
/**
-
查询最新一个区块的Hash值
-
@return
*/
public String getLastBlockHash() {
byte[] lastBlockHashBytes = blocksBucket.get(LAST_BLOCK_KEY);
if (lastBlockHashBytes != null) {
return (String) SerializeUtils.deserialize(lastBlockHashBytes);
}
return “”;
}
/**
- 关闭数据库
*/
public void closeDB() {
try {
db.close();
} catch (Exception e) {
throw new RuntimeException("关闭数据库失败。。 ", e);
}
}
}
编写main函数先测试
package com.example.blockchain;
import org.apache.commons.codec.binary.Hex;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception {
Blockchain.createBlockchain(“test”);
String address = “test”;
// 查询
Blockchain blockchain = Blockchain.createBlockchain(address);
TXOutput[] txOutputs = blockchain.findUTXO(address);
int balance = 0;
if (txOutputs != null && txOutputs.length > 0) {
for (TXOutput txOutput : txOutputs) {
balance += txOutput.getValue();
}
}
System.out.printf(“Balance of ‘%s’: %d\n”, address, balance);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
**其实上面说了这么多,钱是永远赚不完的,在这个知识付费的时代,知识技能提升才是是根本!我作为一名8年的高级工程师,知识技能已经学习的差不多。**在看这篇文章的可能有刚刚入门,刚刚开始工作,或者大佬级人物。
像刚刚开始学Android开发小白想要快速提升自己,最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以这里分享一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
这么重要的事情说三遍啦!点赞+点赞+点赞!
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
indUTXO(address);
int balance = 0;
if (txOutputs != null && txOutputs.length > 0) {
for (TXOutput txOutput : txOutputs) {
balance += txOutput.getValue();
}
}
System.out.printf(“Balance of ‘%s’: %d\n”, address, balance);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-wGbzLymT-1713596433020)]
[外链图片转存中…(img-SFWC9EO1-1713596433021)]
[外链图片转存中…(img-UkY0U5OG-1713596433022)]
[外链图片转存中…(img-eYh7WQi7-1713596433023)]
[外链图片转存中…(img-Dkf2ko7p-1713596433025)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
**其实上面说了这么多,钱是永远赚不完的,在这个知识付费的时代,知识技能提升才是是根本!我作为一名8年的高级工程师,知识技能已经学习的差不多。**在看这篇文章的可能有刚刚入门,刚刚开始工作,或者大佬级人物。
像刚刚开始学Android开发小白想要快速提升自己,最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以这里分享一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
这么重要的事情说三遍啦!点赞+点赞+点赞!
[外链图片转存中…(img-5b6DbZ48-1713596433026)]
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
[外链图片转存中…(img-SHXZGlzh-1713596433027)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!