Commit 01b2c901 authored by obscuren's avatar obscuren

Updated ethash

parent c756633f
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
}, },
{ {
"ImportPath": "github.com/ethereum/ethash", "ImportPath": "github.com/ethereum/ethash",
"Comment": "v23.1-73-g67a0e12", "Comment": "v23.1-81-g4039fd0",
"Rev": "67a0e12a091de035ef083186247e84be2d863c62" "Rev": "4039fd095084679fb0bf3feae91d02506b5d67aa"
}, },
{ {
"ImportPath": "github.com/ethereum/serpent-go", "ImportPath": "github.com/ethereum/serpent-go",
......
...@@ -34,13 +34,12 @@ import ( ...@@ -34,13 +34,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
) )
var minDifficulty = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) var minDifficulty = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
var powlogger = logger.NewLogger("POW")
type ParamsAndCache struct { type ParamsAndCache struct {
params *C.ethash_params params *C.ethash_params
cache *C.ethash_cache cache *C.ethash_cache
...@@ -92,10 +91,13 @@ func makeParamsAndCache(chainManager pow.ChainManager, blockNum uint64) (*Params ...@@ -92,10 +91,13 @@ func makeParamsAndCache(chainManager pow.ChainManager, blockNum uint64) (*Params
return nil, err return nil, err
} }
powlogger.Infoln("Making Cache") glog.V(logger.Info).Infoln("Making cache")
start := time.Now() start := time.Now()
C.ethash_mkcache(paramsAndCache.cache, paramsAndCache.params, (*C.uint8_t)(unsafe.Pointer(&seedHash[0]))) C.ethash_mkcache(paramsAndCache.cache, paramsAndCache.params, (*C.ethash_blockhash_t)(unsafe.Pointer(&seedHash[0])))
powlogger.Infoln("Took:", time.Since(start))
if glog.V(logger.Info) {
glog.Infoln("Took:", time.Since(start))
}
return paramsAndCache, nil return paramsAndCache, nil
} }
...@@ -131,9 +133,9 @@ func makeDAG(p *ParamsAndCache) *DAG { ...@@ -131,9 +133,9 @@ func makeDAG(p *ParamsAndCache) *DAG {
for { for {
select { select {
case <-t.C: case <-t.C:
powlogger.Infof("... still generating DAG (%v) ...\n", time.Since(tstart).Seconds()) glog.V(logger.Info).Infof("... still generating DAG (%v) ...\n", time.Since(tstart).Seconds())
case str := <-donech: case str := <-donech:
powlogger.Infof("... %s ...\n", str) glog.V(logger.Info).Infof("... %s ...\n", str)
break done break done
} }
} }
...@@ -193,30 +195,30 @@ func (pow *Ethash) UpdateDAG() { ...@@ -193,30 +195,30 @@ func (pow *Ethash) UpdateDAG() {
pow.paramsAndCache = paramsAndCache pow.paramsAndCache = paramsAndCache
path := path.Join("/", "tmp", "dag") path := path.Join("/", "tmp", "dag")
pow.dag = nil pow.dag = nil
powlogger.Infoln("Retrieving DAG") glog.V(logger.Info).Infoln("Retrieving DAG")
start := time.Now() start := time.Now()
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
powlogger.Infof("No DAG found. Generating new DAG in '%s' (this takes a while)...\n", path) glog.V(logger.Info).Infof("No DAG found. Generating new DAG in '%s' (this takes a while)...\n", path)
pow.dag = makeDAG(paramsAndCache) pow.dag = makeDAG(paramsAndCache)
file = pow.writeDagToDisk(pow.dag, thisEpoch) file = pow.writeDagToDisk(pow.dag, thisEpoch)
pow.dag.file = true pow.dag.file = true
} else { } else {
data, err := ioutil.ReadAll(file) data, err := ioutil.ReadAll(file)
if err != nil { if err != nil {
powlogger.Infof("DAG load err: %v\n", err) glog.V(logger.Info).Infof("DAG load err: %v\n", err)
} }
if len(data) < 8 { if len(data) < 8 {
powlogger.Infof("DAG in '%s' is less than 8 bytes, it must be corrupted. Generating new DAG (this takes a while)...\n", path) glog.V(logger.Info).Infof("DAG in '%s' is less than 8 bytes, it must be corrupted. Generating new DAG (this takes a while)...\n", path)
pow.dag = makeDAG(paramsAndCache) pow.dag = makeDAG(paramsAndCache)
file = pow.writeDagToDisk(pow.dag, thisEpoch) file = pow.writeDagToDisk(pow.dag, thisEpoch)
pow.dag.file = true pow.dag.file = true
} else { } else {
dataEpoch := binary.BigEndian.Uint64(data[0:8]) dataEpoch := binary.BigEndian.Uint64(data[0:8])
if dataEpoch < thisEpoch { if dataEpoch < thisEpoch {
powlogger.Infof("DAG in '%s' is stale. Generating new DAG (this takes a while)...\n", path) glog.V(logger.Info).Infof("DAG in '%s' is stale. Generating new DAG (this takes a while)...\n", path)
pow.dag = makeDAG(paramsAndCache) pow.dag = makeDAG(paramsAndCache)
file = pow.writeDagToDisk(pow.dag, thisEpoch) file = pow.writeDagToDisk(pow.dag, thisEpoch)
pow.dag.file = true pow.dag.file = true
...@@ -224,7 +226,7 @@ func (pow *Ethash) UpdateDAG() { ...@@ -224,7 +226,7 @@ func (pow *Ethash) UpdateDAG() {
// FIXME // FIXME
panic(fmt.Errorf("Saved DAG in '%s' reports to be from future epoch %v (current epoch is %v)\n", path, dataEpoch, thisEpoch)) panic(fmt.Errorf("Saved DAG in '%s' reports to be from future epoch %v (current epoch is %v)\n", path, dataEpoch, thisEpoch))
} else if len(data) != (int(paramsAndCache.params.full_size) + 8) { } else if len(data) != (int(paramsAndCache.params.full_size) + 8) {
powlogger.Infof("DAG in '%s' is corrupted. Generating new DAG (this takes a while)...\n", path) glog.V(logger.Info).Infof("DAG in '%s' is corrupted. Generating new DAG (this takes a while)...\n", path)
pow.dag = makeDAG(paramsAndCache) pow.dag = makeDAG(paramsAndCache)
file = pow.writeDagToDisk(pow.dag, thisEpoch) file = pow.writeDagToDisk(pow.dag, thisEpoch)
pow.dag.file = true pow.dag.file = true
...@@ -238,7 +240,7 @@ func (pow *Ethash) UpdateDAG() { ...@@ -238,7 +240,7 @@ func (pow *Ethash) UpdateDAG() {
} }
} }
} }
powlogger.Infoln("Took:", time.Since(start)) glog.V(logger.Info).Infoln("Took:", time.Since(start))
file.Close() file.Close()
} }
...@@ -319,7 +321,7 @@ func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte ...@@ -319,7 +321,7 @@ func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte
start := time.Now().UnixNano() start := time.Now().UnixNano()
nonce := uint64(r.Int63()) nonce := uint64(r.Int63())
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) cMiningHash := (*C.ethash_blockhash_t)(unsafe.Pointer(&miningHash[0]))
target := new(big.Int).Div(minDifficulty, diff) target := new(big.Int).Div(minDifficulty, diff)
var ret C.ethash_return_value var ret C.ethash_return_value
...@@ -336,11 +338,11 @@ func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte ...@@ -336,11 +338,11 @@ func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte
pow.HashRate = int64(hashes) pow.HashRate = int64(hashes)
C.ethash_full(&ret, pow.dag.dag, pow.dag.paramsAndCache.params, cMiningHash, C.uint64_t(nonce)) C.ethash_full(&ret, pow.dag.dag, pow.dag.paramsAndCache.params, cMiningHash, C.uint64_t(nonce))
result := common.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result[0]), C.int(32))) result := common.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result), C.int(32)))
// TODO: disagrees with the spec https://github.com/ethereum/wiki/wiki/Ethash#mining // TODO: disagrees with the spec https://github.com/ethereum/wiki/wiki/Ethash#mining
if result.Cmp(target) <= 0 { if result.Cmp(target) <= 0 {
mixDigest := C.GoBytes(unsafe.Pointer(&ret.mix_hash[0]), C.int(32)) mixDigest := C.GoBytes(unsafe.Pointer(&ret.mix_hash), C.int(32))
seedHash, err := GetSeedHash(block.NumberU64()) // This seedhash is useless seedHash, err := GetSeedHash(block.NumberU64()) // This seedhash is useless
if err != nil { if err != nil {
panic(err) panic(err)
...@@ -366,14 +368,14 @@ func (pow *Ethash) Verify(block pow.Block) bool { ...@@ -366,14 +368,14 @@ func (pow *Ethash) Verify(block pow.Block) bool {
func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *big.Int, blockNum uint64, nonce uint64) bool { func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *big.Int, blockNum uint64, nonce uint64) bool {
// Make sure the block num is valid // Make sure the block num is valid
if blockNum >= epochLength*2048 { if blockNum >= epochLength*2048 {
powlogger.Infoln(fmt.Sprintf("Block number exceeds limit, invalid (value is %v, limit is %v)", glog.V(logger.Info).Infoln(fmt.Sprintf("Block number exceeds limit, invalid (value is %v, limit is %v)",
blockNum, epochLength*2048)) blockNum, epochLength*2048))
return false return false
} }
// First check: make sure header, mixDigest, nonce are correct without hitting the cache // First check: make sure header, mixDigest, nonce are correct without hitting the cache
// This is to prevent DOS attacks // This is to prevent DOS attacks
chash := (*C.uint8_t)(unsafe.Pointer(&hash[0])) chash := (*C.ethash_blockhash_t)(unsafe.Pointer(&hash[0]))
cnonce := C.uint64_t(nonce) cnonce := C.uint64_t(nonce)
target := new(big.Int).Div(minDifficulty, difficulty) target := new(big.Int).Div(minDifficulty, difficulty)
...@@ -387,7 +389,7 @@ func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *b ...@@ -387,7 +389,7 @@ func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *b
// If we can't make the params for some reason, this block is invalid // If we can't make the params for some reason, this block is invalid
pAc, err = makeParamsAndCache(pow.chainManager, blockNum+1) pAc, err = makeParamsAndCache(pow.chainManager, blockNum+1)
if err != nil { if err != nil {
powlogger.Infoln("big fucking eror", err) glog.V(logger.Info).Infoln("big fucking eror", err)
return false return false
} }
} else { } else {
...@@ -401,7 +403,7 @@ func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *b ...@@ -401,7 +403,7 @@ func (pow *Ethash) verify(hash common.Hash, mixDigest common.Hash, difficulty *b
C.ethash_light(ret, pAc.cache, pAc.params, chash, cnonce) C.ethash_light(ret, pAc.cache, pAc.params, chash, cnonce)
result := common.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result[0]), C.int(32))) result := common.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result), C.int(32)))
return result.Cmp(target) <= 0 return result.Cmp(target) <= 0
} }
...@@ -417,7 +419,7 @@ func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte { ...@@ -417,7 +419,7 @@ func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte {
pow.UpdateDAG() pow.UpdateDAG()
pow.dagMutex.Lock() pow.dagMutex.Lock()
defer pow.dagMutex.Unlock() defer pow.dagMutex.Unlock()
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) cMiningHash := (*C.ethash_blockhash_t)(unsafe.Pointer(&miningHash[0]))
cnonce := C.uint64_t(nonce) cnonce := C.uint64_t(nonce)
ret := new(C.ethash_return_value) ret := new(C.ethash_return_value)
// pow.hash is the output/return of ethash_full // pow.hash is the output/return of ethash_full
...@@ -427,7 +429,7 @@ func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte { ...@@ -427,7 +429,7 @@ func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte {
} }
func (pow *Ethash) LightHash(nonce uint64, miningHash []byte) []byte { func (pow *Ethash) LightHash(nonce uint64, miningHash []byte) []byte {
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) cMiningHash := (*C.ethash_blockhash_t)(unsafe.Pointer(&miningHash[0]))
cnonce := C.uint64_t(nonce) cnonce := C.uint64_t(nonce)
ret := new(C.ethash_return_value) ret := new(C.ethash_return_value)
C.ethash_light(ret, pow.paramsAndCache.cache, pow.paramsAndCache.params, cMiningHash, cnonce) C.ethash_light(ret, pow.paramsAndCache.cache, pow.paramsAndCache.params, cMiningHash, cnonce)
......
#!/usr/bin/env python #!/usr/bin/env python
from distutils.core import setup, Extension from distutils.core import setup, Extension
pyethash = Extension('pyethash', pyethash = Extension('pyethash',
sources = [ sources=[
'src/python/core.c', 'src/python/core.c',
'src/libethash/util.c', 'src/libethash/util.c',
'src/libethash/internal.c', 'src/libethash/internal.c',
'src/libethash/sha3.c'], 'src/libethash/sha3.c'],
depends = [ depends=[
'src/libethash/ethash.h', 'src/libethash/ethash.h',
'src/libethash/compiler.h', 'src/libethash/compiler.h',
'src/libethash/data_sizes.h', 'src/libethash/data_sizes.h',
'src/libethash/endian.h', 'src/libethash/endian.h',
'src/libethash/ethash.h', 'src/libethash/ethash.h',
'src/libethash/fnv.h', 'src/libethash/fnv.h',
'src/libethash/internal.h', 'src/libethash/internal.h',
'src/libethash/sha3.h', 'src/libethash/sha3.h',
'src/libethash/util.h' 'src/libethash/util.h'
], ],
extra_compile_args = ["-Isrc/", "-std=gnu99", "-Wall"]) extra_compile_args=["-Isrc/", "-std=gnu99", "-Wall"])
setup ( setup(
name = 'pyethash', name='pyethash',
author = "Matthew Wampler-Doty", author="Matthew Wampler-Doty",
author_email = "matthew.wampler.doty@gmail.com", author_email="matthew.wampler.doty@gmail.com",
license = 'GPL', license='GPL',
version = '23', version='0.1.23',
url = 'https://github.com/ethereum/ethash', url='https://github.com/ethereum/ethash',
download_url = 'https://github.com/ethereum/ethash/tarball/v23', download_url='https://github.com/ethereum/ethash/tarball/v23',
description = 'Python wrappers for ethash, the ethereum proof of work hashing function', description=('Python wrappers for ethash, the ethereum proof of work'
ext_modules = [pyethash], 'hashing function'),
) ext_modules=[pyethash],
)
...@@ -106,10 +106,11 @@ extern "C" int main(void) ...@@ -106,10 +106,11 @@ extern "C" int main(void)
//params.full_size = 8209 * 4096; // 8MBish; //params.full_size = 8209 * 4096; // 8MBish;
//params.cache_size = 8209*4096; //params.cache_size = 8209*4096;
//params.cache_size = 2053*4096; //params.cache_size = 2053*4096;
uint8_t seed[32], previous_hash[32]; ethash_blockhash_t seed;
ethash_blockhash_t previous_hash;
memcpy(seed, hexStringToBytes("9410b944535a83d9adf6bbdcc80e051f30676173c16ca0d32d6f1263fc246466").data(), 32); memcpy(&seed, hexStringToBytes("9410b944535a83d9adf6bbdcc80e051f30676173c16ca0d32d6f1263fc246466").data(), 32);
memcpy(previous_hash, hexStringToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").data(), 32); memcpy(&previous_hash, hexStringToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").data(), 32);
// allocate page aligned buffer for dataset // allocate page aligned buffer for dataset
#ifdef FULL #ifdef FULL
...@@ -128,9 +129,9 @@ extern "C" int main(void) ...@@ -128,9 +129,9 @@ extern "C" int main(void)
ethash_mkcache(&cache, &params, seed); ethash_mkcache(&cache, &params, seed);
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(high_resolution_clock::now() - startTime).count(); auto time = std::chrono::duration_cast<std::chrono::milliseconds>(high_resolution_clock::now() - startTime).count();
uint8_t cache_hash[32]; ethash_blockhash_t cache_hash;
SHA3_256(cache_hash, (uint8_t const*)cache_mem, params.cache_size); SHA3_256(&cache_hash, (uint8_t const*)cache_mem, params.cache_size);
debugf("ethash_mkcache: %ums, sha3: %s\n", (unsigned)time, bytesToHexString(cache_hash,sizeof(cache_hash)).data()); debugf("ethash_mkcache: %ums, sha3: %s\n", (unsigned)((time*1000)/CLOCKS_PER_SEC), bytesToHexString(cache_hash,sizeof(cache_hash)).data());
// print a couple of test hashes // print a couple of test hashes
{ {
......
...@@ -54,15 +54,7 @@ ethash_cl_miner::ethash_cl_miner() ...@@ -54,15 +54,7 @@ ethash_cl_miner::ethash_cl_miner()
{ {
} }
void ethash_cl_miner::finish() bool ethash_cl_miner::init(ethash_params const& params, ethash_blockhash_t const *seed, unsigned workgroup_size)
{
if (m_queue())
{
m_queue.finish();
}
}
bool ethash_cl_miner::init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size)
{ {
// store params // store params
m_params = params; m_params = params;
......
...@@ -19,7 +19,7 @@ public: ...@@ -19,7 +19,7 @@ public:
public: public:
ethash_cl_miner(); ethash_cl_miner();
bool init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size = 64); bool init(ethash_params const& params, ethash_blockhash_t const *seed, unsigned workgroup_size = 64);
void finish(); void finish();
void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count);
...@@ -42,4 +42,4 @@ private: ...@@ -42,4 +42,4 @@ private:
cl::Buffer m_search_buf[c_num_buffers]; cl::Buffer m_search_buf[c_num_buffers];
unsigned m_workgroup_size; unsigned m_workgroup_size;
bool m_opencl_1_1; bool m_opencl_1_1;
}; };
\ No newline at end of file
...@@ -47,9 +47,26 @@ typedef struct ethash_params { ...@@ -47,9 +47,26 @@ typedef struct ethash_params {
uint64_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)). uint64_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)).
} ethash_params; } ethash_params;
/// Type of a blockhash
typedef struct ethash_blockhash { uint8_t b[32]; } ethash_blockhash_t;
static inline uint8_t ethash_blockhash_get(ethash_blockhash_t const* hash, unsigned int i)
{
return hash->b[i];
}
static inline void ethash_blockhash_set(ethash_blockhash_t *hash, unsigned int i, uint8_t v)
{
hash->b[i] = v;
}
static inline void ethash_blockhash_reset(ethash_blockhash_t *hash)
{
memset(hash, 0, 32);
}
typedef struct ethash_return_value { typedef struct ethash_return_value {
uint8_t result[32]; ethash_blockhash_t result;
uint8_t mix_hash[32]; ethash_blockhash_t mix_hash;
} ethash_return_value; } ethash_return_value;
uint64_t ethash_get_datasize(const uint32_t block_number); uint64_t ethash_get_datasize(const uint32_t block_number);
...@@ -65,58 +82,68 @@ typedef struct ethash_cache { ...@@ -65,58 +82,68 @@ typedef struct ethash_cache {
void *mem; void *mem;
} ethash_cache; } ethash_cache;
void ethash_mkcache(ethash_cache *cache, ethash_params const *params, const uint8_t seed[32]); void ethash_mkcache(ethash_cache *cache, ethash_params const *params, ethash_blockhash_t const *seed);
void ethash_compute_full_data(void *mem, ethash_params const *params, ethash_cache const *cache); void ethash_compute_full_data(void *mem, ethash_params const *params, ethash_cache const *cache);
void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); void ethash_full(ethash_return_value *ret,
void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); void const *full_mem,
void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number); ethash_params const *params,
ethash_blockhash_t const *header_hash,
static inline void ethash_prep_light(void *cache, ethash_params const *params, const uint8_t seed[32]) { const uint64_t nonce);
ethash_cache c; void ethash_light(ethash_return_value *ret,
c.mem = cache; ethash_cache const *cache,
ethash_mkcache(&c, params, seed); ethash_params const *params,
ethash_blockhash_t const *header_hash,
const uint64_t nonce);
void ethash_get_seedhash(ethash_blockhash_t *seedhash, const uint32_t block_number);
static inline void ethash_prep_light(void *cache, ethash_params const *params, ethash_blockhash_t const* seed)
{
ethash_cache c;
c.mem = cache;
ethash_mkcache(&c, params, seed);
} }
static inline void ethash_compute_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { static inline void ethash_compute_light(ethash_return_value *ret, void const *cache, ethash_params const *params, ethash_blockhash_t const *header_hash, const uint64_t nonce)
ethash_cache c; {
c.mem = (void *) cache; ethash_cache c;
ethash_light(ret, &c, params, header_hash, nonce); c.mem = (void *) cache;
ethash_light(ret, &c, params, header_hash, nonce);
} }
static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache) { static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache)
ethash_cache c; {
c.mem = (void *) cache; ethash_cache c;
ethash_compute_full_data(full, params, &c); c.mem = (void *) cache;
ethash_compute_full_data(full, params, &c);
} }
static inline void ethash_compute_full(ethash_return_value *ret, void const *full, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { static inline void ethash_compute_full(ethash_return_value *ret,
ethash_full(ret, full, params, header_hash, nonce); void const *full,
ethash_params const *params,
ethash_blockhash_t const *header_hash,
const uint64_t nonce)
{
ethash_full(ret, full, params, header_hash, nonce);
} }
/// @brief Compare two s256-bit big-endian values. // Returns if hash is less than or equal to difficulty
/// @returns 1 if @a a is less than or equal to @a b, 0 otherwise. static inline int ethash_check_difficulty(ethash_blockhash_t const *hash,
/// Both parameters are 256-bit big-endian values. ethash_blockhash_t const *difficulty)
static inline int ethash_leq_be256(const uint8_t a[32], const uint8_t b[32]) { {
// Boundary is big endian // Difficulty is big endian
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
if (a[i] == b[i]) if (ethash_blockhash_get(hash, i) == ethash_blockhash_get(difficulty, i)) {
continue; continue;
return a[i] < b[i]; }
} return ethash_blockhash_get(hash, i) < ethash_blockhash_get(difficulty, i);
return 1; }
return 1;
} }
/// Perofrms a cursory check on the validity of the nonce. int ethash_quick_check_difficulty(ethash_blockhash_t const *header_hash,
/// @returns 1 if the nonce may possibly be valid for the given header_hash & boundary. const uint64_t nonce,
/// @p boundary equivalent to 2 ^ 256 / block_difficulty, represented as a 256-bit big-endian. ethash_blockhash_t const *mix_hash,
int ethash_preliminary_check_boundary( ethash_blockhash_t const *difficulty);
const uint8_t header_hash[32],
const uint64_t nonce,
const uint8_t mix_hash[32],
const uint8_t boundary[32]);
#define ethash_quick_check_difficulty ethash_preliminary_check_boundary
#define ethash_check_difficulty ethash_leq_be256
#ifdef __cplusplus #ifdef __cplusplus
} }
......
...@@ -50,14 +50,14 @@ uint64_t ethash_get_cachesize(const uint32_t block_number) { ...@@ -50,14 +50,14 @@ uint64_t ethash_get_cachesize(const uint32_t block_number) {
// Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014) // Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014)
// https://bitslog.files.wordpress.com/2013/12/memohash-v0-3.pdf // https://bitslog.files.wordpress.com/2013/12/memohash-v0-3.pdf
// SeqMemoHash(s, R, N) // SeqMemoHash(s, R, N)
void static ethash_compute_cache_nodes( void static ethash_compute_cache_nodes(node *const nodes,
node *const nodes, ethash_params const *params,
ethash_params const *params, ethash_blockhash_t const* seed)
const uint8_t seed[32]) { {
assert((params->cache_size % sizeof(node)) == 0); assert((params->cache_size % sizeof(node)) == 0);
uint32_t const num_nodes = (uint32_t) (params->cache_size / sizeof(node)); uint32_t const num_nodes = (uint32_t) (params->cache_size / sizeof(node));
SHA3_512(nodes[0].bytes, seed, 32); SHA3_512(nodes[0].bytes, (uint8_t*)seed, 32);
for (unsigned i = 1; i != num_nodes; ++i) { for (unsigned i = 1; i != num_nodes; ++i) {
SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64); SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64);
...@@ -84,20 +84,19 @@ void static ethash_compute_cache_nodes( ...@@ -84,20 +84,19 @@ void static ethash_compute_cache_nodes(
#endif #endif
} }
void ethash_mkcache( void ethash_mkcache(ethash_cache *cache,
ethash_cache *cache, ethash_params const *params,
ethash_params const *params, ethash_blockhash_t const* seed)
const uint8_t seed[32]) { {
node *nodes = (node *) cache->mem; node *nodes = (node *) cache->mem;
ethash_compute_cache_nodes(nodes, params, seed); ethash_compute_cache_nodes(nodes, params, seed);
} }
void ethash_calculate_dag_item( void ethash_calculate_dag_item(node *const ret,
node *const ret, const unsigned node_index,
const unsigned node_index, const struct ethash_params *params,
const struct ethash_params *params, const struct ethash_cache *cache)
const struct ethash_cache *cache) { {
uint32_t num_parent_nodes = (uint32_t) (params->cache_size / sizeof(node)); uint32_t num_parent_nodes = (uint32_t) (params->cache_size / sizeof(node));
node const *cache_nodes = (node const *) cache->mem; node const *cache_nodes = (node const *) cache->mem;
node const *init = &cache_nodes[node_index % num_parent_nodes]; node const *init = &cache_nodes[node_index % num_parent_nodes];
...@@ -161,13 +160,13 @@ void ethash_compute_full_data( ...@@ -161,13 +160,13 @@ void ethash_compute_full_data(
} }
} }
static void ethash_hash( static void ethash_hash(ethash_return_value *ret,
ethash_return_value *ret, node const *full_nodes,
node const *full_nodes, ethash_cache const *cache,
ethash_cache const *cache, ethash_params const *params,
ethash_params const *params, ethash_blockhash_t const *header_hash,
const uint8_t header_hash[32], const uint64_t nonce)
const uint64_t nonce) { {
assert((params->full_size % MIX_WORDS) == 0); assert((params->full_size % MIX_WORDS) == 0);
...@@ -251,16 +250,16 @@ static void ethash_hash( ...@@ -251,16 +250,16 @@ static void ethash_hash(
} }
#endif #endif
memcpy(ret->mix_hash, mix->bytes, 32); memcpy(&ret->mix_hash, mix->bytes, 32);
// final Keccak hash // final Keccak hash
SHA3_256(ret->result, s_mix->bytes, 64 + 32); // Keccak-256(s + compressed_mix) SHA3_256(&ret->result, s_mix->bytes, 64 + 32); // Keccak-256(s + compressed_mix)
} }
void ethash_quick_hash( void ethash_quick_hash(ethash_blockhash_t *return_hash,
uint8_t return_hash[32], ethash_blockhash_t const *header_hash,
const uint8_t header_hash[32], const uint64_t nonce,
const uint64_t nonce, ethash_blockhash_t const *mix_hash)
const uint8_t mix_hash[32]) { {
uint8_t buf[64 + 32]; uint8_t buf[64 + 32];
memcpy(buf, header_hash, 32); memcpy(buf, header_hash, 32);
...@@ -273,28 +272,39 @@ void ethash_quick_hash( ...@@ -273,28 +272,39 @@ void ethash_quick_hash(
SHA3_256(return_hash, buf, 64 + 32); SHA3_256(return_hash, buf, 64 + 32);
} }
void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number) { void ethash_get_seedhash(ethash_blockhash_t *seedhash, const uint32_t block_number)
memset(seedhash, 0, 32); {
ethash_blockhash_reset(seedhash);
const uint32_t epochs = block_number / EPOCH_LENGTH; const uint32_t epochs = block_number / EPOCH_LENGTH;
for (uint32_t i = 0; i < epochs; ++i) for (uint32_t i = 0; i < epochs; ++i)
SHA3_256(seedhash, seedhash, 32); SHA3_256(seedhash, (uint8_t*)seedhash, 32);
} }
int ethash_preliminary_check_boundary( int ethash_quick_check_difficulty(ethash_blockhash_t const *header_hash,
const uint8_t header_hash[32], const uint64_t nonce,
const uint64_t nonce, ethash_blockhash_t const *mix_hash,
const uint8_t mix_hash[32], ethash_blockhash_t const *difficulty)
const uint8_t difficulty[32]) { {
uint8_t return_hash[32]; ethash_blockhash_t return_hash;
ethash_quick_hash(return_hash, header_hash, nonce, mix_hash); ethash_quick_hash(&return_hash, header_hash, nonce, mix_hash);
return ethash_leq_be256(return_hash, difficulty); return ethash_check_difficulty(&return_hash, difficulty);
} }
void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { void ethash_full(ethash_return_value *ret,
ethash_hash(ret, (node const *) full_mem, NULL, params, previous_hash, nonce); void const *full_mem,
ethash_params const *params,
ethash_blockhash_t const *header_hash,
const uint64_t nonce)
{
ethash_hash(ret, (node const *) full_mem, NULL, params, header_hash, nonce);
} }
void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { void ethash_light(ethash_return_value *ret,
ethash_hash(ret, NULL, cache, params, previous_hash, nonce); ethash_cache const *cache,
ethash_params const *params,
ethash_blockhash_t const *header_hash,
const uint64_t nonce)
{
ethash_hash(ret, NULL, cache, params, header_hash, nonce);
} }
...@@ -30,19 +30,16 @@ typedef union node { ...@@ -30,19 +30,16 @@ typedef union node {
} node; } node;
void ethash_calculate_dag_item( void ethash_calculate_dag_item(node *const ret,
node *const ret, const unsigned node_index,
const unsigned node_index, ethash_params const *params,
ethash_params const *params, ethash_cache const *cache);
ethash_cache const *cache
); void ethash_quick_hash(ethash_blockhash_t *return_hash,
ethash_blockhash_t const *header_hash,
void ethash_quick_hash( const uint64_t nonce,
uint8_t return_hash[32], ethash_blockhash_t const *mix_hash);
const uint8_t header_hash[32],
const uint64_t nonce,
const uint8_t mix_hash[32]);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
\ No newline at end of file
...@@ -28,8 +28,6 @@ ...@@ -28,8 +28,6 @@
extern "C" { extern "C" {
#endif #endif
typedef struct ethash_blockhash { uint8_t b[32]; } ethash_blockhash_t;
static const char DAG_FILE_NAME[] = "full"; static const char DAG_FILE_NAME[] = "full";
static const char DAG_MEMO_NAME[] = "full.info"; static const char DAG_MEMO_NAME[] = "full.info";
// MSVC thinks that "static const unsigned int" is not a compile time variable. Sorry for the #define :( // MSVC thinks that "static const unsigned int" is not a compile time variable. Sorry for the #define :(
...@@ -54,6 +52,7 @@ enum ethash_io_rc { ...@@ -54,6 +52,7 @@ enum ethash_io_rc {
* @return For possible return values @see enum ethash_io_rc * @return For possible return values @see enum ethash_io_rc
*/ */
enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash); enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash);
/** /**
* Fully computes data and writes it to the file on disk. * Fully computes data and writes it to the file on disk.
* *
......
...@@ -8,20 +8,24 @@ extern "C" { ...@@ -8,20 +8,24 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
struct ethash_blockhash;
#define decsha3(bits) \ #define decsha3(bits) \
int sha3_##bits(uint8_t*, size_t, const uint8_t*, size_t); int sha3_##bits(uint8_t*, size_t, const uint8_t*, size_t);
decsha3(256) decsha3(256)
decsha3(512) decsha3(512)
static inline void SHA3_256(uint8_t * const ret, uint8_t const *data, const size_t size) { static inline void SHA3_256(struct ethash_blockhash const* ret, uint8_t const *data, const size_t size)
sha3_256(ret, 32, data, size); {
sha3_256((uint8_t*)ret, 32, data, size);
} }
static inline void SHA3_512(uint8_t * const ret, uint8_t const *data, const size_t size) { static inline void SHA3_512(uint8_t *ret, uint8_t const *data, const size_t size)
{
sha3_512(ret, 64, data, size); sha3_512(ret, 64, data, size);
} }
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
\ No newline at end of file
...@@ -19,16 +19,17 @@ ...@@ -19,16 +19,17 @@
* @author Tim Hughes <tim@twistedfury.com> * @author Tim Hughes <tim@twistedfury.com>
* @date 2015 * @date 2015
*/ */
#include <stdint.h> #include <stdint.h>
#include <cryptopp/sha3.h> #include <cryptopp/sha3.h>
extern "C" { extern "C" {
void SHA3_256(uint8_t *const ret, const uint8_t *data, size_t size) { struct ethash_blockhash;
CryptoPP::SHA3_256().CalculateDigest(ret, data, size); typedef struct ethash_blockhash ethash_blockhash_t;
void SHA3_256(ethash_blockhash_t const* ret, const uint8_t *data, size_t size) {
CryptoPP::SHA3_256().CalculateDigest((uint8_t*)ret, data, size);
} }
void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size) { void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size) {
CryptoPP::SHA3_512().CalculateDigest(ret, data, size); CryptoPP::SHA3_512().CalculateDigest(ret, data, size);
} }
} }
\ No newline at end of file
...@@ -2,14 +2,18 @@ ...@@ -2,14 +2,18 @@
#include "compiler.h" #include "compiler.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void SHA3_256(uint8_t *const ret, const uint8_t *data, size_t size); struct ethash_blockhash;
typedef struct ethash_blockhash ethash_blockhash_t;
void SHA3_256(ethash_blockhash_t *const ret, const uint8_t *data, size_t size);
void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size); void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
\ No newline at end of file
...@@ -69,7 +69,7 @@ mkcache_bytes(PyObject *self, PyObject *args) { ...@@ -69,7 +69,7 @@ mkcache_bytes(PyObject *self, PyObject *args) {
params.cache_size = (size_t) cache_size; params.cache_size = (size_t) cache_size;
ethash_cache cache; ethash_cache cache;
cache.mem = malloc(cache_size); cache.mem = malloc(cache_size);
ethash_mkcache(&cache, &params, (uint8_t *) seed); ethash_mkcache(&cache, &params, (ethash_blockhash_t *) seed);
PyObject * val = Py_BuildValue(PY_STRING_FORMAT, cache.mem, cache_size); PyObject * val = Py_BuildValue(PY_STRING_FORMAT, cache.mem, cache_size);
free(cache.mem); free(cache.mem);
return val; return val;
...@@ -114,28 +114,25 @@ calc_dataset_bytes(PyObject *self, PyObject *args) { ...@@ -114,28 +114,25 @@ calc_dataset_bytes(PyObject *self, PyObject *args) {
// hashimoto_light(full_size, cache, header, nonce) // hashimoto_light(full_size, cache, header, nonce)
static PyObject * static PyObject *
hashimoto_light(PyObject *self, PyObject *args) { hashimoto_light(PyObject *self, PyObject *args) {
char *cache_bytes, *header; char *cache_bytes;
char *header;
unsigned long full_size; unsigned long full_size;
unsigned long long nonce; unsigned long long nonce;
int cache_size, header_size; int cache_size, header_size;
if (!PyArg_ParseTuple(args, "k" PY_STRING_FORMAT PY_STRING_FORMAT "K", &full_size, &cache_bytes, &cache_size, &header, &header_size, &nonce)) if (!PyArg_ParseTuple(args, "k" PY_STRING_FORMAT PY_STRING_FORMAT "K", &full_size, &cache_bytes, &cache_size, &header, &header_size, &nonce))
return 0; return 0;
if (full_size % MIX_WORDS != 0) { if (full_size % MIX_WORDS != 0) {
char error_message[1024]; char error_message[1024];
sprintf(error_message, "The size of data set must be a multiple of %i bytes (was %lu)", MIX_WORDS, full_size); sprintf(error_message, "The size of data set must be a multiple of %i bytes (was %lu)", MIX_WORDS, full_size);
PyErr_SetString(PyExc_ValueError, error_message); PyErr_SetString(PyExc_ValueError, error_message);
return 0; return 0;
} }
if (cache_size % HASH_BYTES != 0) { if (cache_size % HASH_BYTES != 0) {
char error_message[1024]; char error_message[1024];
sprintf(error_message, "The size of the cache must be a multiple of %i bytes (was %i)", HASH_BYTES, cache_size); sprintf(error_message, "The size of the cache must be a multiple of %i bytes (was %i)", HASH_BYTES, cache_size);
PyErr_SetString(PyExc_ValueError, error_message); PyErr_SetString(PyExc_ValueError, error_message);
return 0; return 0;
} }
if (header_size != 32) { if (header_size != 32) {
char error_message[1024]; char error_message[1024];
sprintf(error_message, "Seed must be 32 bytes long (was %i)", header_size); sprintf(error_message, "Seed must be 32 bytes long (was %i)", header_size);
...@@ -143,23 +140,23 @@ hashimoto_light(PyObject *self, PyObject *args) { ...@@ -143,23 +140,23 @@ hashimoto_light(PyObject *self, PyObject *args) {
return 0; return 0;
} }
ethash_return_value out; ethash_return_value out;
ethash_params params; ethash_params params;
params.cache_size = (size_t) cache_size; params.cache_size = (size_t) cache_size;
params.full_size = (size_t) full_size; params.full_size = (size_t) full_size;
ethash_cache cache; ethash_cache cache;
cache.mem = (void *) cache_bytes; cache.mem = (void *) cache_bytes;
ethash_light(&out, &cache, &params, (uint8_t *) header, nonce); ethash_light(&out, &cache, &params, (ethash_blockhash_t *) header, nonce);
return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "," PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "}", return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "," PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "}",
"mix digest", out.mix_hash, 32, "mix digest", &out.mix_hash, 32,
"result", out.result, 32); "result", &out.result, 32);
} }
// hashimoto_full(dataset, header, nonce) // hashimoto_full(dataset, header, nonce)
static PyObject * static PyObject *
hashimoto_full(PyObject *self, PyObject *args) { hashimoto_full(PyObject *self, PyObject *args) {
char *full_bytes, *header; char *full_bytes;
char *header;
unsigned long long nonce; unsigned long long nonce;
int full_size, header_size; int full_size, header_size;
...@@ -184,16 +181,18 @@ hashimoto_full(PyObject *self, PyObject *args) { ...@@ -184,16 +181,18 @@ hashimoto_full(PyObject *self, PyObject *args) {
ethash_return_value out; ethash_return_value out;
ethash_params params; ethash_params params;
params.full_size = (size_t) full_size; params.full_size = (size_t) full_size;
ethash_full(&out, (void *) full_bytes, &params, (uint8_t *) header, nonce); ethash_full(&out, (void *) full_bytes, &params, (ethash_blockhash_t *) header, nonce);
return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "}", return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT "}",
"mix digest", out.mix_hash, 32, "mix digest", &out.mix_hash, 32,
"result", out.result, 32); "result", &out.result, 32);
} }
// mine(dataset_bytes, header, difficulty_bytes) // mine(dataset_bytes, header, difficulty_bytes)
static PyObject * static PyObject *
mine(PyObject *self, PyObject *args) { mine(PyObject *self, PyObject *args) {
char *full_bytes, *header, *difficulty; char *full_bytes;
char *header;
char *difficulty;
srand(time(0)); srand(time(0));
uint64_t nonce = ((uint64_t) rand()) << 32 | rand(); uint64_t nonce = ((uint64_t) rand()) << 32 | rand();
int full_size, header_size, difficulty_size; int full_size, header_size, difficulty_size;
...@@ -228,13 +227,13 @@ mine(PyObject *self, PyObject *args) { ...@@ -228,13 +227,13 @@ mine(PyObject *self, PyObject *args) {
// TODO: Multi threading? // TODO: Multi threading?
do { do {
ethash_full(&out, (void *) full_bytes, &params, (const uint8_t *) header, nonce++); ethash_full(&out, (void *) full_bytes, &params, (const ethash_blockhash_t *) header, nonce++);
// TODO: disagrees with the spec https://github.com/ethereum/wiki/wiki/Ethash#mining // TODO: disagrees with the spec https://github.com/ethereum/wiki/wiki/Ethash#mining
} while (!ethash_check_difficulty(out.result, (const uint8_t *) difficulty)); } while (!ethash_check_difficulty(&out.result, (const ethash_blockhash_t *) difficulty));
return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":K}", return Py_BuildValue("{" PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":" PY_STRING_FORMAT ", " PY_CONST_STRING_FORMAT ":K}",
"mix digest", out.mix_hash, 32, "mix digest", &out.mix_hash, 32,
"result", out.result, 32, "result", &out.result, 32,
"nonce", nonce); "nonce", nonce);
} }
...@@ -251,9 +250,9 @@ get_seedhash(PyObject *self, PyObject *args) { ...@@ -251,9 +250,9 @@ get_seedhash(PyObject *self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, error_message); PyErr_SetString(PyExc_ValueError, error_message);
return 0; return 0;
} }
uint8_t seedhash[32]; ethash_blockhash_t seedhash;
ethash_get_seedhash(seedhash, block_number); ethash_get_seedhash(&seedhash, block_number);
return Py_BuildValue(PY_STRING_FORMAT, (char *) seedhash, 32); return Py_BuildValue(PY_STRING_FORMAT, (char *) &seedhash, 32);
} }
static PyMethodDef PyethashMethods[] = static PyMethodDef PyethashMethods[] =
......
...@@ -3,6 +3,15 @@ ...@@ -3,6 +3,15 @@
# Strict mode # Strict mode
set -e set -e
if [ -x "$(which virtualenv2)" ] ; then
VIRTUALENV_EXEC=virtualenv2
elif [ -x "$(which virtualenv)" ] ; then
VIRTUALENV_EXEC=virtualenv
else
echo "Could not find a suitable version of virtualenv"
false
fi
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
...@@ -11,9 +20,11 @@ while [ -h "$SOURCE" ]; do ...@@ -11,9 +20,11 @@ while [ -h "$SOURCE" ]; do
done done
TEST_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" TEST_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
[ -d $TEST_DIR/python-virtual-env ] || virtualenv --system-site-packages $TEST_DIR/python-virtual-env [ -d $TEST_DIR/python-virtual-env ] || $VIRTUALENV_EXEC --system-site-packages $TEST_DIR/python-virtual-env
source $TEST_DIR/python-virtual-env/bin/activate source $TEST_DIR/python-virtual-env/bin/activate
pip install -r $TEST_DIR/requirements.txt > /dev/null pip install -r $TEST_DIR/requirements.txt > /dev/null
# force installation of nose in virtualenv even if existing in thereuser's system
pip install nose -I
pip install --upgrade --no-deps --force-reinstall -e $TEST_DIR/../.. pip install --upgrade --no-deps --force-reinstall -e $TEST_DIR/../..
cd $TEST_DIR cd $TEST_DIR
nosetests --with-doctest -v --nocapture nosetests --with-doctest -v --nocapture
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment