package ethash /* #cgo CFLAGS: -std=gnu99 -Wall #include "libethash/ethash.h" #include "libethash/util.c" #include "libethash/internal.c" #include "libethash/sha3.c" */ import "C" import ( "bytes" "encoding/binary" "log" "math/big" "math/rand" "sync" "time" "unsafe" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" ) var powlogger = logger.NewLogger("POW") type DAG struct { SeedBlockNum uint64 dag unsafe.Pointer // full GB of memory for dag } type ParamsAndCache struct { params *C.ethash_params cache *C.ethash_cache SeedBlockNum uint64 } type Ethash struct { turbo bool HashRate int64 chainManager pow.ChainManager dag *DAG paramsAndCache *ParamsAndCache nextdag unsafe.Pointer ret *C.ethash_return_value dagMutex *sync.Mutex cacheMutex *sync.Mutex } func parseNonce(nonce []byte) (uint64, error) { nonceBuf := bytes.NewBuffer(nonce) nonceInt, err := binary.ReadUvarint(nonceBuf) if err != nil { return 0, err } return nonceInt, nil } const epochLength uint64 = 30000 func getSeedBlockNum(blockNum uint64) uint64 { var seedBlockNum uint64 = 0 if blockNum >= 2*epochLength { seedBlockNum = ((blockNum / epochLength) - 1) * epochLength } return seedBlockNum } func makeParamsAndCache(chainManager pow.ChainManager, blockNum uint64) *ParamsAndCache { seedBlockNum := getSeedBlockNum(blockNum) paramsAndCache := &ParamsAndCache{ params: new(C.ethash_params), cache: new(C.ethash_cache), SeedBlockNum: seedBlockNum, } C.ethash_params_init(paramsAndCache.params, C.uint32_t(seedBlockNum)) paramsAndCache.cache.mem = C.malloc(paramsAndCache.params.cache_size) seedHash := chainManager.GetBlockByNumber(seedBlockNum).Header().Hash() log.Println("Params", paramsAndCache.params) log.Println("Making Cache") start := time.Now() C.ethash_mkcache(paramsAndCache.cache, paramsAndCache.params, (*C.uint8_t)((unsafe.Pointer)(&seedHash[0]))) log.Println("Took:", time.Since(start)) return paramsAndCache } func (pow *Ethash) updateCache() { pow.cacheMutex.Lock() seedNum := getSeedBlockNum(pow.chainManager.CurrentBlock().NumberU64()) if pow.paramsAndCache.SeedBlockNum != seedNum { pow.paramsAndCache = makeParamsAndCache(pow.chainManager, pow.chainManager.CurrentBlock().NumberU64()) } pow.cacheMutex.Unlock() } func makeDAG(p *ParamsAndCache) *DAG { d := &DAG{ dag: C.malloc(p.params.full_size), SeedBlockNum: p.SeedBlockNum, } C.ethash_compute_full_data(d.dag, p.params, p.cache) return d } func (pow *Ethash) updateDAG() { pow.cacheMutex.Lock() pow.dagMutex.Lock() seedNum := getSeedBlockNum(pow.chainManager.CurrentBlock().NumberU64()) if pow.dag == nil || pow.dag.SeedBlockNum != seedNum { pow.dag = nil log.Println("Making Dag") start := time.Now() pow.dag = makeDAG(pow.paramsAndCache) log.Println("Took:", time.Since(start)) } pow.dagMutex.Unlock() pow.cacheMutex.Unlock() } func New(chainManager pow.ChainManager) *Ethash { return &Ethash{ turbo: false, paramsAndCache: makeParamsAndCache(chainManager, chainManager.CurrentBlock().NumberU64()), chainManager: chainManager, dag: nil, ret: new(C.ethash_return_value), cacheMutex: new(sync.Mutex), dagMutex: new(sync.Mutex), } } func (pow *Ethash) DAGSize() uint64 { return uint64(pow.paramsAndCache.params.full_size) } func (pow *Ethash) CacheSize() uint64 { return uint64(pow.paramsAndCache.params.cache_size) } func (pow *Ethash) GetSeedHash(blockNum uint64) []byte { return pow.chainManager.GetBlockByNumber(getSeedBlockNum(blockNum)).Header().Hash() } func (pow *Ethash) Stop() { pow.cacheMutex.Lock() pow.dagMutex.Lock() if pow.paramsAndCache.cache != nil { C.free(pow.paramsAndCache.cache.mem) } if pow.dag != nil { C.free(pow.dag.dag) } pow.dagMutex.Unlock() pow.cacheMutex.Unlock() } func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) ([]byte, []byte, []byte) { pow.updateDAG() // Not very elegant, multiple mining instances are not supported pow.dagMutex.Lock() pow.cacheMutex.Lock() defer pow.cacheMutex.Unlock() defer pow.dagMutex.Unlock() r := rand.New(rand.NewSource(time.Now().UnixNano())) miningHash := block.HashNoNonce() diff := block.Difficulty() log.Println("difficulty", diff) i := int64(0) start := time.Now().UnixNano() t := time.Now() nonce := uint64(r.Int63()) for { select { case <-stop: powlogger.Infoln("Breaking from mining") pow.HashRate = 0 pow.dagMutex.Unlock() return nil, nil, nil default: i++ if time.Since(t) > (1 * time.Second) { elapsed := time.Now().UnixNano() - start hashes := ((float64(1e9) / float64(elapsed)) * float64(i)) / 1000 pow.HashRate = int64(hashes) powlogger.Infoln("Hashing @", pow.HashRate, "khash") t = time.Now() } cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash)) cnonce := C.uint64_t(nonce) log.Printf("seed hash, nonce: %x %x\n", miningHash, nonce) // pow.hash is the output/return of ethash_full C.ethash_full(pow.ret, pow.dag.dag, pow.paramsAndCache.params, cMiningHash, cnonce) res := C.ethash_check_difficulty((*C.uint8_t)(&pow.ret.result[0]), (*C.uint8_t)(unsafe.Pointer(&diff.Bytes()[0]))) if res == 1 { mixDigest := C.GoBytes(unsafe.Pointer(&pow.ret.mix_hash[0]), 32) // We don't really nead 32 bytes here buf := make([]byte, 32) binary.PutUvarint(buf, nonce) return buf, mixDigest, pow.GetSeedHash(block.NumberU64()) } nonce += 1 } if !pow.turbo { time.Sleep(20 * time.Microsecond) } } } func (pow *Ethash) Verify(block pow.Block) bool { // Make sure the SeedHash is set correctly if bytes.Compare(block.SeedHash(), pow.GetSeedHash(block.NumberU64())) != 0 { log.Println("Block had wrong SeedHash") log.Println("Expected: ", pow.GetSeedHash(block.NumberU64())) log.Println("Actual: ", block.SeedHash()) return false } nonceInt, err := parseNonce(block.Nonce()) if err != nil { log.Println("nonce to int err:", err) return false } return pow.verify(block.HashNoNonce(), block.MixDigest(), block.Difficulty(), block.NumberU64(), nonceInt) } func (pow *Ethash) verify(hash []byte, mixDigest []byte, difficulty *big.Int, blockNum uint64, nonce uint64) bool { // First check: make sure header, mixDigest, nonce are correct without hitting the DAG // This is to prevent DOS attacks chash := (*C.uint8_t)(unsafe.Pointer(&hash)) cnonce := C.uint64_t(nonce) cmixDigest := (*C.uint8_t)(unsafe.Pointer(&mixDigest)) cdifficulty := (*C.uint8_t)(unsafe.Pointer(&difficulty.Bytes()[0])) if C.ethash_quick_check_difficulty(chash, cnonce, cmixDigest, cdifficulty) != 1 { log.Println("Failed to pass quick check. Are you sure that the mix digest is correct?") return false } var pAc *ParamsAndCache // If its an old block (doesn't use the current cache) // get the cache for it but don't update (so we don't need the mutex) // Otherwise, it's the current block or a future. // If current, updateCache will do nothing. if getSeedBlockNum(blockNum) < pow.paramsAndCache.SeedBlockNum { pAc = makeParamsAndCache(pow.chainManager, blockNum) } else { pow.updateCache() pow.cacheMutex.Lock() defer pow.cacheMutex.Unlock() pAc = pow.paramsAndCache } C.ethash_light(pow.ret, pAc.cache, pAc.params, chash, cnonce) res := C.ethash_check_difficulty((*C.uint8_t)(unsafe.Pointer(&pow.ret.result[0])), cdifficulty) return res == 1 } func (pow *Ethash) GetHashrate() int64 { return pow.HashRate } func (pow *Ethash) Turbo(on bool) { pow.turbo = on } func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte { pow.updateDAG() pow.dagMutex.Lock() defer pow.dagMutex.Unlock() cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash)) cnonce := C.uint64_t(nonce) log.Println("seed hash, nonce:", miningHash, nonce) // pow.hash is the output/return of ethash_full C.ethash_full(pow.ret, pow.dag.dag, pow.paramsAndCache.params, cMiningHash, cnonce) ghash_full := C.GoBytes(unsafe.Pointer(&pow.ret.result[0]), 32) return ghash_full } func (pow *Ethash) LightHash(nonce uint64, miningHash []byte) []byte { cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash)) cnonce := C.uint64_t(nonce) C.ethash_light(pow.ret, pow.paramsAndCache.cache, pow.paramsAndCache.params, cMiningHash, cnonce) ghash_light := C.GoBytes(unsafe.Pointer(&pow.ret.result[0]), 32) return ghash_light }