0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

关于比特币源码技术的分析

C语言专家集中营 来源:未知 作者:胡薇 2018-04-02 16:55 次阅读

本章将介绍一些新的数据结构。除非特别说明,本章提到的所有的类与函数均位于main.h或main.cpp。

每个节点均保存有一个区块链副本。区块链由相互连接的区块(CBlock实例)所构成。每个区块包含多笔交易(CTransaction实力)。为了存储、搜索、读取在内存与磁盘中的区块和交易信息比特币引入了一些访问类。它们包括:CBlockIndex和CDiskBlockIndex用于索引区块,CDiskTxPos和CTxIndex用于索引交易。

CBlock

CBlock类表示一个区块。

[cpp]view plaincopy

classCBlock

{

public:

//header

intnVersion;

uint256hashPrevBlock;

uint256hashMerkleRoot;

unsignedintnTime;

unsignedintnBits;

unsignedintnNonce;

//networkanddisk

vectorvtx;

//memoryonly

mutablevectorvMerkleTree;

CBlock()

{

SetNull();

}

IMPLEMENT_SERIALIZE

(

READWRITE(this->nVersion);

nVersion=this->nVersion;

READWRITE(hashPrevBlock);

READWRITE(hashMerkleRoot);

READWRITE(nTime);

READWRITE(nBits);

READWRITE(nNonce);

//ConnectBlockdependsonvtxbeinglastsoitcancalculateoffset

if(!(nType&(SER_GETHASH|SER_BLOCKHEADERONLY)))

READWRITE(vtx);

elseif(fRead)

const_cast(this)->vtx.clear();

)

voidSetNull()

{

nVersion=1;

hashPrevBlock=0;

hashMerkleRoot=0;

nTime=0;

nBits=0;

nNonce=0;

vtx.clear();

vMerkleTree.clear();

}

boolIsNull()const

{

return(nBits==0);

}

uint256GetHash()const

{

returnHash(BEGIN(nVersion),END(nNonce));

}

uint256BuildMerkleTree()const

{

vMerkleTree.clear();

foreach(constCTransaction&tx,vtx)

vMerkleTree.push_back(tx.GetHash());

intj=0;

for(intnSize=vtx.size();nSize>1;nSize=(nSize+1)/2)

{

for(inti=0;i< nSize; i += 2)  

{

inti2=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());

}

vectorGetMerkleBranch(intnIndex)const

{

if(vMerkleTree.empty())

BuildMerkleTree();

vectorvMerkleBranch;

intj=0;

for(intnSize=vtx.size();nSize>1;nSize=(nSize+1)/2)

{

inti=min(nIndex^1,nSize-1);

vMerkleBranch.push_back(vMerkleTree[j+i]);

nIndex>>=1;

j+=nSize;

}

returnvMerkleBranch;

}

staticuint256CheckMerkleBranch(uint256hash,constvector&vMerkleBranch,intnIndex)

{

if(nIndex==-1)

return0;

foreach(constuint256&otherside,vMerkleBranch)

{

if(nIndex&1)

hash=Hash(BEGIN(otherside),END(otherside),BEGIN(hash),END(hash));

else

hash=Hash(BEGIN(hash),END(hash),BEGIN(otherside),END(otherside));

nIndex>>=1;

}

returnhash;

}

boolWriteToDisk(boolfWriteTransactions,unsignedint&nFileRet,unsignedint&nBlockPosRet)

{

//Openhistoryfiletoappend

CAutoFilefileout=AppendBlockFile(nFileRet);

if(!fileout)

returnerror("CBlock::WriteToDisk():AppendBlockFilefailed");

if(!fWriteTransactions)

fileout.nType|=SER_BLOCKHEADERONLY;

//Writeindexheader

unsignedintnSize=fileout.GetSerializeSize(*this);

fileout<< FLATDATA(pchMessageStart) << nSize;  

//Writeblock

nBlockPosRet=ftell(fileout);

if(nBlockPosRet==-1)

returnerror("CBlock::WriteToDisk():ftellfailed");

fileout<< *this;  

returntrue;

}

boolReadFromDisk(unsignedintnFile,unsignedintnBlockPos,boolfReadTransactions)

{

SetNull();

//Openhistoryfiletoread

CAutoFilefilein=OpenBlockFile(nFile,nBlockPos,"rb");

if(!filein)

returnerror("CBlock::ReadFromDisk():OpenBlockFilefailed");

if(!fReadTransactions)

filein.nType|=SER_BLOCKHEADERONLY;

//Readblock

filein>>*this;

//Checktheheader

if(CBigNum().SetCompact(nBits)>bnProofOfWorkLimit)

returnerror("CBlock::ReadFromDisk():nBitserrorsinblockheader");

if(GetHash()>CBigNum().SetCompact(nBits).getuint256())

returnerror("CBlock::ReadFromDisk():GetHash()errorsinblockheader");

returntrue;

}

voidprint()const

{

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());

for(inti=0;i< vtx.size(); i++)  

{

printf("");

vtx[i].print();

}

printf("vMerkleTree:");

for(inti=0;i< vMerkleTree.size(); i++)  

printf("%s",vMerkleTree[i].ToString().substr(0,6).c_str());

printf("\n");

}

int64GetBlockValue(int64nFees)const;

boolDisconnectBlock(CTxDB&txdb,CBlockIndex*pindex);

boolConnectBlock(CTxDB&txdb,CBlockIndex*pindex);

boolReadFromDisk(constCBlockIndex*blockindex,boolfReadTransactions);

boolAddToBlockIndex(unsignedintnFile,unsignedintnBlockPos);

boolCheckBlock()const;

boolAcceptBlock();

};

