区块链游戏源码(兄弟连区块链教程btcpool矿池源码分析StratumServer模块解析)

  区块链游戏源码(兄弟连区块链教程btcpool矿池源码分析StratumServer模块解析)

兄弟连区块链教程btcpool矿池源码分析StratumServer模块解析,2018年下半年,区块链行业正逐渐褪去发展之初的浮躁、回归理性,表面上看相关人才需求与身价似乎正在回落。但事实上,正是初期泡沫的渐退,让人们更多的关注点放在了区块链真正的技术之上。

  

# btcpool矿池-StratumServer模块解析

  

## 核心机制总结

  

* 接收的job延迟超过60秒将丢弃

  

* 如果job中prevHash与本地job中prevHash不同,即为已产生新块,job中isClean状态将置为true

  

* true即要求矿机立即切换job

  

* 三种情况下将向矿机下发新job:

  

* 收到新高度的job

  

* 过去一个job为新高度且为空块job,且最新job为非空块job

  

* 达到预定的时间间隔30秒

  

* 最近一次下发job的时间将写入文件(由file_last_notify_time指定)

  

* 本地job有效期为300秒

  

* 每10秒拉取一次新用户列表(由list_id_api_url指定),用户写入本地map中

  

* sserver最大可用SessionId数为16777214

  

* btcpool支持BtcAgent扩展协议和Stratum协议,使用magic_number(0x7F)区分

  

* 处理Stratum协议:

  

* suggest_target与suggest_difficulty等价,用于设置初始挖矿难度,需在subscribe之前请求

  

* 使用sessionID作为extraNonce1_以确保矿机任务不重复

  

* authorize之前有15秒读超时,authorize之后有10分钟读超时,10分钟无提交将断开连接

  

* 初始难度为16384,或从suggest_difficulty指定,下次将一次调整到位保持10s提交share

  

* 每个session会维护一个localJobs_队列,队列长度为10条

  

* share被拒绝的几种情况:

  

* JOB_NOT_FOUND,localJobs_队列中该job已被挤出

  

* DUPLICATE_SHARE,share已提交过,已提交的share会计入submitShares_

  

* JOB_NOT_FOUND,jobRepository_中job不存在,即job已过期(300秒过期时间)

  

* JOB_NOT_FOUND,jobRepository_中job状态为Stale,即job是旧的非新job

  

* TIME_TOO_OLD,share中提交的nTime小于job规定的minTime

  

* TIME_TOO_OLD,share中提交的nTime比当前时间大10分钟

  

* LOW_diffICULTY,share中提交的hash不满足难度目标

  

* 处理BtcAgent扩展协议:

  

* Agent下矿机默认难度也为16384

  

* 使用Agent sessionID作为extraNonce2_前半部分,以确保Agent下矿机任务不重复

  

* 矿池下发新任务时,如session为BtcAgent,将为Agent下所有矿机计算难度

  

* 如难度发生变更,将按难度不同,分别构造多条CMD_MINING_SET_DIFF指令一并下发处理

  

## StratumServer命令使用

  

```shell

  

sserver -c sserver。cfg -l LOG_dir

  

#-c指定sserver配置文件

  

#-l指定日志目录

  

```

  

## sserver。cfg配置文件

  

```shell

  

//是否使用testnet

  

testnet = true;

  

//kafka集群

  

kafka = {

  

brokers = "1。1。1。1!9092,2。2。2。2!9092,3。3。3。3!9092";

  

};

  

//sserver配置

  

sserver = {

  

//IP和端口

  

ip = "0。0。0。0";

  

port = 3333;

  

//server id,全局唯一,取值范围[1, 255]

  

id = 1;

  

//最近一次挖矿通知时间写入文件,用于监控

  

file_last_notify_time = "/work/xxx/sserver_lastnotifytime。txt";

  

//如果启用模拟器,所有share均被接受,用于测试

  

enable_simulator = false;

  

//如果启用,所有share都将成块并被提交,用于测试

  

enable_submit_invalid_block = false;

  

//两次share提交的间隔时间

  

share_avg_seconds = 10;

  

};

  

users = {

  

//用户列表api

  

list_id_api_url = "example。com/get_user_id_list";

  

};

  

```

  

## SOLVED_SHARE消息

  

