Do a POW (Node)
This commit is contained in:
parent
8c799f4e91
commit
225fba30e1
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
|||
/npm-debug.log
|
||||
/docs/
|
||||
/worker.browserify.js
|
||||
/build/
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
/docs/
|
||||
/jsdoc.json
|
||||
/worker.browserify.js
|
||||
/build/
|
||||
|
|
|
@ -49,7 +49,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
|||
- [ ] msg
|
||||
- [ ] broadcast
|
||||
- [x] WIF
|
||||
- [ ] POW
|
||||
- [x] POW
|
||||
- [ ] High-level classes
|
||||
- [ ] Address
|
||||
- [x] encode
|
||||
|
|
10
binding.gyp
Normal file
10
binding.gyp
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "worker",
|
||||
"include_dirs": ["<!(node -e \"require('nan')\")"],
|
||||
"cflags": ["-Wall", "-O2"],
|
||||
"sources": ["src/worker.cc", "src/pow.cc"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -26,8 +26,8 @@ exports.randomBytes = function(size) {
|
|||
return new Buffer(arr);
|
||||
};
|
||||
|
||||
// See `platform.js` for comments.
|
||||
var B64 = new BN("18446744073709551616");
|
||||
|
||||
exports.getTarget = function(opts) {
|
||||
var length = new BN(opts.payloadLength);
|
||||
length.iaddn(8);
|
||||
|
@ -50,15 +50,19 @@ var FAILBACK_POOL_SIZE = 8;
|
|||
// because of the WebCryptoAPI (see
|
||||
// <http://caniuse.com/#feat=cryptography>).
|
||||
exports.doPOW = function(opts) {
|
||||
// Try to get native cores count then fallback to more or less
|
||||
// reasonable value. See <https://stackoverflow.com/q/3289465> for
|
||||
// details.
|
||||
// Try to get CPU cores count otherwise fallback to default value.
|
||||
// Currenty navigator's concurrency property available in Chrome and
|
||||
// not available in Firefox; hope default value won't slow down POW
|
||||
// speed much on systems with 1 or 2 cores. There are core estimator
|
||||
// libraries exist (see <https://stackoverflow.com/q/3289465>) but
|
||||
// they are buggy. Ulimately library user could adjust pool size
|
||||
// manually.
|
||||
var poolSize = opts.poolSize || navigator.hardwareConcurrency;
|
||||
poolSize = poolSize || FAILBACK_POOL_SIZE;
|
||||
|
||||
// Check all input params prematurely to not let promise executor or
|
||||
// worker to fail because of it.
|
||||
assert(poolSize > 0, "Bad pool size");
|
||||
assert(poolSize > 0, "Pool size is too low");
|
||||
assert(opts.workerUrl, "Bad worker URL");
|
||||
assert(typeof opts.target === "number", "Bad target");
|
||||
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
|
||||
|
@ -79,8 +83,8 @@ exports.doPOW = function(opts) {
|
|||
// It's very unlikely that execution will ever reach this place.
|
||||
// Currently the only reason why Worker may return value less
|
||||
// than zero is a 32-bit nonce overflow (see worker
|
||||
// implementation). It's more than 4G double hashes.
|
||||
reject();
|
||||
// implementation). It's 4G double hashes.
|
||||
reject(new Error("uint32_t nonce overflow"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +119,7 @@ exports.doPOW = function(opts) {
|
|||
reject(e);
|
||||
};
|
||||
});
|
||||
// Allow to stop POW via custom function added to the Promise
|
||||
// Allow to stop a POW via custom function added to the Promise
|
||||
// instance.
|
||||
promise.cancel = cancel;
|
||||
return promise;
|
||||
|
|
|
@ -4,9 +4,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var os = require("os");
|
||||
var crypto = require("crypto");
|
||||
var promise = typeof Promise === "undefined" ?
|
||||
require("es6-promise").Promise :
|
||||
Promise;
|
||||
var bignum = require("bignum");
|
||||
var assert = require("./util").assert;
|
||||
var worker = require("./worker");
|
||||
|
||||
var createHash = crypto.createHash;
|
||||
|
||||
|
@ -45,3 +50,34 @@ exports.getTarget = function(opts) {
|
|||
assert(target <= 9007199254740991, "Unsafe target");
|
||||
return target;
|
||||
};
|
||||
|
||||
exports.doPOW = function(opts) {
|
||||
var poolSize = opts.poolSize || os.cpus().length;
|
||||
|
||||
// Check all input params prematurely to not let promise executor or
|
||||
// worker to fail because of it.
|
||||
// 1 - UINT32_MAX
|
||||
assert(poolSize > 0, "Pool size is too low");
|
||||
assert(poolSize <= 4294967295, "Pool size is too high");
|
||||
// 0 - (2^53 - 1)
|
||||
assert(typeof opts.target === "number", "Bad target");
|
||||
assert(opts.target >= 0, "Target is too low");
|
||||
assert(opts.target <= 9007199254740991, "Target is too high");
|
||||
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
|
||||
|
||||
// TODO(Kagami): Allow to cancel a POW (see `platform.browser.js`).
|
||||
return new promise(function(resolve, reject) {
|
||||
worker.powAsync(
|
||||
poolSize,
|
||||
opts.target,
|
||||
opts.initialHash,
|
||||
function(err, nonce) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(nonce);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
12
lib/worker.js
Normal file
12
lib/worker.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Worker routines for Node platform. This module tries to load native
|
||||
* addon and makes it available for Node.
|
||||
*/
|
||||
|
||||
try {
|
||||
module.exports = require("../build/Release/worker");
|
||||
} catch(e) {
|
||||
// Do nothing for a moment. Everything will work except the functions
|
||||
// that uses worker routines.
|
||||
// TODO(Kagami) Provide pure JS fallback.
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
"./lib/platform.js": "./lib/platform.browser.js"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "node-gyp rebuild || exit 0",
|
||||
"test": "ALL_TESTS=1 mocha && ALL_TESTS=1 npm run -s kc && ALL_TESTS=1 npm run -s kf && jshint .",
|
||||
"m": "mocha",
|
||||
"k": "npm run -s w && xvfb-run -a karma start",
|
||||
|
@ -53,7 +54,9 @@
|
|||
"bs58": "^2.0.0",
|
||||
"buffer-equal": "~0.0.1",
|
||||
"eccrypto": "^0.1.2",
|
||||
"es6-promise": "^2.0.1",
|
||||
"hash.js": "^1.0.2",
|
||||
"nan": "^1.4.1",
|
||||
"object-assign": "^2.0.0",
|
||||
"sha.js": "^2.3.0"
|
||||
}
|
||||
|
|
50
src/pow.cc
Normal file
50
src/pow.cc
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Based on <https://github.com/grant-olson/bitmessage-powfaster>
|
||||
// fastcpu implementation.
|
||||
// TODO(Kagami): Port it to WIN32 (see bitmessage-powfaster for an
|
||||
// example).
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#define HASH_SIZE 64
|
||||
#define NTOHLL(x) ( ( (uint64_t)(ntohl( (unsigned int)((x << 32) >> 32) )) << 32) | ntohl( ((unsigned int)(x >> 32)) ) )
|
||||
#define MAX_SAFE_JS_INTEGER 9007199254740991
|
||||
|
||||
int pow(uint32_t pool_size,
|
||||
int64_t target,
|
||||
const uint8_t* initial_hash,
|
||||
int64_t* nonce) {
|
||||
uint8_t message[HASH_SIZE+sizeof(uint64_t)];
|
||||
uint8_t digest[HASH_SIZE];
|
||||
uint64_t* be_nonce;
|
||||
uint64_t* be_trial;
|
||||
uint64_t i;
|
||||
SHA512_CTX sha;
|
||||
|
||||
memcpy(message+sizeof(uint64_t), initial_hash, HASH_SIZE);
|
||||
be_nonce = (uint64_t *)message;
|
||||
be_trial = (uint64_t *)digest;
|
||||
i = 0;
|
||||
while (1) {
|
||||
// This is very unlikely to be ever happen but it's better to be
|
||||
// sure anyway.
|
||||
if (i > MAX_SAFE_JS_INTEGER) {
|
||||
return -1;
|
||||
}
|
||||
*be_nonce = NTOHLL(i);
|
||||
SHA512_Init(&sha);
|
||||
SHA512_Update(&sha, message, HASH_SIZE+sizeof(uint64_t));
|
||||
SHA512_Final(digest, &sha);
|
||||
SHA512_Init(&sha);
|
||||
SHA512_Update(&sha, digest, HASH_SIZE);
|
||||
SHA512_Final(digest, &sha);
|
||||
if (NTOHLL(*be_trial) <= (uint64_t)target) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
*nonce = i;
|
||||
return 0;
|
||||
}
|
9
src/pow.h
Normal file
9
src/pow.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#ifndef BITMESSAGE_POW_H
|
||||
#define BITMESSAGE_POW_H
|
||||
|
||||
int pow(uint32_t pool_size,
|
||||
int64_t target,
|
||||
const uint8_t* initial_hash,
|
||||
int64_t* nonce);
|
||||
|
||||
#endif
|
90
src/worker.cc
Normal file
90
src/worker.cc
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include <node.h>
|
||||
#include <nan.h>
|
||||
#include <stdlib.h>
|
||||
#include "./pow.h"
|
||||
|
||||
using node::Buffer;
|
||||
using v8::Handle;
|
||||
using v8::Local;
|
||||
using v8::FunctionTemplate;
|
||||
using v8::Function;
|
||||
using v8::Value;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
using v8::Integer;
|
||||
|
||||
class PowWorker : public NanAsyncWorker {
|
||||
public:
|
||||
PowWorker(NanCallback* callback,
|
||||
uint32_t pool_size,
|
||||
int64_t target,
|
||||
uint8_t* initial_hash)
|
||||
: NanAsyncWorker(callback),
|
||||
pool_size(pool_size),
|
||||
target(target),
|
||||
initial_hash(initial_hash) {}
|
||||
~PowWorker() {
|
||||
free(initial_hash);
|
||||
}
|
||||
|
||||
// Executed inside the worker-thread.
|
||||
// It is not safe to access V8, or V8 data structures
|
||||
// here, so everything we need for input and output
|
||||
// should go on `this`.
|
||||
void Execute () {
|
||||
error = pow(pool_size, target, initial_hash, &nonce);
|
||||
}
|
||||
|
||||
// Executed when the async work is complete
|
||||
// this function will be run inside the main event loop
|
||||
// so it is safe to use V8 again
|
||||
void HandleOKCallback () {
|
||||
NanScope();
|
||||
if (error) {
|
||||
Local<Value> argv[] = {NanError("Max safe integer overflow")};
|
||||
callback->Call(1, argv);
|
||||
} else {
|
||||
Local<Value> argv[] = {NanNull(), NanNew<Integer>(nonce)};
|
||||
callback->Call(2, argv);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t pool_size;
|
||||
int64_t target;
|
||||
uint8_t* initial_hash;
|
||||
int64_t nonce;
|
||||
int error;
|
||||
};
|
||||
|
||||
NAN_METHOD(PowAsync) {
|
||||
NanScope();
|
||||
|
||||
NanCallback *callback = new NanCallback(args[3].As<Function>());
|
||||
uint32_t pool_size = args[0]->Uint32Value();
|
||||
int64_t target = args[1]->IntegerValue();
|
||||
size_t length = Buffer::Length(args[2]->ToObject());
|
||||
char* buf = Buffer::Data(args[2]->ToObject());
|
||||
uint8_t* initial_hash = (uint8_t *)malloc(length);
|
||||
|
||||
if (initial_hash == NULL) {
|
||||
Local<Value> argv[] = {NanError("Cannot allocate memory")};
|
||||
callback->Call(1, argv);
|
||||
} else {
|
||||
memcpy(initial_hash, buf, length);
|
||||
NanAsyncQueueWorker(
|
||||
new PowWorker(callback, pool_size, target, initial_hash));
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
// Expose synchronous and asynchronous access to our
|
||||
// Estimate() function
|
||||
void InitAll(Handle<Object> exports) {
|
||||
exports->Set(
|
||||
NanNew<String>("powAsync"),
|
||||
NanNew<FunctionTemplate>(PowAsync)->GetFunction());
|
||||
}
|
||||
|
||||
NODE_MODULE(worker, InitAll)
|
2
test.js
2
test.js
|
@ -279,7 +279,7 @@ describe("POW", function() {
|
|||
expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false;
|
||||
});
|
||||
|
||||
if (allTests && typeof window !== "undefined") {
|
||||
if (allTests) {
|
||||
it("should do a POW", function() {
|
||||
this.timeout(120000);
|
||||
return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})
|
||||
|
|
Loading…
Reference in New Issue
Block a user