这里有几点注意:

一个区块包含若干笔交易,并存放于vector vtx。

一个区块的哈希,由函数GetHash()生成,是通过计算区块的块头(block-header)而不是整个区块数据所得到。更具体讲,哈希由成员变量nVersion到nNonce生成,而并不包括交易容器vtx。

成员变量uint256 hashMerkleRoot是块头的一部分。它由成员函数BuildMerkleTree()生成,该函数将所有交易vtx作为输入参数,并最终生成一个256位哈希hashMerkleRoot。

uint256 hashPrevBlock是当前区块之前的区块哈希,被包括在块头当中。因此,一个区块的哈希与它在区块链当中先前区块的哈希有关。

CBlock::BuildMerkleTree()

BuildMerkleTree()建立一个梅克尔树并返回树根。该树的每个节点,包括叶节点和中间节点,均为一个uint256哈希值。该树被扁平化并放置在vector vMerkleTree中。为了扁平化梅克尔树,该函数首先将第0层的叶节点放入vMerkleTree,接着将第1层的中间结点紧接着放在它们之后,向上重复该过程直到到达根结点。

位于第25行的变量j用于索引vMerkleTree的起始位置来放入树每一层的第一个节点。变量nSize是每层的节点个数。从第0层开始,nSize=vtx.size(),并且j=0。

最外层的for循环访问树的每一层。每层节点个数是上一层的一半(nSize=(nSize+1)/2,第26行)。里面的for循环依次访问某一层的每对节点,指针i每一步增量为2。节点对(i和i2)的哈希附加在容器vMerkleTree,其中i2=i+1。当i到达最后一对节点,i=nSize-1当nSize为奇数,或者i=nSize-2当nSize为偶数。在前者情形,最后一个节点(i2=i=nSize-1)则与其自身的复制组成节点对。

CBlock::GetMerkleBranch()

该函数返回一个梅克尔树分支。整个梅克尔树已被扁平化至容器vMerkleTree。参数nIndex指向容器vMerkleTree[nIndex]的那个节点。它的取值从0到vtx.size()。因此,nIndex永远指向一个叶节点,为某笔交易的哈希。

