public bool Validate(BlockHeader header) { uint epoch = GetEpoch(header.Number); IEthashDataSet dataSet = _hintBasedCache.Get(epoch); if (dataSet == null) { if (_logger.IsWarn) { _logger.Warn($"Ethash cache miss for block {header.ToString(BlockHeader.Format.Short)}"); } _hintBasedCache.Hint(_hintBasedCacheUser, header.Number, header.Number); dataSet = _hintBasedCache.Get(epoch); if (dataSet == null) { if (_logger.IsError) { _logger.Error($"Hint based cache could not get data set for {header.ToString(BlockHeader.Format.Short)}"); } return(false); } } ulong fullSize = GetDataSize(epoch); Keccak headerHashed = GetTruncatedHash(header); (byte[] _, byte[] result, bool isValid) = Hashimoto(fullSize, dataSet, headerHashed, header.MixHash, header.Nonce); if (!isValid) { return(false); } return(IsLessThanTarget(result, header.Difficulty)); }
public FullDataSet(ulong setSize, IEthashDataSet cache) { Data = new uint[(uint)(setSize / Ethash.HashBytes)][]; for (uint i = 0; i < Data.Length; i++) { Data[i] = cache.CalcDataSetItem(i); } }
public bool Validate(BlockHeader header) { uint epoch = GetEpoch(header.Number); IEthashDataSet cache = GetOrAddCache(epoch); ulong fullSize = GetDataSize(epoch); Keccak headerHashed = GetTruncatedHash(header); (byte[] _, byte[] result) = Hashimoto(fullSize, cache, headerHashed, header.MixHash, header.Nonce); BigInteger threshold = BigInteger.Divide(BigInteger.Pow(2, 256), header.Difficulty); return(IsLessThanTarget(result, threshold)); }
private IEthashDataSet GetOrAddCache(uint epoch, bool precompute = true) { IEthashDataSet dataSet = _cacheCache.Get(epoch); if (dataSet == null) { uint cacheSize = GetCacheSize(epoch); Keccak seed = GetSeedHash(epoch); if (_logger.IsDebug) { _logger.Debug($"Building cache for epoch {epoch}"); } _cacheStopwatch.Restart(); dataSet = new EthashCache(cacheSize, seed.Bytes); _cacheStopwatch.Stop(); if (_logger.IsDebug) { _logger.Debug($"Cache for epoch {epoch} built in {_cacheStopwatch.ElapsedMilliseconds}ms"); } _cacheCache.Set(epoch, dataSet); } uint epochToPrecompute = epoch + 1; if (precompute && _epochsRequested.TryAdd(epochToPrecompute, null)) { if (_logger.IsDebug) { _logger.Debug($"Asking to precompute epoch {epochToPrecompute}"); } PrecomputeCache(epochToPrecompute).ContinueWith(t => { if (t.IsFaulted) { if (_logger.IsError) { _logger.Error($"Precompute failure at epoch {epochToPrecompute}"); } } else { if (_logger.IsDebug) { _logger.Debug($"Epoch precompute success at {epochToPrecompute}"); } } }); } return(dataSet); }
public FullDataSet(ulong setSize, IEthashDataSet cache) { //Console.WriteLine($"building data set of length {setSize}"); // TODO: temp, remove Data = new uint[(uint)(setSize / Ethash.HashBytes)][]; for (uint i = 0; i < Data.Length; i++) { if (i % 100000 == 0) { //Console.WriteLine($"building data set of length {setSize}, built {i}"); // TODO: temp, remove } Data[i] = cache.CalcDataSetItem(i); } }
public (byte[], byte[]) Hashimoto(ulong fullSize, IEthashDataSet dataSet, Keccak headerHash, Keccak expectedMixHash, ulong nonce) { uint hashesInFull = (uint)(fullSize / HashBytes); // TODO: at current rate would cover around 200 years... but will the block rate change? what with private chains with shorter block times? const uint wordsInMix = MixBytes / WordBytes; const uint hashesInMix = MixBytes / HashBytes; byte[] nonceBytes = new byte[8]; BinaryPrimitives.WriteUInt64LittleEndian(nonceBytes, nonce); byte[] headerAndNonceHashed = Keccak512.Compute(Bytes.Concat(headerHash.Bytes, nonceBytes)).Bytes; // this tests fine uint[] mixInts = new uint[MixBytes / WordBytes]; for (int i = 0; i < hashesInMix; i++) { Buffer.BlockCopy(headerAndNonceHashed, 0, mixInts, i * headerAndNonceHashed.Length, headerAndNonceHashed.Length); } uint firstOfHeaderAndNonce = GetUInt(headerAndNonceHashed, 0); for (uint i = 0; i < Accesses; i++) { uint p = Fnv(i ^ firstOfHeaderAndNonce, mixInts[i % wordsInMix]) % (hashesInFull / hashesInMix) * hashesInMix; // since we take 'hashesInMix' consecutive blocks we want only starting indices of such blocks uint[] newData = new uint[wordsInMix]; for (uint j = 0; j < hashesInMix; j++) { uint[] item = dataSet.CalcDataSetItem(p + j); Buffer.BlockCopy(item, 0, newData, (int)(j * item.Length * 4), item.Length * 4); } Fnv(mixInts, newData); } uint[] cmixInts = new uint[MixBytes / WordBytes / 4]; for (uint i = 0; i < mixInts.Length; i += 4) { cmixInts[i / 4] = Fnv(Fnv(Fnv(mixInts[i], mixInts[i + 1]), mixInts[i + 2]), mixInts[i + 3]); } byte[] cmix = new byte[MixBytes / WordBytes]; Buffer.BlockCopy(cmixInts, 0, cmix, 0, cmix.Length); if (expectedMixHash != null && !Bytes.AreEqual(cmix, expectedMixHash.Bytes)) { // TODO: handle properly throw new InvalidOperationException(); // TODO: need to change this } return(cmix, Keccak.Compute(Bytes.Concat(headerAndNonceHashed, cmix)).Bytes); // this tests fine }
private IEthashDataSet GetOrAddCache(uint epoch) { DataSetWithAccessTime theOne; lock (_cacheCache) { for (uint i = Math.Max(epoch, 2) - 2; i < epoch + 3; i++) { if (_cacheCache.Get(i) == default) { DataSetWithAccessTime someone = new DataSetWithAccessTime(i, BuildCache(i), Timestamper.Default.EpochSeconds); _cacheCache.Set(i, someone); _cacheMonitor.Add(someone); } } var now = Timestamper.Default.EpochSeconds; theOne = _cacheCache.Get(epoch); theOne.AccessTime = now; HashSet <DataSetWithAccessTime> removed = null; foreach (DataSetWithAccessTime dataSetWithAccessTime in _cacheMonitor) { if (now - dataSetWithAccessTime.AccessTime > 15) { _cacheCache.Delete(dataSetWithAccessTime.Epoch); if (removed == null) { removed = new HashSet <DataSetWithAccessTime>(); } removed.Add(dataSetWithAccessTime); } } if (removed != null) { foreach (DataSetWithAccessTime dataSetWithAccessTime in removed) { _cacheMonitor.Remove(dataSetWithAccessTime); dataSetWithAccessTime.DataSet.Result.Dispose(); } } } IEthashDataSet dataSet = theOne.DataSet.Result; return(dataSet); }
public (Keccak MixHash, ulong Nonce) Mine(BlockHeader header, ulong?startNonce = null) { uint epoch = GetEpoch(header.Number); IEthashDataSet dataSet = _hintBasedCache.Get(epoch); if (dataSet == null) { if (_logger.IsWarn) { _logger.Warn($"Ethash cache miss for block {header.ToString(BlockHeader.Format.Short)}"); } dataSet = BuildCache(epoch); } ulong fullSize = GetDataSize(epoch); ulong nonce = startNonce ?? GetRandomNonce(); BigInteger target = BigInteger.Divide(_2To256, header.Difficulty); Keccak headerHashed = GetTruncatedHash(header); // parallel for (just with ulong...) - adjust based on the available mining threads, low priority byte[] mixHash; while (true) { byte[] result; (mixHash, result, _) = Hashimoto(fullSize, dataSet, headerHashed, null, nonce); if (IsLessThanTarget(result, target)) { break; } unchecked { nonce += 1; } } return(new Keccak(mixHash), nonce); }
// TODO: in a separate thread private IEthashDataSet GetOrAddCache(uint epoch) { IEthashDataSet dataSet = _cacheCache.Get(epoch); if (dataSet == null) { uint cacheSize = GetCacheSize(epoch); Keccak seed = GetSeedHash(epoch); if (_logger.IsInfo) { _logger.Info($"Building cache for epoch {epoch}"); } _cacheStopwatch.Restart(); dataSet = new EthashCache(cacheSize, seed.Bytes); _cacheStopwatch.Stop(); if (_logger.IsInfo) { _logger.Info($"Cache for epoch {epoch} built in {_cacheStopwatch.ElapsedMilliseconds}ms"); } _cacheCache.Set(epoch, dataSet); } return(dataSet); }