從0開始搭建一個區塊鏈Demo
主要以比特幣區塊鏈爲基礎,做一個法院案件記錄上鍊及修改的樣例。結合上一篇的介紹
https://blog.csdn.net/NEU_LightBulb/article/details/103475390
假設區塊鏈服務用的庫表和數據庫表在一個數據庫裏。整個工程是一個Springboot2工程
https://github.com/zjw271208550/learn/tree/master/blockchain-client-1
-
數據存儲
CREATE TABLE "db_data"."t_data" (
"c_bh" varchar NOT NULL, --案件編號
"c_ah" varchar, --案件案號
"dt_jarq" timestamp(6), --結案日期
"dt_update" timestamp(6), --數據更新日期
CONSTRAINT "t_data_pkey" PRIMARY KEY ("c_bh")
)
-
數據實體——繼承BlockData
public class Data extends BlockData {
private String bh;
private String ah;
private Date jarq;
private Date update;
public Data(String bh, String ah, Date jarq,Date update) {
super(bh, bh + ah + (null==jarq?"":date2String(jarq)) + date2String(update));
this.bh = bh;
this.ah = ah;
this.jarq = jarq;
this.update = update;
}
private static String date2String(Date date){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String rel = sdf.format(date.getTime());
return rel;
}
public String getBh() {
return bh;
}
public void setBh(String bh) {
this.bh = bh;
}
public String getAh() {
return ah;
}
public void setAh(String ah) {
this.ah = ah;
}
public Date getJarq() {
return jarq;
}
public void setJarq(Date jarq) {
this.jarq = jarq;
}
public Date getUpdate() {
return update;
}
public void setUpdate(Date update) {
this.update = update;
}
}
-
Mapper——繼承BCMapper
@Repository
@Mapper
public interface DBMapper extends BCMapper {
@Select("SELECT * FROM db_data.t_data WHERE c_bh=#{id}")
@Results({
@Result(property = "bh", column = "c_bh"),
@Result(property = "ah", column = "c_ah"),
@Result(property = "jarq",column = "dt_jarq"),
@Result(property = "update",column = "dt_update")
})
Data getDataById(@Param("id") String id);
@Insert("INSERT INTO db_data.t_data VALUES(#{bh},#{ah},#{jarq},#{update}) ")
void addData(Data data);
@Update("UPDATE db_data.t_data SET dt_jarq=#{jarq} WHERE c_bh=#{id}")
void updateJarqById(@Param("jarq") Date jarq, @Param("id") String id);
@Delete("DELETE FROM db_data.t_data WHERE c_bh=#{id}")
void deleteDataById(@Param("id") String id);
}
-
Service——調用ServiceFunction中的方法3
@Service
public class BaseService {
private final Logger logger = LoggerFactory.getLogger(BaseService.class);
@Autowired
private DBMapper dbMapper;
@Autowired
private ClientKey clientKey;
/**
* 根據 id獲得數據
*/
public Data getDataById(String id){
Data data = dbMapper.getDataById(id);
if(checkDataById(data)){
return data;
}else{
logger.error("賬目中這個編號的數據與數據庫中的不符");
return null;
}
}
/**
* 創世
*/
public void init(){
if(!ServiceFunction.hasInit(dbMapper)) {
ServiceFunction.initBlock(dbMapper);
}
}
/**
* 添加一個新數據,數據準備上鍊
*/
public String addOneData(Data data) throws Exception{
if(!ServiceFunction.hasInit(dbMapper)){
String message = "尚未創世或創世失敗";
logger.error(message);
return message;
}
if(checkDataById(data)){
String message = "賬目中已存在這個編號的數據";
logger.error(message);
return message;
}else {
boolean rel = ServiceFunction.addNewData(dbMapper,clientKey.getPublicKey(),clientKey.getPrivateKey(),data);
if(rel) {
dbMapper.addData(data);
return "成功";
}else {
String message = "添加新數據失敗";
logger.error(message);
return message;
}
}
}
/**
* 挖掘,數據上鍊
*/
public boolean mine(){
boolean rel = false;
try {
rel = ServiceFunction.mineTransactions(dbMapper,clientKey.getPublicKey());
}catch (Exception e){
logger.error("挖掘失敗",e);
return false;
}
return rel;
}
private boolean checkDataById(Data dataInDB){
return ServiceFunction.checkDataById(dbMapper,dataInDB);
}
}
-
Controller
@RestController
public class Controller {
private final Logger logger = LoggerFactory.getLogger(BaseService.class);
@Autowired
BaseService service;
/**
* 初始化區塊鏈的創世塊
* ===> Block
* ===> 是否創世 >> 是否合法
*/
@RequestMapping("/init")
public String init(){
service.init();
return "OK";
}
/**
* 添加一條數據
* ===> 原始數據與 BC的關係
* ===> Tx機制
* ===> 檢查數據唯一性 >> 創建 txIn、txOut >> 創建tx >> 簽名 >> Tx信息入庫 >> data入庫
*/
@RequestMapping("/add")
public String add(){
Data data = new Data("abcd1234","xxxxxx",null,new Date(new java.util.Date().getTime()));
try {
return service.addOneData(data);
}catch (Exception e){
logger.error("添加異常",e);
return e.getMessage();
}
}
/**
* 挖掘區塊,正式上鍊
* ===> UTxOut
* ===> 獲取未上鍊 Tx(並廣播) >> 驗證 Tx合法性 >> 驗證 TxIn合法性 >> 生成區塊(並廣播) >> 生成 UTxOut(更新 Tx)
*/
@RequestMapping("/mine")
public String mine(){
try {
if(service.mine()) {
return "OK";
}else {
return "失敗";
}
}catch (Exception e){
logger.error("挖掘異常",e);
return e.getMessage();
}
}
/**
* 在整個網絡中查詢(不過濾地址公鑰)
* ===> UTxOut中是否存在 >> 是否一致
*/
@RequestMapping("/quaryall")
public Data quaryInAll(){
String bh = "abcd1234";
Data data = service.getDataById(bh);
return data;
}
}
-
Client Key
@Component
public class ClientKey {
private final Logger logger = LoggerFactory.getLogger(ClientKey.class);
private String realAddress;
private String publicKey;
private String privateKey;
public ClientKey() {
try {
this.realAddress = InetAddress.getLocalHost().getHostAddress();
}catch (UnknownHostException e){
this.realAddress = "172.16.192.90";
logger.error(e.getMessage());
}
try {
byte[] privateKeyBytes = Crypt.generateECPrivateKeyFromRandom(this.realAddress.getBytes());
this.privateKey = Crypt.bytes2Base64String(privateKeyBytes);
byte[] publicKeyBytes = Crypt.getECPublicKeyFromPrivateKey(privateKeyBytes);
this.publicKey = Crypt.bytes2Base64String(publicKeyBytes);
}catch (Exception e){
logger.error(e.getMessage());
this.privateKey = null;
this.publicKey = null;
}
}
public String getRealAddress() {
return realAddress;
}
public void setRealAddress(String realAddress) {
this.realAddress = realAddress;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}
-
演示
- 直接ADD
- 執行創世
查看Block表的創世塊數據
- 再ADD
查看數據表中插入的數據
查看生成的輸入交易與輸出交易
查看對應的交易,這時的交易還沒有被挖掘到區塊鏈中,所以沒有對應的區塊編號也沒有對應區塊
同樣的沒有上鍊也就是現在的數據還是不被所有節點承認的,所以爲輸出的交易中也不會有記錄
- 查詢
就像剛剛說的,這個數據還沒被承認,所以是查詢不到的
- 挖掘
挖掘後數據正式上鍊,挖掘出新的區塊記錄交易,且交易與這個區塊ID關聯
同時數據生效出現在爲輸出的交易記錄中
- 再查詢