```c++

  

if (isSubmitInvalidBlock_ == true bnBlockHash <;= bnNetworkTarget) {

  

//

  

// build found block

  

//

  

FoundBlock foundBlock;

  

foundBlock。jobId_ = share。jobId_;

  

foundBlock。workerId_ = share。workerHashId_;

  

foundBlock。userId_ = share。userId_;

  

foundBlock。height_ = sjob->;height_;

  

memcpy(foundBlock。header80_, (const uint8_t *)&;header, sizeof(CBlockHeader));

  

snprintf(foundBlock。workerFullName_, sizeof(foundBlock。workerFullName_),

  

"%s", workFullName。c_str());

  

// send

  

sendSolvedShare2Kafka(&;foundBlock, coinbaseBin);

  

// mark jobs as stale

  

jobRepository_->;markAllJobsAsStale();

  

LOG(INFO) <;<; ">;>;>;>; found a new block! " <;<; blkHash。ToString()

  

<;<; ", jobId! " <;<; share。jobId_ <;<; ", userId! " <;<; share。userId_

  

<;<; ", by! " <;<; workFullName <;<; " <;<;<;<;";

  

}

  

```

  

## 计算挖矿难度

  

```c++

  

//构造函数

  

//kMinDiff_为最小难度,static const uint64 kMinDiff_ = 64;

  

//kMaxDiff_为最大难度,static const uint64 kMaxDiff_ = 4611686018427387904ull;

  

//kDefaultDiff_为默认初始难度,static const uint64 kDefaultDiff_ = 16384;

  

//kDiffWindow_为N个share的时间窗口,static const time_t kDiffWindow_ = 900;

  

//kRecordSeconds_为1个share的时间,static const time_t kRecordSeconds_ = 10;

  

//sharesNum_和shares_初始值均为90

  

DiffController(const int32_t shareAvgSeconds) !

  

startTime_(0),

  

minDiff_(kMinDiff_), curDiff_(kDefaultDiff_), curHashRateLevel_(0),

  

sharesNum_(kDiffWindow_/kRecordSeconds_), /* every N seconds as a record */

  

shares_ (kDiffWindow_/kRecordSeconds_)

  

{

  

if (shareAvgSeconds >;= 1 &;&; shareAvgSeconds <;= 60) {

  

shareAvgSeconds_ = shareAvgSeconds;

  

} else {

  

shareAvgSeconds_ = 8;

  

}

  

}

  

//代码btcpool/src/StratumSession。h

  

//计算挖矿难度

  

//不低于最小难度64

  

uint64 DiffController!!calcCurDiff() {

  

uint64 diff = _calcCurDiff();

  

if (diff <; minDiff_) {

  

diff = minDiff_;

  

}

  

return diff;

  

}

  

uint64 DiffController!!_calcCurDiff() {

  

const time_t now = time(nullptr);

  

const int64 k = now / kRecordSeconds_;

  

const double sharesCount = (double)sharesNum_。sum(k);

  

if (startTime_ == 0) { // first time, we set the start time

  

startTime_ = time(nullptr);

  

}

  

const double kRateHigh = 1。40;

  

const double kRateLow = 0。40;

  

//时间窗口(900秒)内预期的share数

  

double expectedCount = round(kDiffWindow_ / (double)shareAvgSeconds_);

  

//return now >;= startTime_ + kDiffWindow_;

  

if (isFullWindow(now)) { /* have a full window now */

  

// big miner have big expected share count to make it looks more smooth。

  

expectedCount *= minerCoefficient(now, k);

  

}

  

if (expectedCount >; kDiffWindow_) {

  

//最多1秒提交1个,预期share数最大为900

  

expectedCount = kDiffWindow_; // one second per share is enough

  

}

  

// this is for very low hashrate miner, eg。 USB miners

  

// should received at least one share every 60 seconds

  

//非完整时间窗口、且时间已超过60s、提交的share数小于(60秒1个)、当前难度大于或等于2倍最小难度

  

//此时降低难度为之前1/2

  

if (!isFullWindow(now) &;&; now >;= startTime_ + 60 &;&;

  

sharesCount <;= (int32_t)((now - startTime_)/60。0) &;&;

  

curDiff_ >;= minDiff_*2) {

  

setCurDiff(curDiff_ / 2);

  

sharesNum_。mapMultiply(2。0);

  

return curDiff_;

  

}

  

// too fast

  

//如果提交share数超过预期数的1。4倍时

  

if (sharesCount >; expectedCount * kRateHigh) {

  

//如果share数大于预期share数,比特币购买且当前难度<;最大难度时,提升难度为原来2倍

  

while (sharesNum_。sum(k) >; expectedCount &;&;

  

curDiff_ <; kMaxDiff_) {

  

setCurDiff(curDiff_ * 2);

  

sharesNum_。mapDivide(2。0); //share数/2

  

}

  

return curDiff_;

  

}

  

// too slow

  

//如果是完整时间窗口,且当前难度大于或等于2被最小难度

  

if (isFullWindow(now) &;&; curDiff_ >;= minDiff_*2) {

  

//如果share数低于预期数的0。4,且当前难度大于或等于2倍最小难度,降低难度为原来的1/2

  

while (sharesNum_。sum(k) <; expectedCount * kRateLow &;&;

  

curDiff_ >;= minDiff_*2) {

  

setCurDiff(curDiff_ / 2);

  

sharesNum_。mapMultiply(2。0); //share数乘2

  

}

  

assert(curDiff_ >;= minDiff_);

  

return curDiff_;

  

}

  

return curDiff_;

  

}

  

```

  

