Алгоритм консенсуса Ethash для анализа исходного кода Ethereum
Ветка кода:GitHub.com/ether EU M/go…
введение
В настоящее время в Ethereum существует две реализации алгоритмов консенсуса:clique
иethash
. иethash
это текущая основная сеть Ethereum (Homestead
версия) изPOW
Алгоритм консенсуса.
Структура каталогов
ethash
Модули находятся в каталоге проекта Ethereum.consensus/ethash
Под содержанием.
-
algorithm.goДостигнуто
Dagger-Hashimoto
Все функции алгоритма, такие как генерацияcache
иdataset
,в соответствии сHeader
иNonce
Вычислить хэши майнинга и т.д. -
api.goпонял
RPC
в использованииapi
метод. -
consensus.goРеализованы некоторые методы интерфейса консенсуса Ethereum, в том числе
Verify
метод серии (VerifyHeader
,VerifySeal
Ждать),Prepare
иFinalize
,CalcDifficulty
,Author
,SealHash
. -
ethash.goДостигнуто
cache
структура иdataset
структуры и соответствующие им методы,MakeCache
/MakeDataset
функция,Ethash
объектNew
функция, иEthash
внутренний метод. -
sealer.goреализует интерфейс консенсуса
Seal
Методы иEthash
внутренний методmine
. Эти методы реализуютethash
функция майнинга.
Принципы дизайна Ethash
Цели дизайна Ethash
Когда Эфириум разработал алгоритм консенсуса, он рассчитывал достичь трех целей:
- анти-
ASIC
Сексуальность: Преимущество создания специального оборудования для алгоритма должно быть как можно меньше, чтобы обычные пользователи компьютеров могли майнить с помощью процессоров.- Сопротивление по лимиту памяти (
ASIC
Дорого использовать память майнера) - Когда считывается большой объем случайных данных из памяти, скорость вычислений ограничивается не только вычислительным блоком, но и скоростью считывания памяти.
- Сопротивление по лимиту памяти (
- Проверка легкого клиента: блок должен быть проверен легким клиентом быстро и эффективно.
- Майнеры должны быть обязаны хранить полное состояние блокчейна.
набор хеш-данных
ethash
Чтобы вычислить хэш, вам нужно сначала иметь набор данных. Этот набор данных большой, с начальным размером примерно1G
, будет обновляться каждые 30 000 блоков, и каждое обновление будет больше, чем раньше.8M
о. Источник данных для вычисления хэша исходит из этого набора данных; это данные, которые решают, какие данные в наборе данных используются для вычисления хэша.header
данные иNonce
поле. Эта часть сделанаDagger
реализуется по алгоритму.
Dagger
Dagger
Алгоритмы используются для создания наборов данныхDataset
Да, основная частьDataset
Генерация и организационная структура.
можно поставитьDataset
думать о несколькихitem
(dataItem) массива, каждыйitem
да64
Байтовый массив байтов (хэш).dataset
Начальный размер примерно1G
, каждые 30 000 блоков (одинepoch
интервал) будет обновляться один раз, и каждое обновление будет больше, чем раньше8M
о.
Dataset
каждогоitem
состоит из блока кэша (cache
), блок кеша также можно рассматривать как множественныйitem
(cacheItem), объем памяти, занимаемый блоком кеша, меньшеdataset
намного меньше, его первоначальный размер составляет около16M
. такой жеdataset
Точно так же он будет обновляться каждые 30 000 блоков, и каждое обновление будет больше, чем раньше.128K
о.
генерироватьdataItem
Процесс таков: выбрать «случайное» из блока кеша («случайное» здесь не является реальным случайным числом, а означает, что его нельзя определить заранее, но каждый раз получается одно и то же значение)cacheItem
Проведите расчет, и полученный результат участвует в следующем расчете, этот процесс будет повторяться 256 раз.
Кэш-блоки создаютсяseed
генерируется, в то время какseed
Значение связано с высотой блока. так сгенерироватьdataset
Процесс показан на следующем рисунке:
Dagger
Еще один ключевой момент – уверенность. то есть то же самоеepoch
, каждый раз вычисляемыйseed
кеш,dataset
все одинаковые. В противном случае для одного и того же блока майнер и валидатор используют разныеdataset
, это невозможно проверить.
Алгоритм Хашимото
даThaddeus Dryja
Творческий. направленный на прохождениеIO
Ограничения бойкота майнеров. В процессе майнинга устанавливается предел чтения памяти.Поскольку само запоминающее устройство будет дешевле и более распространено, чем вычислительное устройство, крупные компании по всему миру также вложили значительные средства в оптимизацию обновления памяти, чтобы сделать память адаптируемой для различных пользователей. сцена, поэтому есть понятие оперативной памятиRAM
, следовательно, существующая память может быть относительно близка к оптимальному алгоритму оценки.Hashimoto
Алгоритм использует блокчейн в качестве исходных данных и удовлетворяет требованиям 1 и 3 выше.
Его функция состоит в том, чтобы использовать поля хэша и одноразового номера заголовка блока, а также использовать данные набора данных для создания окончательного значения хеш-функции.
Анализ исходного кода
Создать набор хеш-данных
generate
функционировать вethash.go
файл, в основном для созданияdataset
, который включает в себя следующее содержимое.
Создать размер кеша
cache size
главныйРазмер кеша проверки ethash для определенного номера блока*,epochLength
30000, еслиepoch
меньше 2048, из известныхepoch
вернуть соответствующийcache size
, иначе пересчитатьepoch
cache
Размер , растет линейно,size
Значение равно (2 ^ 24 ^ + 2 ^ 17 ^ * эпоха - 64), разделите это значение на 64, чтобы увидеть, является ли результат простым числом, если нет, вычтите 128 и пересчитайте, пока не найдете наибольшее простое число. номер.
csize := cacheSize(d.epoch*epochLength + 1)
func cacheSize(block uint64) uint64 {
epoch := int(block / epochLength)
if epoch < maxEpoch {
return cacheSizes[epoch]
}
return calcCacheSize(epoch)
}
func calcCacheSize(epoch int) uint64 {
size := cacheInitBytes + cacheGrowthBytes*uint64(epoch) - hashBytes
for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
size -= 2 * hashBytes
}
return size
}
Создать размер набора данных
dataset Size
главныйРазмер кеша проверки ethash для определенного номера блока, аналогично созданному вышеcache size
dsize := datasetSize(d.epoch*epochLength + 1)
func datasetSize(block uint64) uint64 {
epoch := int(block / epochLength)
if epoch < maxEpoch {
return datasetSizes[epoch]
}
return calcDatasetSize(epoch)
}
генерировать семя
*seedHash — это начальное число, используемое для создания кэша проверки и набора данных для майнинга. *Длина 32.
seed := seedHash(d.epoch*epochLength + 1)
func seedHash(block uint64) []byte {
seed := make([]byte, 32)
if block < epochLength {
return seed
}
keccak256 := makeHasher(sha3.NewLegacyKeccak256())
for i := 0; i < int(block/epochLength); i++ {
keccak256(seed, seed)
}
return seed
}
генерировать кеш
generateCache(cache, d.epoch, seed)
Следующий анализgenerateCache
Код ключа:
Узнайте первымhashBytes, в следующих расчетах используется эта единица, и ее значение равно 64, что эквивалентноkeccak512
Длина хэша, как показано нижеitemвызов[hashBytes]byte
.
①: Инициализацияcache
Этот цикл используется для инициализацииcache
: первыйseed
Хэш заполнитьcache
первоеitem
, затем используйте предыдущийitem
хэш заполнения послеitem
.
for offset := uint64(hashBytes); offset < size; offset += hashBytes {
keccak512(cache[offset:], cache[offset-hashBytes:offset])
atomic.AddUint32(&progress, 1)
}
②: XOR данных в кеше согласно правилам
для каждогоitem
(srcOff
), выберите один "наугад"item
(xorOff
) XOR с ним, записываем хэш результата вdstOff
середина. Эта операционная логика будетcacheRounds
Второсортный.
Две вещи, которые следует отметить:
- Один
srcOff
меняется с хвоста на голову иdstOff
Он меняется от головы к хвосту. и они соответствующие, т. е. когдаsrcOff
При представлении предпоследнего x-го элементаdstOff
Он представляет положительный x-й элемент. - два это
xorOff
выбор. Обратите внимание, что мы просто взяли «случайный» в кавычки.xorOff
Значение кажется случайным, поскольку даноseed
Раньше вы не могли знать, каково значение xorOff, но однаждыseed
Значение определено, тогда каждый разxorOff
значения детерминированы. Значение seed определяется высотой блока. это тоже самоеepoch
всегда получай одно и то жеcache
причина данных.
for i := 0; i < cacheRounds; i++ {
for j := 0; j < rows; j++ {
var (
srcOff = ((j - 1 + rows) % rows) * hashBytes
dstOff = j * hashBytes
xorOff = (binary.LittleEndian.Uint32(cache[dstOff:]) % uint32(rows)) * hashBytes
)
bitutil.XORBytes(temp, cache[srcOff:srcOff+hashBytes], cache[xorOff:xorOff+hashBytes])
keccak512(cache[dstOff:], temp)
atomic.AddUint32(&progress, 1)
}
}
создать набор данных
dataset
расчет размеров иcache
Точно так же величина отличается: 2 ^ 30 ^ + 2 ^ 23 ^ * эпоха - 128, затем каждый раз вычитать 256, чтобы найти наибольшее простое число.
Генерация данных представляет собой цикл, каждый раз генерирующий 64 байта, основная функцияgenerateDatasetItem
:
generateDatasetItem
Источник данныхcache
data, а окончательное значение набора данных будет храниться в переменной mix. Весь процесс также состоит из нескольких циклов.
①: Инициализацияmix
Переменная
По паре значений кешаmix
переменные инициализируются. вhashWords
представляетhash
сколько их тамword
Стоимость: одинhash
длинаhashBytes
то есть 64 байта,word
(тип uint32) имеет длину 4 байта, поэтомуhashWords
Значение равно 16. Выбратьcache
Какие из данных определяются параметромindex
иi
определяется переменными.
mix := make([]byte, hashBytes)
binary.LittleEndian.PutUint32(mix, cache[(index%rows)*hashWords]^index)
for i := 1; i < hashWords; i++ {
binary.LittleEndian.PutUint32(mix[i*4:], cache[(index%rows)*hashWords+uint32(i)])
}
keccak512(mix, mix)
②: будетmix
преобразовать в[]uint32
тип
intMix := make([]uint32, hashWords)
for i := 0; i < len(intMix); i++ {
intMix[i] = binary.LittleEndian.Uint32(mix[i*4:])
}
③: будетcache
данные объединяются вintmix
for i := uint32(0); i < datasetParents; i++ {
parent := fnv(index^i, intMix[i%16]) % rows
fnvHash(intMix, cache[parent*hashWords:])
}
FNV
Алгоритм хеширования — это алгоритм хеширования, который не требует использования ключа.
Алгоритм прост: умножьте a на простое число FNV 0x01000193, затем XOR с b.
Сначала используйте этот алгоритм для расчета значения индекса, используйте этот индекс изcache
выбрать значение (data
), затем кmix
Вычисляется один раз для каждого байта вFNV
, чтобы получить окончательное значение хеш-функции.
func fnv(a, b uint32) uint32 {
return a*0x01000193 ^ b
}
func fnvHash(mix []uint32, data []uint32) {
for i := 0; i < len(mix); i++ {
mix[i] = mix[i]*0x01000193 ^ data[i]
}
}
④: будетintMix
восстановлен вmix
и рассчитатьmix
хэш вернулся
for i, val := range intMix {
binary.LittleEndian.PutUint32(mix[i*4:], val)
}
keccak512(mix, mix)
return mix
generateCache
иgenerateDataset
реализуетсяDagger
Основная функция алгоритма и весь процесс создания набора хэш-данных завершены.
Основные функции механизма консенсуса
Код находится наconsensus.go
①:Author
// 返回coinbase, coinbase是打包第一笔交易的矿工的地址
func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
②:VerifyHeader
Существует два основных этапа проверки: первая проверкаЗаголовок известенилиэто неизвестный предок, второй шагethash
чек:
2.1 заголовок. Дополнительный не может превышать 32 байта
if uint64(len(header.Extra)) > params.MaximumExtraDataSize { // 不超过32字节
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
2.2 Отметка времени не может превышать 15 секунд, а блоки после 15 секунд считаются будущими блоками.
if !uncle {
if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
return consensus.ErrFutureBlock
}
}
2.3 Временная метка текущего заголовка меньше, чем у родительского блока
if header.Time <= parent.Time { // 当前header的时间小于等于父块的
return errZeroBlockTime
}
2.4 Проверка сложности блока на основе метки времени и сложности родительского блока
expected := ethash.CalcDifficulty(chain, header.Time, parent)
if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
}
2.5 Проверкаgas limit
менее 2^63^-1
cap := uint64(0x7fffffffffffffff)
if header.GasLimit > cap {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
}
2.6 ПодтверждениеgasUsed
для gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
2.7 Убедитесь, что номер блока равен родительскому блоку плюс 1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber
}
2.8 Проверить, соответствует ли данный блок требованиям сложности pow
if seal {
if err := ethash.VerifySeal(chain, header); err != nil {
return err
}
}
③:VerifyUncles
3.1 До двух дядюшек
if len(block.Uncles()) > maxUncles {
return errTooManyUncles
}
3.2 Собирайте блоки дяди и блоки предков
number, parent := block.NumberU64()-1, block.ParentHash()
for i := 0; i < 7; i++ {
ancestor := chain.GetBlock(parent, number)
if ancestor == nil {
break
}
ancestors[ancestor.Hash()] = ancestor.Header()
for _, uncle := range ancestor.Uncles() {
uncles.Add(uncle.Hash())
}
parent, number = ancestor.ParentHash(), number-1
}
ancestors[block.Hash()] = block.Header()
uncles.Add(block.Hash())
3.3 Убедитесь, что дядя вознаграждается только один раз и что у дяди есть действительный предок
for _, uncle := range block.Uncles() {
// Make sure every uncle is rewarded only once
hash := uncle.Hash()
if uncles.Contains(hash) {
return errDuplicateUncle
}
uncles.Add(hash)
// Make sure the uncle has a valid ancestry
if ancestors[hash] != nil {
return errUncleIsAncestor
}
if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
return errDanglingUncle
}
if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
return err
}
④:Prepare
инициализация
header
изDifficulty
поле
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent)
return nil
⑤:Finalize
Все модификации состояния после транзакции (например, награды за блок) выполняются, ноне собираетсяблок.
5.1 Накапливайте награды за любой блок и дяди-блоки
accumulateRewards(chain.Config(), state, header, uncles)
5.2 Рассчитайте корневой хэш дерева состояний и отправьте его вheader
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
⑥:FinalizeAndAssemble
Запустите любые модификации состояния после транзакции (например, награды за блок) и соберите окончательный блок.
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
return types.NewBlock(header, txs, uncles, receipts), nil
}
Очевидно, чемFinalize
переборtypes.NewBlock
⑦:SealHash
обратно вseal
хэш предыдущего блока (за которым последуетseal
Хэш блока после этого другой)
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
hasher.Sum(hash[:0])
return hash
}
⑧:Seal
Генерирует новый запечатанный запрос для данного входного блока (добыча полезных ископаемых) и поместите результат в указанный канал.
Обратите внимание, что метод вернется немедленно и отправит результат асинхронно. В зависимости от алгоритма консенсуса также может быть возвращено несколько результатов. Эта часть будет подробно проанализирована в следующем майнинге, пропустите ее здесь.
Детали майнинга
Если у вас возникнут вопросы при чтении этой статьи, вы можете оставить мне сообщение, и я отвечу вовремя. Если вы считаете, что это хорошо написано, вы можете обратить внимание на нижнюю частьСсылаться наиз
github项目
, можно впервые обратить внимание на динамику статьи автора.
Определение основного интерфейса майнинга:
Seal(chain ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
Входитьseal
функция:
①: Если операция невернаPOW
, сразу вернуть пустымnonce
иMixDigest
, и блок также является пустым блоком.
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
header := block.Header()
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
select {
case results <- block.WithSeal(header):
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header()))
}
return nil
}
②: Поделитьсяpow
Если это так, перейдите к его общему объекту для выполненияSeal
действовать
if ethash.shared != nil {
return ethash.shared.Seal(chain, block, results, stop)
}
③: Получите исходный код и сгенерируйте его на его основе.ethash
нужны семена
f ethash.rand == nil {
// 获得种子
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
ethash.lock.Unlock()
return err
}
ethash.rand = rand.New(rand.NewSource(seed.Int64())) // 给rand赋值
}
④: Основная работа по добыче полезных ископаемых переданаmine
for i := 0; i < threads; i++ {
pend.Add(1)
go func(id int, nonce uint64) {
defer pend.Done()
ethash.mine(block, id, nonce, abort, locals) // 真正执行挖矿的动作
}(i, uint64(ethash.rand.Int63()))
}
⑤: Обработать результат майнинга
- Внешнее неожиданное прерывание, остановить все потоки майнинга
- Один из потоков копает правильный блок, прерывая все остальные потоки
- Объект ethash изменяется, останавливает все текущие операции и перезапускает текущий метод.
go func() {
var result *types.Block
select {
case <-stop:
close(abort)
case result = <-locals:
select {
case results <- result: //其中一个线程挖到正确块,中止其他所有线程
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ethash.SealHash(block.Header()))
}
close(abort)
case <-ethash.update:
close(abort)
if err := ethash.Seal(chain, block, results, stop); err != nil {
ethash.config.Log.Error("Failed to restart sealing after update", "err", err)
}
}
Из вышеизложенного можно узнатьseal
Основная работаmine
Функция завершена, давайте сосредоточимся на ней.
mine
Функция на самом деле относительно проста, онадействительноpow
шахтер, используемый для поискаnonce
ценность,nonce
Значение начинается сseed
ценность,seed
Значение - это сложность, которая в конечном итоге приведет к созданию правильного сопоставляемого и проверяемого блока.
①: Извлеките соответствующие данные из заголовка блока и поместите их в поле глобальной переменной.
var (
header = block.Header()
hash = ethash.SealHash(header).Bytes()
target = new(big.Int).Div(two256, header.Difficulty) // 这是用来验证的target
number = header.Number.Uint64()
dataset = ethash.dataset(number, false)
)
②: начать генерировать случайныеnonce
, пока мы не прервем или не найдем хорошийnonce
var (
attempts = int64(0)
nonce = seed
)
③: Совокупность завершенаdataset
данные для генерации окончательного хэша для определенного заголовка и одноразового номера
func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
//定义一个lookup函数,用于在数据集中查找数据
lookup := func(index uint32) []uint32 {
offset := index * hashWords //hashWords是上面定义的常量值= 16
return dataset[offset : offset+hashWords]
}
return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
}
можно найти на самом делеhashimotoFull
Работа функции состоит в том, чтобы прочитать и разделить исходный набор данных, а затем передать его вhashimoto
функция. Следующий анализ фокусаhashimoto
функция:
3.1 Получить заголовок блока в соответствии с начальным числом
rows := uint32(size / mixBytes) ①
seed := make([]byte, 40) ②
copy(seed, hash) ③
binary.LittleEndian.PutUint64(seed[32:], nonce)④
seed = crypto.Keccak512(seed)⑤
seedHead := binary.LittleEndian.Uint32(seed)⑥
- Подсчитайте количество строк в наборе данных
- сливаться
header+nonce
до 40 байтseed
- заголовок блока
hash
скопировать вseed
середина - будет
nonce
Введите значениеseed
После (40-32=8) байт (сам одноразовый номерuint64
тип, который составляет 64 бита, что соответствует размеру 8 байт), просто поместитеhash
иnonce
Полностью заполнен 40 байтами seed -
Keccak512
шифрованиеseed
- от
seed
Получить заголовок блока из
3.2 Начать смешивание с повторных семян
-
mixBytes
постоянная = 128,mix
имеет длину 32, а элементыuint32
, составляет 32 бита, что соответствует размеру 4 байта. такmix
Общий размер 4*32=128 байт.
mix := make([]uint32, mixBytes/4)
for i := 0; i < len(mix); i++ {
mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:])
}
3.3 Узел набора гибридных случайных данных
temp := make([]uint32, len(mix))//与mix结构相同,长度相同
for i := 0; i < loopAccesses; i++ {
parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows
for j := uint32(0); j < mixBytes/hashBytes; j++ {
copy(temp[j*hashWords:], lookup(2*parent+j))
}
fnvHash(mix, temp)
}
3.4 Компрессионное смешивание
for i := 0; i < len(mix); i += 4 {
mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
}
mix = mix[:len(mix)/4]
digest := make([]byte, common.HashLength)
for i, val := range mix {
binary.LittleEndian.PutUint32(digest[i*4:], val)
}
return digest, crypto.Keccak256(append(seed, digest...))
То, что наконец возвращено,digest
иdigest
иseed
хэш от ; иdigest
На самом деле этоmix
из[]byte
форма. спередиEthash.mine
Код, который мы видели, использует второе возвращаемое значение сtarget
переменные сравниваются, чтобы определить, является ли это допустимым значением хеш-функции.
проверить силу
Проверка информации о майнинге состоит из двух частей:
- проверять
Header.Difficulty
это правильно или нет - проверять
Header.MixDigest
иHeader.Nonce
это правильно или нет
①: ПроверкаHeader.Difficulty
Код в основном вEthash.verifyHeader
середина:
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
......
expected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
}
}
Рассчитывается с учетом высоты блока и разницы во времени в качестве параметровDifficulty
значение, а затем с блоком для проверкиHeader.Difficulty
Поля сравниваются, и если они равны, считается правильным.
②:MixDigest
иNonce
Проверка проводится в основномHeader.verifySeal
середина:
Метод аутентификации: использоватьHeader.Nonce
и хэш заголовка черезhashimoto
пересчитыватьMixDigest
иresult
Хэш-значение и проверяющие узлы не нуждаются в данных набора данных.