返回的梅克尔树分支包含了所有从vMerkleTre[nIndex]到树根所伴随的节点。回忆建立梅克尔树的过程,每一层的节点两两相配,成为下一层的节点。在第0层,若nIndex为偶数,则与节点vMerkleTree[nIndex]伴随的节点是vMerkleTree[nIndex+1];若nIndex为奇数,则伴随节点为vMerkleTree[nIndex-1]。为了简化,我们把vMerkleTree[nIndex]称作节点A0,其伴随节点为B0。A0与B0相结合并生成另一个位于第1层节点A1。在第1层,A1的伴随节点为B1,两者配对并生成位于第2层的节点A2。重复该过程,直到到达梅克尔树根。节点[A0, A1, A2, ...]形成一条从A0到根节点的路径,它们的伴随节点[B0, B1, ...]则构成所返回的梅克尔分支。

为了找到伴随节点,该函数遍历树的每一层(第4行)。在每一层,指针nIndex比它前一层的值减半。因此,nIndex永远指向路径节点[A0, A1, A2, ...]。另一个指针i设为min(nIndex^1, nSize-1),位于第46行。或运算符^1翻转nIndex最后一位,而不修改其他位。这将保证i永远指向nIndex所指向节点的伴随节点,例如节点[B0, B1, B2, ...]。

你可能会问,梅克尔树的作用是什么。它可以快速地验证一笔交易是否被包括在一个区块当中。下面我们将会介绍。

CBlock::CheckMerkleBranch()

这个函数接受它的第一个参数hash,用它检查第二个参数vMerkleBranch,并返回一个生成的梅克尔树根。如果所返回的树根与hashMerkleRoot相同,则hash所指向交易的哈希被包括在该CBlock当中。第三个参数nIndex是第一个参数在vMerkleTree当中的位置;它与GetMerkleBranch()的唯一参数是相同的。

因此,若需要检查一笔交易是否被包括在一个CBlock里,则仅需生成该笔交易的哈希并调用该函数,验证返回值是否与hashMerkleroot相同。

CBlock::WriteToDisk()与CBlock::ReadFromDisk()

WriteToDisk将一个CBlock写入磁盘。在第80行,它调用AppendBlockFile()。

[cpp]view plaincopy

FILE*OpenBlockFile(unsignedintnFile,unsignedintnBlockPos,constchar*pszMode)

{

if(nFile==-1)

returnNULL;

FILE*file=fopen(strprintf("%s\\blk%04d.dat",GetAppDir().c_str(),nFile).c_str(),pszMode);

if(!file)

returnNULL;

if(nBlockPos!=0&&!strchr(pszMode,'a')&&!strchr(pszMode,'w'))

{

if(fseek(file,nBlockPos,SEEK_SET)!=0)

{

fclose(file);

returnNULL;

}

}

returnfile;

}

staticunsignedintnCurrentBlockFile=1;

FILE*AppendBlockFile(unsignedint&nFileRet)

{

nFileRet=0;

loop

{

FILE*file=OpenBlockFile(nCurrentBlockFile,0,"ab");

if(!file)

returnNULL;

if(fseek(file,0,SEEK_END)!=0)

returnNULL;

//FAT32filesizemax4GB,fseekandftellmax2GB,sowemuststayunder2GB

if(ftell(file)< 0x7F000000 - MAX_SIZE)  

{

nFileRet=nCurrentBlockFile;

returnfile;

}

fclose(file);

nCurrentBlockFile++;

}

}

AppendBlockFile()的第一个参数nFileRet用于存放返回值。分析AppendBlockFile()和OpenBlockFile()源码显示nFileRet是一系列数据文件,以“blk0001.data”,“blk0002.data”的形式命名。区块被保存在这些文件中。它们被称作区块数据文件。

返回WriteToDisk(),第三个参数nBlockPosRet是当前CBlock在区块数据文件中存放的起始位置。在Block::WriteToDisk()的第89行,nBlockRet被赋给返回值ftell(),其为区块数据文件的当前指针位置。在其后面,当前CBlock以序列化后的形式被存储在区块数据文件里(第92行)。

ReadFromDisk()可以很直观地被理解。

CBlockIndex与CDiskBlockIndex

在上面对CBlock::WriteToDisk()和CBlock::ReadFromDisk()的分析显示区块被保存在一系列区块数据文件当中。CBlockIndex将这些信息全部封装在一个单独类CBlockIndex。

