我们以节点去读节点列表为例:
首先来读NodeAction.sol,这个合约在用户部署系统合约的时候就已经部署在区块链上了
struct NodeInfo{
string id;
string ip;
uint port;
NodeType category;
string desc; // 节点描述
string CAhash; // 节点机构证书哈希
string agencyinfo;
uint idx;
uint blocknumber;
}
mapping(string =>NodeInfo) m_nodedata;
string[] m_nodeids;
这里定义了一个包含节点信息的结构体和建立了一个全局的键值对映射,这是一个散列表,m_nodedata是全局维护的一个节点列表,所有的节点信息都将加入到这个列表中,然后这个合约还定义了几个函数,包括updateIdx,registerNode,getNode,getNodeIdx等,这些函数提供了对节点列表进行基本的管理功能,实际上就是对这个全局的m_nodedata表进行修改,可是这个列表只是存在与系统合约当中,节点是怎么从合约中读到这个列表的呢?
现在回到BCOS源码:
在nodeconnparamsManager.cpp中,我们可以找到一个节点配置管理类NodeConnParamsManager,这个类中有一个函数callSysContractData,这个函数可以取得合约中的节点列表,在一系列比较之后维护一个该类中的节点列表_mNodeConnParams,这个列表决定了该节点将和那些节点连成链,现在我们只关心它是怎么取得合约中的节点列表的。
可以看到_pSysContractApi->getAllNode(blockNum, vNodeParams);
这个getAllNode函数通过块号,读取到了合约中节点列表的数据,将之保存在vNodeParams,那么我们就追踪这个函数。
在nodeconnparamsManagerAPI.h中找到_pSysContractApi的定义
std::shared_ptr<SystemContractApi> _pSysContractApi = nullptr;
这是一个智能指针,指向一个类型为SystemContractApi的对象(当然在这里时为nullptr)
在systemcontractapi.h找到这个SystemContractApi类,果然发现了:
virtual void getAllNode(int /*<0 代表最新块*/ ,std::vector< NodeConnParams> & )
{
}
在这里这个函数并未实现,在systemcontract.cpp中找到这个函数的实现:
{
DEV_READ_GUARDED(m_locknode)//锁
{
DEV_READ_GUARDED(m_blocklock)//锁
{
LOG(TRACE)<< "SystemContract::getAllNode _blocknumber=" <<_blocknumber
<<",m_tempblock.info().number()="<<m_tempblock->info().number()<<",m_nodelist.size()="<<m_nodelist.size();
if( (_blocknumber == m_tempblock->info().number()) || ( _blocknumber < 0 ) )
{
for( size_t i=0;i<m_nodelist.size();i++)
{
_nodevector.push_back(m_nodelist[i]);
}
return;
}
}
//下面指定块号获取节点列表
tempGetAllNode(_blocknumber,_nodevector);
}
}//function
好啦,现在来分析这个函数,上面和线程有关的操作不去管它,重点看if后面的部分,发现用的俩个变量m_tempblock和m_nodelist不知道是啥,找到定义去看一下:
std::shared_ptr<Block> m_tempblock
std::vector< NodeConnParams> m_nodelist;
m_tempblock是一个指向Block类型的指针,m_tempblock->info().number()的意思就是从所指的那个区块中找到区块头的信息,然后从区块头中返回区块号,m_nodelist就是一个在SystemContract类中维护的节点列表。
那么这个意思就是首先去判断传进来的块号是不是小于零,或者当前块,是的话就不用那么麻烦的去读了,直接把类里维护的那个列表传出去就行,这里可见这个类的列表是实时更新的。
我们可以找到更新的函数updateNode()
this->getNodeFromContract(std::bind(&SystemContract::call,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3),m_nodelist);
实际从中合约获得节点列表靠的是getNodeFromContract函数,不过不着急,殊途同归,我们继续往下看。
如果传入的块号不是最新块号的话,就调用
tempGetAllNode(_blocknumber,_nodevector);
...
Block tempblock = m_client->block(_blocknumber );
tempblock.setEvmEventLog(true);
auto tempCall = [&](Address const& _to, bytes const& _inputdata,bool ) {
ExecutionResult ret;
try
{
srand((unsigned)utcTime());
struct timeval tv;
gettimeofday(&tv, NULL);
u256 nonce = (u256)(rand() + rand() + tv.tv_usec);
u256 gas = tempblock.gasLimitRemaining() ;
u256 gasPrice = 100000000;
Transaction t(0, gasPrice, gas, _to, _inputdata, nonce);
t.forceSender(m_god);
ret = tempblock.execute(m_client->blockChain().lastHashes(), t, Permanence::Reverted);
}
catch (...)
{
// TODO: Some sort of notification of failure.
LOG(ERROR) << boost::current_exception_diagnostic_information() << endl;
LOG(INFO) << "SystemContract::tempCall call Fail!" << toString(_inputdata);
}
return ret;
};
getNodeFromContract(tempCall,_nodelist);
...
分析这个函数
首先通过块号去读块,怎么读的不管它,反正是读到了保存在tempblock 里,接着设置了一个匿名函数,这个函数的实质是生成了一个交易,运行这个交易可以得到一个回复,由刚才得到的那个块tempblock来执行这个交易,这个函数执行了吗?没执行,它实际上是一个交易的模板,作为getNodeFromContract的参数使用
终于到了getNodeFromContract函数了
Address nodeAction;
nodeAction = getRoute("NodeAction");
首先得到了我们最先分析的NodeAction.sol合约的地址
bytes inputdata1 = abiIn("getNodeIdsLength()");
ExecutionResult ret1 = _call(nodeAction, inputdata1,false);
u256 nodeidslen = abiOut<u256>(ret1.output);
通过刚才的函数模板来生成和执行交易,交易的内容是要调用nodeAction 这个地址下的getNodeIdsLength函数,得到返回值变成节点读的懂的格式放在nodeidslen 中
for ( size_t i = 0; i < (size_t)nodeidslen; i++)
{
//第一步,先拿到nodeid
bytes inputdata2 = abiIn("getNodeId(uint256)", (u256)i);
ExecutionResult ret2 = _call(nodeAction, inputdata2,false);
string nodeid = abiOut<string>(ret2.output);
//第二步,拿到node 信息
string ip = ""; //节点ip
u256 port = 0; //节点端口
u256 category = 0; //NodeConnParams应该定义枚举
string desc; //节点描述
string cahash = ""; //节点的机构信息
string agencyinfo="";
u256 idx; //节点索引
u256 blocknumber;
bytes inputdata4 = abiIn("getNode(string)",nodeid);
ExecutionResult ret4 = _call(nodeAction, inputdata4,false);
bytesConstRef o(&(ret4.output));
dev::eth::ContractABI().abiOut<>(o,ip,port,category,desc,cahash,agencyinfo,blocknumber);
bytes inputdata5 = abiIn("getNodeIdx(string)",nodeid);
ExecutionResult ret5 = _call(nodeAction, inputdata5,false);
bytesConstRef o2(&(ret5.output));
dev::eth::ContractABI().abiOut<>(o2,idx);
NodeConnParams nodeconnparam;
nodeconnparam._sNodeId = nodeid;
nodeconnparam._sAgencyInfo = agencyinfo;
nodeconnparam._sIP = ip;
nodeconnparam._iPort =(int) port;
nodeconnparam._iIdentityType = (int)category;
nodeconnparam._sAgencyDesc=desc;
nodeconnparam._sCAhash=cahash;
nodeconnparam._iIdx=idx;
_nodelist.push_back(nodeconnparam);
LOG(TRACE) << "SystemContract::updateNode Node[" << i << "]=" << nodeconnparam.toString();
}//for
//排序连续
sort(_nodelist.begin(), _nodelist.end(), [&](const NodeConnParams & a, const NodeConnParams & b) { return a._iIdx < b._iIdx; });
后面的代码都是一样的逻辑,通过调用部署好的智能合约,获得在合约中维护的那个节点列表,然后转化成节点看的懂的格式,保存在相应的节点参数类中,此时节点内部就有了一个和智能合约上一模一样的节点列表。
最后留一个问题:
节点读合约和我们使用web3.js读合约和明显是不同的,那么块内部究竟是怎么执行交易的呢,下一步就是研究一下tempblock.execute函数到底是个啥。