密碼學專題訓練
實 驗 報 告
實驗名稱 實驗三 區塊鏈專題
一、實驗目的
1.理解分散式儲存的相關概念和原理;
2.理解傳統分散式系統存在的弊端和安全威脅;
3.瞭解基本的共識機制和共識演算法;
4.理解並掌握Hash函式設計思想和應用場景;
5.理解並掌握公鑰密碼體制在保護身份資訊中的作用;
6.瞭解區塊鏈當前存在的技術瓶頸。
二、實驗環境
1.PC計算機 Windows 11
2.DEV
- 實驗原理
分散式儲存是一種將資料分散儲存在多個節點上的儲存方式,每個節點都可以獨立地儲存和訪問資料。這種分散式的儲存方式可以提供更高的可靠性、可擴充套件性和效能。
分散式儲存的原理是將資料切分成多個塊或物件,並分佈在多個儲存節點上。每個節點都可以儲存一部分資料,並提供資料的讀寫操作。透過將資料分佈在多個節點上,分散式儲存系統可以實現資料的冗餘備份和自動恢復,從而提供高可用性和資料的可靠性。分散式儲存系統通常具有以下特點:
可擴充套件性:分散式儲存系統可以根據需求動態地擴充套件儲存容量和效能。透過新增新的儲存節點,系統可以線性地擴充套件儲存能力,以適應不斷增長的資料量和訪問需求。
高可用性:由於資料被冗餘備份在多個節點上,當某個節點發生故障時,系統可以自動將資料從其他節點恢復到新的節點上,保證資料的可用性和可靠性。
高效能:分散式儲存系統可以透過並行處理和負載均衡來提供高效能的資料訪問。資料可以並行地從多個節點讀取或寫入,從而提高資料的訪問速度。
彈性和容錯性:分散式儲存系統具有彈性和容錯性,可以適應節點故障、網路故障和其他異常情況。系統可以自動檢測和修復故障,保證資料的完整性和可用性。
資料一致性:分散式儲存系統通常提供一致性模型,確保資料在多個節點之間的一致性。系統可以透過複製、副本和資料同步機制來實現資料的一致性。
傳統分散式系統是指由多臺計算機透過網路連線,協同完成共同的任務的系統。傳統分散式系統相比於集中式系統,具有一些優點,如可擴充套件性、高可用性、資源共享等。但是,傳統分散式系統也面臨著一些挑戰和問題,主要有以下幾個方面:
通訊開銷:傳統分散式系統需要透過網路進行資料的傳輸和同步,這會增加通訊的延遲和成本,影響系統的效能和響應速度。同時,網路的不可靠性也會導致資料的丟失、重複、亂序等問題,給系統的一致性和正確性帶來困難。
資料一致性:傳統分散式系統中,資料被分散儲存在多個節點上,每個節點都可以對資料進行讀寫操作。這就需要保證資料在多個節點之間的一致性,即不同節點上的資料應該是相同的或者等價的。但是,由於網路的延遲和故障,以及節點的併發操作,資料的一致性很難實現,需要採用複雜的一致性協議和演算法,如兩階段提交、Paxos、Raft等。
事務管理:傳統分散式系統中,事務是指一組需要原子性、一致性、隔離性和永續性的操作。事務管理是指保證事務的正確執行和提交的過程。在分散式系統中,事務管理比較複雜,因為涉及到多個節點的協調和同步,以及網路的不確定性和故障的處理。傳統分散式系統需要實現分散式事務的機制,如分散式鎖、分散式日誌、分散式恢復等。
安全性:傳統分散式系統中,資料和服務需要在不同的節點和網路之間進行傳輸和訪問,這就存在著資料的安全性和保密性的風險。例如,資料可能被竊取、篡改、偽造等,服務可能被拒絕、攻擊、濫用等。傳統分散式系統需要實現資料和服務的安全保護的機制,如加密、認證、授權、審計等。
共識機制是分散式系統中實現一致性的重要機制。在分散式系統中,多個節點需要共同達成一致的決策,例如在區塊鏈系統中確定下一個區塊的生成者、在分散式資料庫中確定資料更新的先後順序等。
共識演算法是實現共識機制的具體方法,不同的共識演算法有不同的特點和適用場景。常見的共識演算法包括:
工作量證明(Proof of Work,PoW):是比特幣等區塊鏈系統採用的共識演算法,它要求節點透過解決複雜的數學難題來競爭記賬權,從而保證網路的安全性和去中心化。
權益證明(Proof of Stake,PoS):是一種替代PoW的共識演算法,它要求節點根據自己持有的代幣數量或者抵押的代幣數量來參與記賬,從而減少能源消耗和算力競爭。
委託權益證明(Delegated Proof of Stake,DPoS):是一種改進的PoS共識演算法,它要求節點透過投票選舉出一定數量的超級節點來負責記賬,從而提高網路的效率和穩定性。
實用拜占庭容錯演算法(Practical Byzantine Fault Tolerance,PBFT):是一種基於投票的共識演算法,它要求節點透過多輪的訊息交換來達成一致,從而支援容錯故障節點和惡意節點。
Raft演算法:是一種簡化的PBFT演算法,它要求節點透過選舉出一個領導者來負責日誌複製和提交,從而實現資料的一致性。
比特幣(Bitcoin)是一種去中心化的數字貨幣,由一個人或一群人在2008年使用化名中本聰(Satoshi Nakamoto)的身份提出。它的設計允許使用者在沒有中央權威機構,比如銀行或政府的情況下,進行點對點的交易。
區塊鏈(Blockchain)
區塊鏈是比特幣的底層技術,它是一個公開的分散式賬本,記錄著所有比特幣交易的歷史。每個區塊包含一系列交易,並且每個新區塊都會被新增到鏈上,形成一系列的區塊鏈。區塊鏈的這種設計確保了交易記錄不能被修改,因為任何對舊區塊的更改都需要重新計算後續所有區塊的複雜加密演算法。
挖礦(Mining)
比特幣網路依賴於“挖礦”過程來確認交易並新增新的比特幣到系統中。挖礦是一個競爭性的過程,礦工使用強大的計算能力解決複雜的數學難題以贏得新區塊的建立權。每當一個新區塊被建立,礦工會得到一定數量的比特幣作為獎勵,這是比特幣進入流通的方式。
去中心化
比特幣是去中心化的,沒有中央伺服器或管理機構。每個在比特幣網路上的節點(計算機)都擁有一個完整的區塊鏈副本,這使得比特幣網路對任何單點故障具有極高的抵抗力。
四、實驗核心步驟及程式碼
本次實驗,我決定透過閱讀比特幣原始碼,學習區塊鏈的相關知識。
節點將新交易收集到一個塊中,將它們雜湊到雜湊樹中,並掃描nonce值以使塊的雜湊值滿足工作量證明要求。當他們解決工作量證明時,他們將區塊廣播給每個人,並將該區塊新增到區塊鏈中。區塊中的第一筆交易是一筆特殊的交易,它創造了一個由創造者擁有的新硬幣
#define foreach BOOST_FOREACH
class CBlockIndex;
class CDiskBlockIndex;
const uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
CBlockIndex* pindexBest = NULL;
CBlockIndex* pindexGenesisBlock = NULL;
map<uint256, CBlockIndex*> mapBlockIndex;
unsigned int nTransactionsUpdated = 0;
int nBestHeight = -1;
uint256 hashBestChain = 0;
string GetAppDir()
{string strDir;
if (!strSetDataDir.empty())
{ strDir = strSetDataDir; }
else if (getenv("APPDATA"))
{strDir = strprintf("%s\\Bitcoin", getenv("APPDATA")); }
else if (getenv("USERPROFILE"))
{string strAppData = strprintf("%s\\Application Data", getenv("USERPROFILE"));
static bool fMkdirDone;
if (!fMkdirDone)
{ fMkdirDone = true;
_mkdir(strAppData.c_str()); }
strDir = strprintf("%s\\Bitcoin", strAppData.c_str()); }
else
{ return "."; }
static bool fMkdirDone;
if (!fMkdirDone)
{ fMkdirDone = true;
_mkdir(strDir.c_str()); }
return strDir;}
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode)
{ if (nFile == -1)
return NULL;
FILE* file = fopen(strprintf("%s\\blk%04d.dat", GetAppDir().c_str(), nFile).c_str(), pszMode);
if (!file)
return NULL;
if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w'))
{ if (fseek(file, nBlockPos, SEEK_SET) != 0)
{ fclose(file);
return NULL; } }
return file;}
class CBlock
{public:
// header
int nVersion; //版本號
uint256 hashPrevBlock; //前一區塊的雜湊值
uint256 hashMerkleRoot; //Merkle根
unsigned int nTime; //時間戳
unsigned int nBits; //難度目標
unsigned int nNonce; //隨機數
// network and disk
vector<CTransaction> vtx; //交易
// memory only
mutable vector<uint256> vMerkleTree;
//建構函式
CBlock()
{ SetNull(); }
//儲存時需要序列化
IMPLEMENT_SERIALIZE
(READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
// ConnectBlock depends on vtx being last so it can calculate offset
if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY)))
READWRITE(vtx);
else if (fRead)
const_cast<CBlock*>(this)->vtx.clear(); )
//初始化值置0
void SetNull()
{ nVersion = 1;
hashPrevBlock = 0;
hashMerkleRoot = 0;
nTime = 0;
nBits = 0;
nNonce = 0;
vtx.clear();
vMerkleTree.clear(); }
//得到區塊頭的雜湊值
uint256 GetHash() const
{return Hash(BEGIN(nVersion), END(nNonce)); }
//生成MerkleTree
uint256 BuildMerkleTree() const
{ vMerkleTree.clear();
foreach(const CTransaction& tx, vtx)
vMerkleTree.push_back(tx.GetHash());
int j = 0;
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
{ for (int i = 0; i < nSize; i += 2)
{int i2 = min(i+1, nSize-1);
vMerkleTree.push_back(Hash(BEGIN(vMerkleTree[j+i]), END(vMerkleTree[j+i]),
BEGIN(vMerkleTree[j+i2]), END(vMerkleTree[j+i2]))); }
j += nSize; }
return (vMerkleTree.empty() ? 0 : vMerkleTree.back()); }
//開始區塊資料儲存部分
//得到區塊儲存的資料夾
string GetAppDir()
{ string strDir;
if (!strSetDataDir.empty())
{strDir = strSetDataDir; }
else if (getenv("APPDATA"))
{strDir = strprintf("%s\\Bitcoin", getenv("APPDATA"));}
else if (getenv("USERPROFILE"))
{ string strAppData = strprintf("%s\\Application Data", getenv("USERPROFILE"));
static bool fMkdirDone;
if (!fMkdirDone)
{fMkdirDone = true;
_mkdir(strAppData.c_str());
}strDir = strprintf("%s\\Bitcoin", strAppData.c_str());
}
else
{return ".";}
static bool fMkdirDone;
if (!fMkdirDone)
{ fMkdirDone = true;
_mkdir(strDir.c_str());
} return strDir;}
//開啟區塊檔案
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode)
{ if (nFile == -1)
return NULL;
FILE* file = fopen(strprintf("%s\\blk%04d.dat", GetAppDir().c_str(), nFile).c_str(), pszMode);
if (!file)
return NULL;
if (nBlockPos != 0 && !strchr(pszMode, 'a') && !strchr(pszMode, 'w'))
{ if (fseek(file, nBlockPos, SEEK_SET) != 0)
{ fclose(file);
return NULL; }}
return file;}
//開啟區塊檔案,追加
FILE* AppendBlockFile(unsigned int& nFileRet)
{nFileRet = 0;
loop
{FILE* file = OpenBlockFile(nCurrentBlockFile, 0, "ab");
if (!file)
return NULL;
if (fseek(file, 0, SEEK_END) != 0)
return NULL;
// FAT32 filesize max 4GB, fseek and ftell max 2GB, so we must stay under 2GB
if (ftell(file) < 0x7F000000 - MAX_SIZE)
{ nFileRet = nCurrentBlockFile;
return file; }
fclose(file);
nCurrentBlockFile++; }}
//區塊資料儲存到檔案中
bool WriteToDisk(bool fWriteTransactions, unsigned int& nFileRet, unsigned int& nBlockPosRet)
{ // Open history file to append
CAutoFile fileout = AppendBlockFile(nFileRet);
if (!fileout)
return error("CBlock::WriteToDisk() : AppendBlockFile failed");
if (!fWriteTransactions)
fileout.nType |= SER_BLOCKHEADERONLY;
// Write index header
unsigned int nSize = fileout.GetSerializeSize(*this);
fileout << FLATDATA(pchMessageStart) << nSize;
// Write block
nBlockPosRet = ftell(fileout);
if (nBlockPosRet == -1)
return error("CBlock::WriteToDisk() : ftell failed");
fileout << *this;
return true;
}
//列印區塊資料資訊
void print() const
{printf("\nBlock Info is:\n");
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d)\n",
GetHash().ToString().substr(0,14).c_str(),
nVersion,
hashPrevBlock.ToString().substr(0,14).c_str(),
hashMerkleRoot.ToString().substr(0,6).c_str(),
nTime, nBits, nNonce,
vtx.size() );
printf("\nTransaction Info is:\n");
for (int i = 0; i < vtx.size(); i++)
{ printf(" ");
vtx[i].print();}
printf("\nMerkleTree Info is:\n");
printf(" vMerkleTree: ");
for (int i = 0; i < vMerkleTree.size(); i++)
{printf("%s ", vMerkleTree[i].ToString().substr(0,6).c_str());
}
printf("\n");
}
int64 GetBlockValue(int64 nFees) const;
bool ReadFromDisk(const CBlockIndex* blockindex, bool fReadTransactions);
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos);
bool CheckBlock() const;
bool AcceptBlock();
//除錯資料庫儲存
bool WriteBlockIndex(const CDiskBlockIndex& blockindex);
template<typename K, typename T>
bool Write(const K& key, const T& value, bool fOverwrite=true)
{// Key
CDataStream ssKey(SER_DISK);
ssKey.reserve(1000);
ssKey << key;
//Dbt datKey(&ssKey[0], ssKey.size());
// Value
CDataStream ssValue(SER_DISK);
ssValue.reserve(10000);
ssValue << value;
//Dbt datValue(&ssValue[0], ssValue.size());
// Write
//int ret = pdb->put(GetTxn(), &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE));
// Clear memory in case it was a private key
//memset(datKey.get_data(), 0, datKey.get_size());
//寫入檔案-測試資料
// Open history file to append
unsigned int nFileRet = 1;
bool fWriteTransactions = true;
CAutoFile fileout = AppendBlockFile(nFileRet);
if (!fileout)
return error("CBlock::WriteBlockIndexToDisk() : AppendBlockFile failed");
if (!fWriteTransactions)
fileout.nType |= SER_BLOCKHEADERONLY;
// Write index header
unsigned int nSize = ssKey.size() + ssValue.size();
fileout << FLATDATA(pchMessageStart) << nSize;
// Write block
unsigned int nBlockPosRet = ftell(fileout);
if (nBlockPosRet == -1)
return error("CBlock::WriteIndexToDisk() : ftell failed");
fileout << ssKey << ssValue;
return true;
}
bool WriteHashBestChain(uint256 hashBestChain)
{
return Write(string("hashBestChain"), hashBestChain);
}
};
//區塊鏈是一個樹狀結構,從創世區塊位於根,每個區塊可能有多個候選區塊作為下一個區塊。Pprey和pnext透過/main/最長鏈連結一條路徑。一個blockindex可能有多個pprev指向它,但pnext只指向最長的分支,或者如果區塊不是最長鏈的一部分,pnext將為空
class CBlockIndex
{public:
CBlockIndex* pprev; //前一區塊索引指標
CBlockIndex* pnext; //後一區塊索引指標
//block info
const uint256* phashBlock; //區塊雜湊值
unsigned int nFile; //區塊檔名
unsigned int nBlockPos; //區塊在檔案中的偏移位置
int nHeight; //區塊高度
// block header //區塊頭
int nVersion; //版本號
uint256 hashMerkleRoot; //Merkle根
unsigned int nTime; //時間戳
unsigned int nBits; //難度目標值
unsigned int nNonce; //隨機數
CBlockIndex()
{ phashBlock = NULL;
pprev = NULL;
pnext = NULL;
nFile = 0;
nBlockPos = 0;
nHeight = 0;
nVersion = 0;
hashMerkleRoot = 0;
nTime = 0;
nBits = 0;
nNonce = 0;
}
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block)
{ phashBlock = NULL;
pprev = NULL;
pnext = NULL;
nFile = nFileIn;
nBlockPos = nBlockPosIn;
nHeight = 0;
nVersion = block.nVersion;
hashMerkleRoot = block.hashMerkleRoot;
nTime = block.nTime;
nBits = block.nBits;
nNonce = block.nNonce;
}
uint256 GetBlockHash() const
{ return *phashBlock;
}
bool IsInMainChain() const
{ return (pnext || this == pindexBest);
}
bool EraseBlockFromDisk()
{ //Open history file
CAutoFile fileout = OpenBlockFile(nFile, nBlockPos, "rb+");
if (!fileout)
return false;
// Overwrite with empty null block
CBlock block;
block.SetNull();
fileout << block;
return true;
}
enum { nMedianTimeSpan=11 };
int64 GetMedianTimePast() const
{unsigned int pmedian[nMedianTimeSpan];
unsigned int* pbegin = &pmedian[nMedianTimeSpan];
unsigned int* pend = &pmedian[nMedianTimeSpan];
const CBlockIndex* pindex = this;
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
*(--pbegin) = pindex->nTime;
sort(pbegin, pend);
return pbegin[(pend - pbegin)/2]; }
int64 GetMedianTime() const
{ const CBlockIndex* pindex = this;
for (int i = 0; i < nMedianTimeSpan/2; i++)
{ if (!pindex->pnext)
return nTime;
pindex = pindex->pnext; }
return pindex->GetMedianTimePast(); }
string ToString() const
{return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d, nHeight=%d, merkle=%s, hashBlock=%s)",
pprev, pnext, nFile, nBlockPos, nHeight,
hashMerkleRoot.ToString().substr(0,6).c_str(),
GetBlockHash().ToString().substr(0,14).c_str()); }
void print() const
{ printf("%s\n", ToString().c_str());}};
// Used to marshal pointers into hashes for db storage.
class CDiskBlockIndex : public CBlockIndex
{public:
uint256 hashPrev; //前一區塊的雜湊值
uint256 hashNext; //後一區塊的雜湊值
CDiskBlockIndex()
{ hashPrev = 0;
hashNext = 0;}
explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex)
{hashPrev = (pprev ? pprev->GetBlockHash() : 0);
hashNext = (pnext ? pnext->GetBlockHash() : 0);
cout << "3-####this is hashPrev string:" << hashPrev.ToString().c_str() << endl;
cout << "4-####this is hashNext string:" << hashNext.ToString().c_str() << endl;
}
IMPLEMENT_SERIALIZE
(if (!(nType & SER_GETHASH))
READWRITE(nVersion);
READWRITE(hashNext);
READWRITE(nFile);
READWRITE(nBlockPos);
READWRITE(nHeight);
// block header
READWRITE(this->nVersion);
READWRITE(hashPrev);
READWRITE(hashMerkleRoot);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce); )
uint256 GetBlockHash() const
{CBlock block;
block.nVersion = nVersion;
block.hashPrevBlock = hashPrev;
block.hashMerkleRoot = hashMerkleRoot;
block.nTime = nTime;
block.nBits = nBits;
block.nNonce = nNonce;
return block.GetHash(); }
string ToString() const
{string str = "CDiskBlockIndex(";
str += CBlockIndex::ToString();
str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)",
GetBlockHash().ToString().c_str(),
hashPrev.ToString().c_str(),
hashNext.ToString().c_str());
return str; }
void print() const
{ printf("%s\n", ToString().c_str());}};
bool CBlock :: AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
{// Check for duplicate
uint256 hash = GetHash();
cout<<"\n1-####this is hash string:"<<hash.GetHex().c_str()<<endl;
if (mapBlockIndex.count(hash))
return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,14).c_str());
// Construct new block index object
CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this);
if (!pindexNew)
return error("AddToBlockIndex() : new CBlockIndex failed");
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first);
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{ pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;}
cout << "2-####this is hash string:" << pindexNew->ToString().c_str() << endl;
CDiskBlockIndex tmpDiskBlockIndex = CDiskBlockIndex(pindexNew);
cout << "5-#### this is tmpDiskBlockIndex string:" << tmpDiskBlockIndex.ToString().c_str() << endl;
bool writeIndex = WriteBlockIndex(tmpDiskBlockIndex); //除錯資料庫儲存
// New best
if (pindexNew->nHeight > nBestHeight)
{if (pindexGenesisBlock == NULL && hash == hashGenesisBlock)
{ pindexGenesisBlock = pindexNew;
WriteHashBestChain(hash); }
else if (hashPrevBlock == hashBestChain)
{// Adding to current best branch:DB }
else
{// New best branch:DB }
// New best link
hashBestChain = hash;
pindexBest = pindexNew;
nBestHeight = pindexBest->nHeight;
nTransactionsUpdated++;
printf("\nAddToBlockIndex: new best=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight);}
// Relay wallet transactions that haven't gotten in yet
if (pindexNew == pindexBest)
{ //RelayWalletTransactions(); }
return true;
}
//除錯區塊索引寫入資料庫
bool CBlock :: WriteBlockIndex(const CDiskBlockIndex& blockindex)
{
return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex);
}
整體結構
CBlockHeader類定義了比特幣區塊頭部的結構,包括:
nVersion:區塊的版本號,用於追蹤協議升級。
hashPrevBlock:鏈中前一個區塊的256位雜湊值。
hashMerkleRoot:這個區塊中包含的交易的Merkle樹的根雜湊。
nTime:區塊的時間戳。
nBits:目標難度的緊湊表示形式。
nNonce:用於工作量證明演算法的一個隨機數。
CBlock類繼承自CBlockHeader,增加了構成區塊內容的交易列表(vtx)。
還包括一個只在記憶體中使用的布林標誌fChecked,用來表示是否已經檢查了區塊的有效性。
CBlockLocator結構是一種在區塊鏈中有效定位區塊的方式。它包含了一個區塊雜湊列表(vHave),這些雜湊在朝向創世區塊的方向上是指數級間隔的。這使得節點能夠快速傳達它們的狀態,並且與另一個節點的區塊鏈找到一個共同的點。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
//交易-輸入-前一個輸出:交易雜湊值、輸出索引
class COutPoint
{
public:
uint256 hash;
unsigned int n;
COutPoint()
{
SetNull();
}
COutPoint(uint256 hashIn, unsigned int nIn)
{
hash = hashIn; n = nIn;
}
IMPLEMENT_SERIALIZE(READWRITE(FLATDATA(*this));)
void SetNull()
{
hash = 0; n = -1;
}
bool IsNull() const
{
return (hash == 0 && n == -1);
}
string ToString() const
{
return strprintf("COutPoint(%s, %d)", hash.ToString().substr(0,6).c_str(), n);
}
void print() const
{
printf("%s\n", ToString().c_str());
}
};
//交易-輸入:前一個輸出、解鎖指令碼、序列號
// An input of a transaction. It contains the location of the previous
// transaction's output that it claims and a signature that matches the
// output's public key.
class CTxIn
{
public:
COutPoint prevout;
CScript scriptSig;
unsigned int nSequence;
CTxIn()
{
nSequence = UINT_MAX;
}
IMPLEMENT_SERIALIZE
(
READWRITE(prevout);
READWRITE(scriptSig);
READWRITE(nSequence);
)
string ToString() const
{
string str;
str += strprintf("CTxIn(");
str += prevout.ToString();
if (prevout.IsNull())
str += strprintf(", coinbase %s", HexStr(scriptSig.begin(), scriptSig.end(), false).c_str());
else
str += strprintf(", scriptSig=%s", scriptSig.ToString().substr(0,24).c_str());
if (nSequence != UINT_MAX)
str += strprintf(", nSequence=%u", nSequence);
str += ")";
return str;
}
void print() const
{
printf("%s\n", ToString().c_str());
}
};
//交易-輸出:coinbase值、鎖定指令碼
// An output of a transaction. It contains the public key that the next input
// must be able to sign with to claim it.
class CTxOut
{
public:
int64 nValue;
CScript scriptPubKey;
public:
CTxOut()
{
SetNull();
}
IMPLEMENT_SERIALIZE
(
READWRITE(nValue);
READWRITE(scriptPubKey);
)
void SetNull()
{
nValue = -1;
scriptPubKey.clear();
}
bool IsNull()
{
return (nValue == -1);
}
uint256 GetHash() const
{
return SerializeHash(*this);
}
string ToString() const
{
if (scriptPubKey.size() < 6)
return "CTxOut(error)";
return strprintf("CTxOut(nValue=%I64d.%08I64d, scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,24).c_str());
}
void print() const
{
printf("%s\n", ToString().c_str());
}
};
//交易:版本號、輸入、輸出、鎖定時間
// The basic transaction that is broadcasted on the network and contained in
// blocks. A transaction can contain multiple inputs and outputs.
class CTransaction
{
public:
int nVersion;
vector<CTxIn> vin;
vector<CTxOut> vout;
int nLockTime;
CTransaction()
{
SetNull();
}
IMPLEMENT_SERIALIZE
(
READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
)
void SetNull()
{
nVersion = 1;
vin.clear();
vout.clear();
nLockTime = 0;
}
bool IsNull() const
{
return (vin.empty() && vout.empty());
}
//得到交易的雜湊值
uint256 GetHash() const
{
return SerializeHash(*this);
}
//交易字串
string ToString() const
{
string str;
str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n",
GetHash().ToString().substr(0,6).c_str(),
nVersion,
vin.size(),
vout.size(),
nLockTime);
for (int i = 0; i < vin.size(); i++)
str += " " + vin[i].ToString() + "\n";
for (int i = 0; i < vout.size(); i++)
str += " " + vout[i].ToString() + "\n";
return str;
}
//列印交易詳情
void print() const
{
printf("%s", ToString().c_str());
}
};
區塊結構分析
區塊是透過前後連結構成區塊鏈的一種容器資料結構。它由描述區塊主要資訊的區塊頭和包含若干交易資料的區塊共同組成。區塊頭是80位元組,而平均每個交易至少是250位元組,而且平均每個區塊至少包含超過500個交易。所以,一個區塊是比區塊頭大好多的資料體。這也是比特幣驗證交易是否存在採用Merkcle樹的原因。
區塊識別符號
BlockHash 區塊雜湊值,是透過SHA256演算法對區塊頭資訊進行雜湊得到的,這個值必須滿足POW的DifficultyTarget,該區塊才被認為有效。同時,也是區塊的唯一識別符號,可以透過透過bitcoin-cli根據BlockHash查詢區塊資訊(文章開頭我們就使用過)
BlockHeight 區塊高度,是用來標示區塊在區塊鏈中的位置。創世區塊高度為0,每一個加在後面的區塊,區塊高度遞增1。可以透過bitcoin-cli根據高度查詢區塊雜湊值(文章開頭我們就使用過)
注:BlockHeight並不是唯一識別符號,當區塊鏈出現臨時分叉時,會有兩個區塊的高度值相同的情況。
類 COutPoint
這個類定義了一個輸出點(outpoint),它唯一地標識了前一個交易中的特定輸出,包括一個交易雜湊(Txid hash)和一個索引(uint32_t n),指向它的輸出向量(vout)。
類 CTxIn
這個類表示一個交易輸入,包括對前一個輸出的引用(COutPoint prevout),一個簽名指令碼(CScript scriptSig),它提供滿足引用輸出的scriptPubKey所需的資料,一個序列號(uint32_t nSequence),以及一個可選的見證指令碼(CScriptWitness scriptWitness),用於隔離見證(segregated witness)交易。
類 CTxOut
這個類表示一個交易輸出,包含一個以聰(satoshi)為單位的值(CAmount nValue)和一個公鑰指令碼(CScript scriptPubKey),指定了必須滿足才能花費輸出的條件。
結構體 CMutableTransaction
這個結構體被宣告但在提供的程式碼片段中沒有定義。它可能表示交易的一個可變版本,可以在最終確定前進行修改。
結構體 TransactionSerParams
這個結構體用於指定序列化引數,特別是在序列化過程中是否允許證人資料。
序列化/反序列化函式
UnserializeTransaction 和 SerializeTransaction 模板定義瞭如何將交易型別序列化和反序列化為位元組流。這些函式處理基本和擴充套件的交易序列化格式,其中擴充套件格式包括隔離見證資料。
輔助函式 CalculateOutputValue
這個模板函式透過累加交易的 vout 向量中每個 CTxOut 物件的 nValue 來計算交易中所有輸出的總值。
類 CTransaction
這個類代表了廣播在比特幣網路上的基礎交易結構。一個交易可以包含多個輸入和輸出。
成員變數
vin:一個 CTxIn 型別的向量,表示交易的輸入。
vout:一個 CTxOut 型別的向量,表示交易的輸出。
nVersion:交易資料格式的版本號。
nLockTime:交易的鎖定時間,指定了交易不能被打包進區塊鏈的最早時間。
私有成員變數
m_has_witness:標記交易是否包含隔離見證(SegWit)資料。
hash:交易的ID(txid),代表交易的唯一識別符號。
m_witness_hash:如果交易包含隔離見證資料,這個變數則代表交易的隔離見證ID(wtxid)。
成員函式
CTransaction():建構函式,用於從一個 CMutableTransaction 物件建立一個不可變的 CTransaction 物件。
Serialize():一個模板函式,用於將交易序列化為位元組流。
Deserialize():一個反序列化建構函式,用於從位元組流中重建一個不可變的交易物件。
IsNull():判斷交易是否為空,即沒有輸入和輸出。
GetHash() 和 GetWitnessHash():獲取交易的識別符號。
GetValueOut():計算交易輸出的總金額。
GetTotalSize():獲取整個交易的大小,包括見證資料。
IsCoinBase():檢查這個交易是否是一個coinbase交易,即區塊獎勵交易。
ToString():生成交易的字串表示。
HasWitness():檢查交易是否包含隔離見證資料。
結構體 CMutableTransaction
這是 CTransaction 的一個可變版本。它允許修改交易,通常用於構建一個新交易。
成員函式
CMutableTransaction():建構函式,用來建立和修改可變交易。
Serialize() 和 Unserialize():序列化和反序列化函式。
GetHash():計算並返回交易的雜湊值。
HasWitness():檢查交易是否包含隔離見證資料。
CTxMemPool:
這是記憶體池的主要類,用於管理待確認的交易。它包括了很多成員函式,用於新增、移除、獲取交易等操作。
CTxMemPool 類中包含了多個索引容器(boost::multi_index_container),以便根據不同的標準對記憶體池中的交易進行排序和訪問。這些標準包括交易的雜湊、交易的費率等。
這個類還包含了一些用於處理交易的限制和計算的方法,例如計算交易的祖先和後代、檢查交易的大小限制等。
TxMempoolInfo:
這是一個結構體,用於儲存記憶體池中的交易的資訊,包括交易本身、進入記憶體池的時間、交易的費用、交易的虛擬大小等資訊。
CCoinsViewMemPool:
這個類實現了 CCoinsView 介面,可以將記憶體池中的交易帶入檢視中。它不會檢查記憶體池交易的支出,而是提供對所有未花費的基礎 CCoinsView 中的硬幣以及來自任何記憶體池交易的輸出的訪問。
該類還可以用於跟蹤由正在驗證的交易建立的硬幣,這些硬幣只是臨時儲存在 m_temp_added 中,無法重新整理到後端。
mempoolentry_txid 和 mempoolentry_wtxid:
這兩個結構體分別用於從 CTxMemPoolEntry 或 CTransactionRef 中提取交易雜湊和見證交易雜湊。
#include "block_test.h"
int main()
{
// Genesis Block:
// GetHash() = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
// hashMerkleRoot = 0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
// txNew.vin[0].scriptSig = 486604799 4 0x736B6E616220726F662074756F6C69616220646E6F63657320666F206B6E697262206E6F20726F6C6C65636E61684320393030322F6E614A2F33302073656D695420656854
// txNew.vout[0].nValue = 5000000000
// txNew.vout[0].scriptPubKey = 0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704 OP_CHECKSIG
// block.nVersion = 1
// block.nTime = 1231006505
// block.nBits = 0x1d00ffff
// block.nNonce = 2083236893
// CBlock(hash=000000000019d6, ver=1, hashPrevBlock=00000000000000, hashMerkleRoot=4a5e1e, nTime=1231006505, nBits=1d00ffff, nNonce=2083236893, vtx=1)
// CTransaction(hash=4a5e1e, ver=1, vin.size=1, vout.size=1, nLockTime=0)
// CTxIn(COutPoint(000000, -1), coinbase 04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73)
// CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B)
// vMerkleTree: 4a5e1e
//區塊、交易
CBlock block;
CTransaction txNew;
const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks";
//新交易賦值
txNew.vin.resize(1);
txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((unsigned char*)pszTimestamp, (unsigned char*)pszTimestamp + strlen(pszTimestamp));
txNew.vout.resize(1);
txNew.vout[0].nValue = 50 * COIN;
txNew.vout[0].scriptPubKey = CScript() << CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << OP_CHECKSIG;
//交易加入到區塊中
block.vtx.push_back(txNew);
//區塊頭
block.nVersion = 1;
block.hashPrevBlock = 0;
block.hashMerkleRoot = block.BuildMerkleTree();
block.nTime = 1231006505;
block.nBits = 0x1d00ffff;
block.nNonce = 2083236893;
printf("Genesis Block GetHash is:%s\n\n", block.GetHash().ToString().c_str());
printf("Genesis Block's hash is:%s\n\n", hashGenesisBlock.ToString().c_str());
block.print();
assert(block.hashMerkleRoot == uint256("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
assert(block.GetHash() == hashGenesisBlock);
// Start new block file-創世區塊儲存
unsigned int nFile;
unsigned int nBlockPos;
if (!block.WriteToDisk(!fClient, nFile, nBlockPos))
{
return error("block-Genesis : writing genesis block to disk failed");
}
else
{
printf("block-Genesis ok!\n");
}
//創世區塊索引儲存
if (!block.AddToBlockIndex(nFile, nBlockPos))
return error("LoadBlockIndex() : genesis block not accepted");
return 0;
}
e此程式碼的最後部分定義了一個 DeploymentHeight 函式,用於根據傳入的BuriedDeployment列舉值返回對應的啟用高度。例如,如果傳入DEPLOYMENT_CSV,函式返回的是CSVHeight的值。
這些引數和結構是構建和維護比特幣核心軟體中共識規則的基礎,開發者和網路參與者會根據這些規則來驗證交易和區塊的有效性,確保網路的穩定執行。
- 實驗總結
透過閱讀比特幣原始碼,我深入瞭解了區塊鏈的底層技術和比特幣的基本原理,對比特幣的去中心化、加密機制、挖礦過程以及交易確認等方面有了更深入的理解。同時,透過實際程式碼的閱讀,對比特幣網路中的資料結構、功能模組和共識規則有了更為直觀和深入的認識。
區塊鏈是一種利用密碼學和分散式共識機制實現資料的去中心化、安全和不可篡改的技術。區塊鏈技術在金融、政務、供應鏈、物聯網等領域有著廣泛的應用前景,但也面臨著一些技術挑戰和難題,主要有以下幾個方面:
擴充套件性:區塊鏈的擴充套件性是指系統能夠處理的交易數量和速度。由於區塊鏈的分散式和去中心化的特性,每個節點都需要儲存和驗證所有的交易和區塊,這就限制了區塊鏈的吞吐量和響應時間。這會增加網路的負擔和分叉的風險。另一些解決方案是透過分層擴充套件的方式,將部分交易從主鏈上轉移到側鏈或者狀態通道上,從而減輕主鏈的壓力,但這也會犧牲一定的安全性和去中心化程度。
隱私性:區塊鏈的隱私性是指系統能夠保護使用者的身份和交易資訊不被洩露或者濫用。由於區塊鏈的公開和透明的特性,每個節點都可以檢視所有的交易和賬戶資訊,這可能會暴露使用者的個人隱私和商業機密。例如,透過分析區塊鏈上的交易資料,可以推斷出使用者的消費習慣、資產狀況、社交關係等敏感資訊。為了提高區塊鏈的隱私性,有些解決方案是透過加密或者混淆的方式,對使用者的身份或者交易的內容進行隱藏或者掩蓋,但這也會增加計算的複雜度和成本。另一些解決方案是透過零知識證明或者同態加密等技術,實現在不洩露具體資訊的情況下進行驗證或者計算,但這也需要更高的密碼學水平和安全保障。
能源效率:區塊鏈的能源效率是指系統能夠以較低的能源消耗實現資料的共識和儲存。由於區塊鏈的去中心化和安全的特性,每個節點都需要參與到共識機制和資料同步的過程中,這就需要大量的計算能力和電力消耗。例如,比特幣的工作量證明(PoW)共識機制,要求節點透過解決複雜的數學難題來競爭記賬權,這就導致了大量的算力浪費和能源消耗。據估計,比特幣的年度能耗已經超過了一些國家的總能耗,這不僅增加了區塊鏈的執行成本,還對環境造成了負面影響。為了提高區塊鏈的能源效率,有些解決方案是透過改變共識機制,從工作量證明(PoW)轉向權益證明(PoS)或者委託權益證明(DPoS)等,這樣可以減少算力的競爭和能源的消耗,但這也會增加中心化的風險和攻擊的可能性。
法律法規:區塊鏈的法律法規是指系統能夠符合不同國家和地區的法律法規要求。由於區塊鏈的跨境和去中心化的特性,每個節點都可以跨越國界和地域進行交易和協作,這就涉及到不同的法律法規和監管標準。例如,區塊鏈上的數字貨幣和代幣,在不同的國家和地區有著不同的定義和認可度,有些國家和地區對其進行了合法化和規範化,有些國家和地區對其進行了禁止和打擊,這就給區塊鏈的發展和應用帶來了不確定性和風險。為了適應區塊鏈的法律法規,有些解決方案是透過建立沙箱機制,為區塊鏈的創新和試驗提供一定的空間和便利,但這也需要區塊鏈的參與者和監管者之間進行充分的溝通和協調。