[cpp]view plaincopy

//

//Theblockchainisatreeshapedstructurestartingwiththe

//genesisblockattheroot,witheachblockpotentiallyhavingmultiple

//candidatestobethenextblock.pprevandpnextlinkapaththroughthe

//main/longestchain.Ablockindexmayhavemultiplepprevpointingback

//toit,butpnextwillonlypointforwardtothelongestbranch,orwill

//benulliftheblockisnotpartofthelongestchain.

//

classCBlockIndex

{

public:

constuint256*phashBlock;

CBlockIndex*pprev;

CBlockIndex*pnext;

unsignedintnFile;

unsignedintnBlockPos;

intnHeight;

//blockheader

intnVersion;

uint256hashMerkleRoot;

unsignedintnTime;

unsignedintnBits;

unsignedintnNonce;

//......

uint256GetBlockHash()const

{

return*phashBlock;

}

//......

};

除了nFile和nBlockPos,CBlockIndex还包括一份它所指向的区块头的副本(除了字段hashPrevBlock,可通过pprev->phashBlock访问)。这样可以使计算得到区块哈希不需要从区块数据文件中读取整个区块。

从注释的1-6行,我们可以知道pprev指向其在区块链中紧挨着的前一个区块索引,pnext则指向区块链中紧挨着的下一个区块索引。

如果你仔细注意的话,你会发现我提到的是“区块链中的区块索引”而不是“区块链中的区块”。这里有点让人头晕,毕竟区块链是由区块而不是区块索引构成的,不是吗?的确,区块链由区块所构成,但你也可以换一种说法:区块链由区块索引构成。这是一位区块的完整数据被保存在磁盘上的区块数据文件当中,而一个区块索引则通过区块的成员变量nFile和nBlockPos引用这个区块。指针pprev和pnext分别指向一个区块的前一个和下一个区块,以构成整个区块链。所以,区块索引与区块链同样具有意义。

尽管被称为区块链,比特币的区块链其实并不是线性结构,而是树状结构。这棵树的根节点是创世区块,被硬编码至源码当中。其他区块则在创世区块之上依次累积。在一个区块上累积两个或更多区块是合法的。当这种情况发生之后,区块链将产生一个分叉。一个区块链可能含有若干分叉,而分叉出来的分支则可能进一步分叉。

由于分叉的存在,多个区块索引的pprev字段可能指向同一个前任。这些区块索引每一个都开始一个分支,而这些分支都基于同一个前任区块。然而,前任区块的pnext字段只能指向开始最长一条分支的继任区块。从树根(创世区块)到最长分支的最后一个区块的路径被称为最长链,或最佳链,或这个区块链的主链。

指针phashBlock指向该区块的哈希,可以通过CBlockIndex内嵌的区块头现场计算。

nHeight是该区块在区块链中的高度。区块链由高度为0的区块开始,即创世区块。紧接着的区块高度为1,以此类推。

CBlockIndex实例仅保存在内存当中。若要将区块索引存入磁盘,衍生类CDiskBlockIndex在这里定义。

[cpp]view plaincopy

classCDiskBlockIndex:publicCBlockIndex

{

public:

uint256hashPrev;

uint256hashNext;

CDiskBlockIndex()

{

hashPrev=0;

hashNext=0;

}

explicitCDiskBlockIndex(CBlockIndex*pindex):CBlockIndex(*pindex)

{

hashPrev=(pprev?pprev->GetBlockHash():0);

hashNext=(pnext?pnext->GetBlockHash():0);

}

IMPLEMENT_SERIALIZE

(

if(!(nType&SER_GETHASH))

READWRITE(nVersion);

READWRITE(hashNext);

READWRITE(nFile);

READWRITE(nBlockPos);

READWRITE(nHeight);

//blockheader

READWRITE(this->nVersion);

READWRITE(hashPrev);

READWRITE(hashMerkleRoot);

READWRITE(nTime);

READWRITE(nBits);

READWRITE(nNonce);

)

uint256GetBlockHash()const

{

CBlockblock;

block.nVersion=nVersion;

block.hashPrevBlock=hashPrev;

block.hashMerkleRoot=hashMerkleRoot;

block.nTime=nTime;

block.nBits=nBits;

block.nNonce=nNonce;

returnblock.GetHash();

}

//......

};