## sserver校验share的机制

  

```shell

  

//本地job列表localJobs_最多保留最近10条任务,如有新任务,将挤出1条老任务。如果share所对应的job未在本地列表中,将StratumError!!JOB_NOT_FOUND

  

//本地share列表submitShares_中,如果已有本条share,即重复,将StratumError!!DUPLICATE_SHARE

  

//校验share不通过

  

// job列表exJobs_没有找到job,exJobs_中job有300秒过期时间,过期将删除,报StratumError!!JOB_NOT_FOUND

  

// share中nTime小于job的最小时间,过老,报StratumError!!TIME_TOO_OLD

  

// share中nTime超过job中的nTime 10分钟,过新,报StratumError!!TIME_TOO_NEW

  

// 区块哈希>;job难度目标,不合格,报StratumError!!LOW_DIFFICULTY

  

```

  

## sserver下发新job的机制

  

1、如果收到新高度statum job,将立即下发新job

  

```c++

  

bool isClean = false;

  

if (latestPrevBlockHash_ != sjob->;prevHash_) {

  

isClean = true;

  

latestPrevBlockHash_ = sjob->;prevHash_;

  

LOG(INFO) <;<; "received new height statum job, height! " <;<; sjob->;height_

  

<;<; ", prevhash! " <;<; sjob->;prevHash_。ToString();

  

}

  

shared_ptr<;StratumJobEx>; exJob = std!!make_shared<;StratumJobEx>;(sjob, isClean);

  

{

  

ScopeLock sl(lock_);

  

if (isClean) {

  

// mark all jobs as stale, should do this before insert new job

  

for (auto it ! exJobs_) {

  

it。second->;markStale();

  

}

  

}

  

// insert new job

  

exJobs_[sjob->;jobId_] = exJob;

  

}

  

if (isClean) {

  

sendMiningNotify(exJob);

  

return;

  

}

  

```

  

2、如果过去一个job为新高度且为空块job,并且最新job非空块job,将尽快下发新job

  

```

  

if (isClean == false &;&; exJobs_。size() >;= 2) {

  

auto itr = exJobs_。rbegin();

  

shared_ptr<;StratumJobEx>; exJob1 = itr->;second;

  

itr++;

  

shared_ptr<;StratumJobEx>; exJob2 = itr->;second;

  

if (exJob2->;isClean_ == true &;&;

  

  

exJob2->;sjob_->;merkleBranch_。size() == 0 &;&;

  

exJob1->;sjob_->;merkleBranch_。size() != 0) {

  

sendMiningNotify(exJob);

  

}

  

}

  

```

  

3、每超过一定时间间隔(30秒),将下发新job

  

```

  

void JobRepository!!checkAndSendMiningNotify() {

  

// last job is expried, send a new one

  

if (exJobs_。size() &;&;

  

lastJobSendTime_ + kMiningNotifyInterval_ <;= time(nullptr))

  

{

  

shared_ptr<;StratumJobEx>; exJob = exJobs_。rbegin()->;second;

  

sendMiningNotify(exJob);

  

}

  

}

  

JobRepository!!JobRepository(const char *kafkaBrokers,

  

const string &;fileLastNotifyTime,

  

Server *server)!

  

running_(true),

  

kafkaConsumer_(kafkaBrokers, KAFKA_TOPIC_STRATUM_JOB, 0/*patition*/),

  

server_(server), fileLastNotifyTime_(fileLastNotifyTime),

  

kMaxJobsLifeTime_(300),

  

kMiningNotifyInterval_(30), // TODO! make as config arg

  

lastJobSendTime_(0)

  

{

  

assert(kMiningNotifyInterval_ <; kMaxJobsLifeTime_);

  

}

  

```

  
","content_hash"!"aab1dc91

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

评论