Алгоритм консенсуса Ethash для анализа исходного кода Ethereum

алгоритм блокчейн Эфириум исходный код

Алгоритм консенсуса 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

Когда Эфириум разработал алгоритм консенсуса, он рассчитывал достичь трех целей:

  1. анти-ASICСексуальность: Преимущество создания специального оборудования для алгоритма должно быть как можно меньше, чтобы обычные пользователи компьютеров могли майнить с помощью процессоров.
    • Сопротивление по лимиту памяти (ASICДорого использовать память майнера)
    • Когда считывается большой объем случайных данных из памяти, скорость вычислений ограничивается не только вычислительным блоком, но и скоростью считывания памяти.
  2. Проверка легкого клиента: блок должен быть проверен легким клиентом быстро и эффективно.
  3. Майнеры должны быть обязаны хранить полное состояние блокчейна.

набор хеш-данных

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Процесс показан на следующем рисунке:

image-20201213144908721

DaggerЕще один ключевой момент – уверенность. то есть то же самоеepoch, каждый раз вычисляемыйseedкеш,datasetвсе одинаковые. В противном случае для одного и того же блока майнер и валидатор используют разныеdataset, это невозможно проверить.


Алгоритм Хашимото

даThaddeus DryjaТворческий. направленный на прохождениеIOОграничения бойкота майнеров. В процессе майнинга устанавливается предел чтения памяти.Поскольку само запоминающее устройство будет дешевле и более распространено, чем вычислительное устройство, крупные компании по всему миру также вложили значительные средства в оптимизацию обновления памяти, чтобы сделать память адаптируемой для различных пользователей. сцена, поэтому есть понятие оперативной памятиRAM, следовательно, существующая память может быть относительно близка к оптимальному алгоритму оценки.HashimotoАлгоритм использует блокчейн в качестве исходных данных и удовлетворяет требованиям 1 и 3 выше.

Его функция состоит в том, чтобы использовать поля хэша и одноразового номера заголовка блока, а также использовать данные набора данных для создания окончательного значения хеш-функции.


Анализ исходного кода

Создать набор хеш-данных

generateфункционировать вethash.goфайл, в основном для созданияdataset, который включает в себя следующее содержимое.

Создать размер кеша

cache sizeглавныйРазмер кеша проверки ethash для определенного номера блока*,epochLength30000, если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Источник данныхcachedata, а окончательное значение набора данных будет храниться в переменной 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

image-20201214150532321

①: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)⑥
  1. Подсчитайте количество строк в наборе данных
  2. сливатьсяheader+nonceдо 40 байтseed
  3. заголовок блокаhashскопировать вseedсередина
  4. будетnonceВведите значениеseedПосле (40-32=8) байт (сам одноразовый номерuint64тип, который составляет 64 бита, что соответствует размеру 8 байт), просто поместитеhashиnonceПолностью заполнен 40 байтами seed
  5. Keccak512шифрованиеseed
  6. от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переменные сравниваются, чтобы определить, является ли это допустимым значением хеш-функции.


проверить силу

Проверка информации о майнинге состоит из двух частей:

  1. проверятьHeader.Difficultyэто правильно или нет
  2. проверять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Хэш-значение и проверяющие узлы не нуждаются в данных набора данных.


Резюме и справка

mindcarver.cn☆☆☆

GitHub.com/проверка блоков…☆☆☆

ETH.wiki/concepts/лоб…

ETH.wiki/concepts/лоб…

Woohoo.vi Джей ПРА deep.com/blog/2017-0…