该类拥有另外两个成员变量:前任区块索引和继任区块索引的哈希,hashPrev和hashNext。不要将它们与CBlockIndex中的pprev和pnext搞混,后者是指向区块索引的指针,而前者则是区块的哈希。

CBlockIndex和CDiskBlockIndex本身均不带有哈希;它们永远用所指向区块的哈希辨识自己。从CDiskBlockIndex的构造器(第15行)可以总结出这点。在这个构造器里,hashPrev被赋值为pprev->GetBlockHash(),其中pprev是一个CBlockIndex的指针。如果你检查CBlockIndex的成员函数GetBlockHash,你可以看到它返回的是前任区块的哈希。对于CDiskBlockIndex,它的成员函数GetBlockHash()同样返回它所指向区块的哈希。

CBlockIndex没有序列化方法,而CDiskBlockIndex则通过宏IMPLEMENT_SERIALIZE实现序列化。这是因为后者需要保存在磁盘里,而前者只存在于内存当中。

CMerkleTx与CWallet

在了解梅克尔树和CBlock之后,我们一起检查另外两个由CTransaction衍生的重要的类:CMerkleTx与CWallet。

[cpp]view plaincopy

classCMerkleTx:publicCTransaction

{

public:

uint256hashBlock;

vectorvMerkleBranch;

intnIndex;

//memoryonly

mutableboolfMerkleVerified;

CMerkleTx()

{

Init();

}

CMerkleTx(constCTransaction&txIn):CTransaction(txIn)

{

Init();

}

voidInit()

{

hashBlock=0;

nIndex=-1;

fMerkleVerified=false;

}

int64GetCredit()const

{

//Mustwaituntilcoinbaseissafelydeepenoughinthechainbeforevaluingit

if(IsCoinBase()&&GetBlocksToMaturity()>0)

return0;

returnCTransaction::GetCredit();

}

IMPLEMENT_SERIALIZE

(

nSerSize+=SerReadWrite(s,*(CTransaction*)this,nType,nVersion,ser_action);

nVersion=this->nVersion;

READWRITE(hashBlock);

READWRITE(vMerkleBranch);

READWRITE(nIndex);

)

intSetMerkleBranch(constCBlock*pblock=NULL);

intGetDepthInMainChain()const;

boolIsInMainChain()const{returnGetDepthInMainChain()>0;}

intGetBlocksToMaturity()const;

boolAcceptTransaction(CTxDB&txdb,boolfCheckInputs=true);

boolAcceptTransaction(){CTxDBtxdb("r");returnAcceptTransaction(txdb);}

};

CTransaction的实例被收集并生成一个CBlock。对于指定的CTransaction实例,它的梅克尔分支,和它在CBlock的vector vtx字段中的位置,可以用于查找该实例是否被包含在这个CBlock里面。这便是为什么CMerkleTx包含另外两个字段:一个梅克尔分支vMerkleBranch,和一个位置索引nIndex。一个CMerkleTx实例携带有这两个字段使其能方便地被验证是否属于一个区块。

uint256 hashBlock是该交易所在CBlock的哈希。

CWalletTx进一步延伸CMerkleTx,包括了关于“只有(该笔交易的)拥有者关心”的信息。这些成员字段将被提及,当我们在代码当中遇到它们的时候。

[cpp]view plaincopy

classCWalletTx:publicCMerkleTx

{

public:

vectorvtxPrev;

mapmapValue;

vector

unsignedintfTimeReceivedIsTxTime;

unsignedintnTimeReceived;//timereceivedbythisnode

charfFromMe;

charfSpent;

////probablyneedtosigntheorderinfosoknowitcamefrompayer

//memoryonly

mutableunsignedintnTimeDisplayed;

//......

};

CDiskTxPos与CTxIndex

这两个类用来索引交易。

[cpp]view plaincopy

classCDiskTxPos

{

public:

unsignedintnFile;

unsignedintnBlockPos;

unsignedintnTxPos;

//......

}

classCTxIndex

{

public:

CDiskTxPospos;

vectorvSpent;

}

CDiskTxPos中的nFile,nBlockPos和nTxPos分别是区块数据文件的序号,区块在区块数据文件当中的位置和交易在区块数据中的位置。因此,CDiskTxPos包含了从区块数据文件中找到某笔交易的全部信息。

一个CTxIndex是一个数据库记录“包含有一笔交易和花费这笔交易输出的交易在磁盘的位置”。一笔交易可以很容易地找到它的来源交易(参见第一章),但反过来不行。vector vSpent包含所有由CDiskTxPos pos指向的某笔交易作为来源交易的交易的位置,例如所有花费CDiskTxPos pos指向的交易的交易。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程
    +关注

    关注

    88

    文章

    3431

    浏览量

    92218
  • 比特币
    +关注

    关注

    57

    文章

    7000

    浏览量

    139295

原文标题:比特币源码技术分析-3

文章出处:【微信号:C_Expert,微信公众号:C语言专家集中营】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    #硬声创作季 #区块链 区块链技术与应用-09-BTC-比特脚本-1

    区块链比特
    水管工
    发布于 :2022年10月09日 00:09:46

    #硬声创作季 #区块链 区块链技术与应用-09-BTC-比特脚本-2

    区块链比特
    水管工
    发布于 :2022年10月09日 00:10:46

    #硬声创作季 #区块链 区块链技术与应用-09-BTC-比特脚本-3

    区块链比特
    水管工
    发布于 :2022年10月09日 00:12:00

    #硬声创作季 区块链:1.1认识比特

    区块链比特
    Mr_haohao
    发布于 :2022年10月16日 23:44:12

    #硬声创作季 区块链:1.6比特的分叉

    区块链比特
    Mr_haohao
    发布于 :2022年10月16日 23:48:32

    #硬声创作季 区块链:1.7比特系统总结

    区块链比特
    Mr_haohao
    发布于 :2022年10月16日 23:49:07

    #硬声创作季 区块链与加:1.2比特的来源

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:42:07

    #硬声创作季 区块链与加:1.3比特是什么

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:42:44

    #硬声创作季 区块链与加:1.6比特分叉

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:44:29

    #硬声创作季 区块链与加:2.4比特回顾

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:47:11

    究竟比特是什么

    。  然而,比特却在大洋彼岸赢得了肯定,华尔街最大的机构之一——美银美林宣布其研究范围正式覆盖比特,并对比特
    发表于 12-15 11:17

    比特交易所系统开发如何适应海外市场?

    比特交易所系统开发如何适应海外市场?近日央视携《对话》栏目帮助电视机前的观众们更好的探寻区块链是什么、会为我们的生活带来哪些改变、这项技术具体应该如何应用。目前大家对区块链的技术非常
    发表于 05-28 14:55

    回收蚂蚁矿机,回收比特大陆矿机,收购比特矿机

    回收蚂蚁矿机,回收比特大陆矿机,收购比特矿机全国上门回收蚂蚁矿机比特大陆矿机包括(蚂蚁矿机T9+ 10.5T、蚂蚁矿机S9i 13T、蚂蚁矿机S9i 13.5T、蚂蚁矿机S9i 14
    发表于 06-01 10:32

    回收比特矿机,回收虚拟挖矿机,收购神马矿机

    ================================蚂蚁矿机v94t挖什么2018zui火爆zui潮流的投资项目——挖矿,经过比特国际地位以及价值日益提高,国内区块链技术
    发表于 06-04 11:14

    时代周刊:为什么比特是自由的源泉?

    银行账户,但他无法冻结比特钱包;在难民营,你可能无法找到一家银行,但只要有网络,你就可以收到比特,你不需要获得任何人的批准,也不用证明自己的身份。
    发表于 01-01 23:23