Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added support for getblocktemplate.

  • Loading branch information...
commit 63c087e52aa2fc22d934b52b979353ef3877fc64 1 parent bd6f867
@travist authored
View
51 example/getblocktemplate.dart
@@ -0,0 +1,51 @@
+// Import the dartminer package.
+import 'package:dartminer/dartminer.dart';
+
+// Our main file
+void main() {
+
+ // Create a bitcoin client with the proper configuration.
+ Bitcoin bitcoin = new Bitcoin({
+ "scheme": "http",
+ "host": "127.0.0.1",
+ "port": 18332,
+ "user": "bitcoinrpc",
+ "pass": "123123123123123"
+ });
+
+ // Mine for gold.
+ void mineForGold() {
+
+ // Get work from the bitcoind.
+ bitcoin.getblocktemplate().then((dynamic tpl) {
+
+ // The template we are mining.
+ print(tpl);
+
+ // Create the new template.
+ Template template = new Template.fromJSON(tpl, address: '1N438cAaGjY9cyZ5J5hgvixkch3hiu6XA1');
+
+ // Mine for gold.
+ Map<String, String> result = template.mine();
+
+ // See if there is a result.
+ if (result != null) {
+
+ // We found gold!
+ print('GOLD!');
+
+ // Print the result.
+ print(result);
+
+ // Submit the block.
+ bitcoin.submitblock(params: [result['data']]);
+ }
+
+ // Mine for more gold!
+ mineForGold();
+ });
+ }
+
+ // Mine for gold.
+ mineForGold();
+}
View
2  example/dartminer.dart → example/getwork.dart
@@ -17,7 +17,7 @@ void main() {
void mineForGold() {
// Get work from the bitcoind.
- bitcoin.getwork().then((Map<String, String> work) {
+ bitcoin.getwork().then((dynamic work) {
// Work.
print(work);
View
4 lib/dartminer.dart
@@ -4,7 +4,9 @@ import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
+import 'dart:math';
import 'package:utf/utf.dart' as UTF;
+import 'package:crypto/crypto.dart' as Crypto;
part 'src/util.dart';
part 'src/block.dart';
@@ -13,3 +15,5 @@ part 'src/doublesha256.dart';
part 'src/bitcoin.dart';
part 'src/miner.dart';
part 'src/work.dart';
+part 'src/coinbase.dart';
+part 'src/template.dart';
View
3  lib/src/bitcoin.dart
@@ -31,7 +31,8 @@ class Bitcoin {
Future<dynamic> getwork({params: const[]}) => call('getwork', params: params);
Future<dynamic> getblockhash({params: const[]}) => call('getblockhash', params: params);
Future<dynamic> getblock({params: const []}) => call('getblock', params: params);
- Future<dynamic> getblocktemplate({params: const []}) => call("getblocktemplate", params: params);
+ Future<dynamic> getblocktemplate() => call("getblocktemplate", params: [{"capabilities": ["coinbasetxn", "workid", "coinbase/append"]}]);
+ Future<dynamic> submitblock({params: const []}) => call('submitblock', params: params);
/**
* Make a json-rpc call to our bitcoin daemon.
View
56 lib/src/block.dart
@@ -23,6 +23,9 @@ class Block {
// The nonce for this block.
int nonce;
+ // The expiration.
+ int expires;
+
// The list of transactions in this block.
List<String> tx;
@@ -49,15 +52,57 @@ class Block {
// Set the list of transactions.
tx = block['tx'];
+ // No expiration.
+ expires = 0;
+
// Get the merkle root if not provided for us.
merkleroot = (block['merkleroot'] != null) ? block['merkleroot'] : merkleRoot();
}
/**
+ * Create a new block from a block template.
+ */
+ Block.fromTemplate(dynamic template) {
+
+ // The version of the block.
+ version = template['version'];
+
+ // The previous block hash.
+ previousblockhash = template['previousblockhash'];
+
+ // The current time.
+ time = template['curtime'];
+
+ // The bits.
+ bits = template['bits'];
+
+ // Get the target from the bits.
+ target = bitsToTarget(bits);
+
+ // The nonce for this block.
+ nonce = template['nonce'];
+
+ // Set the expiration.
+ expires = template['expires'];
+
+ // Get the list of transactions.
+ tx = [];
+ template['transactions'].forEach((Map<String, String> transaction) {
+ tx.add(transaction['hash']);
+ });
+
+ // Get the merkleroot.
+ merkleroot = reverseBytes(merkleRoot());
+ }
+
+ /**
* Create a block header from data.
*/
Block.fromData(String data) {
+ // No expiration.
+ expires = 0;
+
// Get the version.
int offset = 0;
int length = 8;
@@ -124,19 +169,24 @@ class Block {
Work toWork() {
// Return the work from header.
- return new Work.fromHeader(getHeader(), target, reverseBytesInWord(nonce));
+ return new Work.fromHeader(
+ getHeader(),
+ target,
+ nonce: reverseBytesInWord(nonce),
+ expires: expires
+ );
}
/**
* Form a block header from this block.
*/
- Uint32List getHeader() {
+ Uint32List getHeader([bool reverseBits = true]) {
Uint32List header = new Uint32List(32);
header[0] = reverseBytesInWord(version);
header.setAll(1, hexToReversedList(previousblockhash));
header.setAll(9, hexToReversedList(merkleroot));
header[17] = reverseBytesInWord(time);
- header[18] = reverseBytesInWord(int.parse(bits, radix: 16));
+ header[18] = reverseBits ? reverseBytesInWord(int.parse(bits, radix: 16)) : int.parse(bits, radix: 16);
header[19] = reverseBytesInWord(nonce);
header[20] = 0x80000000;
return header;
View
141 lib/src/coinbase.dart
@@ -0,0 +1,141 @@
+part of dartminer;
+
+class Coinbase {
+
+ String data;
+
+ /**
+ * Create a new coinbase provided the scriptsig, value, and address.
+ *
+ * @param String script
+ * The coinbase scriptsig value. Provided from coinbaseaux parameter.
+ *
+ * @param int value
+ * The value of the coinbase in Satoshi's
+ *
+ * @param String address
+ * The base58 Bitcoin address to send the value.
+ */
+ Coinbase(String script, int value, String address) {
+
+ // Create a new string buffer.
+ StringBuffer buffer = new StringBuffer();
+ String pubKey = addressToPubKey(address);
+
+ // txn version
+ buffer.write('01000000');
+
+ // txn in-counter
+ buffer.write('01');
+
+ // input[0] prev hash
+ buffer.write('0000000000000000000000000000000000000000000000000000000000000000');
+
+ // input[0] prev seqnum
+ buffer.write('ffffffff');
+
+ // input[0] script length;
+ buffer.write(int2VarIntHex(script.length ~/ 2));
+
+ // input[0] script
+ buffer.write(script);
+
+ // input[0] seqnum
+ buffer.write('ffffffff');
+
+ // out-counter
+ buffer.write('01');
+
+ // output[0] value (little endian)
+ buffer.write(word2LEHex(value));
+
+ // output[0] script length
+ buffer.write(int2VarIntHex(pubKey.length ~/ 2));
+
+ // output[0] script.
+ buffer.write(pubKey);
+
+ // lock-time
+ buffer.write('00000000');
+
+ // Assign the data to the buffer string.
+ data = buffer.toString();
+ }
+
+ /**
+ * Create a new coinbase from data already provided.
+ */
+ Coinbase.fromData(String this.data);
+
+ /**
+ * Convert a Base58 Bitcoin address to its Hash-160 ASCII Hex
+ *
+ * @param String address
+ * The base58 bitcoin address.
+ *
+ * @return String
+ * The hash160 version of the address.
+ */
+ String addressToHash160(String address) {
+ String table = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+ address = reverseString(address);
+ int hash = 0;
+ for (int i = 0; i < address.length; i++) {
+ hash += pow(58, i) * table.indexOf(address[i]);
+ }
+
+ // Return the hash160.
+ String hash160 = hash.toRadixString(16);
+ return hash160.substring(0, (hash160.length - 8));
+ }
+
+ /**
+ * Convert a bitcoin address to a public Key
+ *
+ * @param String address
+ * The base58 bitcoin address.
+ *
+ * @return String
+ * The public key version of a bitcoin address.
+ */
+ String addressToPubKey(String address) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.write('76'); // OP_DUP
+ buffer.write('a9'); // OP_HASH160
+ buffer.write('14'); // push 20 bytes
+ buffer.write(addressToHash160(address));
+ buffer.write('88'); // OP_EQUALVERIFY
+ buffer.write('ac'); // OP_CHECKSIG
+ return buffer.toString();
+ }
+
+ /**
+ * Return the data provided an extranonce.
+ *
+ * @param int extranonce
+ * The extranonce to add to the coinbase data.
+ *
+ * @return String
+ * The data from the coinbase.
+ */
+ String getData([int extranonce = 0]) {
+ // Return the original coinbase if the extranonce is 0.
+ if (extranonce == 0) {
+ return data;
+ }
+
+ // Get the original length;
+ int origLen = int.parse(data.substring(82, 84), radix: 16);
+ int newLen = origLen + 8;
+ int offset = 84 + (origLen * 2);
+
+ // Create a new string buffer.
+ StringBuffer coinbase = new StringBuffer();
+ coinbase.write(data.substring(0, 82));
+ coinbase.write(int2PaddedHex(newLen, 2));
+ coinbase.write(data.substring(84, offset));
+ coinbase.write(bigword2LEHex(extranonce));
+ coinbase.write(data.substring(offset));
+ return coinbase.toString();
+ }
+}
View
29 lib/src/miner.dart
@@ -11,8 +11,8 @@ class Miner {
* @param Map work
* The JSON map of the work.
*/
- Miner.fromJSON(Map<String, String> work, [int startNonce = 0]) {
- this.work = new Work.fromJSON(work, startNonce);
+ Miner.fromJSON(Map<String, String> work, {int expires: 0, int nonce: 0}) {
+ this.work = new Work.fromJSON(work, nonce: nonce, expires: expires);
}
/**
@@ -21,14 +21,11 @@ class Miner {
* @param Uint32List header
* The little-endian Uint32List header.
*
- * @param Uint32List target
- * The target.
- *
* @param int startNonce
* The nonce to start with.
*/
- Miner.fromHeader(Uint32List header, Uint32List target, [int startNonce = 0]) {
- this.work = new Work.fromHeader(header, target, startNonce);
+ Miner.fromHeader(Uint32List header, Uint32List target, {int expires: 0, int nonce: 0}) {
+ this.work = new Work.fromHeader(header, target, nonce: nonce, expires: expires);
}
/**
@@ -39,13 +36,13 @@ class Miner {
/**
* Mine for the nonce.
*/
- Map<String, String> mine([done]) {
+ Map<String, String> mine([bool reverseWords = true]) {
// Perform a hash check every 1M cycles.
int hashCheck = 1000000;
// Record the last time.
- int lastTime = (new DateTime.now()).millisecondsSinceEpoch ~/ 1000;
+ int lastTime = now();
// Iterate while there is more work to be done.
while(work.hasWork() && !work.checkNonce()) {
@@ -55,7 +52,17 @@ class Miner {
// Print an update...
if ((work.nonce % hashCheck) == 0) {
- int thisTime = (new DateTime.now()).millisecondsSinceEpoch ~/ 1000;
+
+ // Get the current time.
+ int thisTime = now();
+
+ // Make sure our mining has not expired.
+ if (work.expired(thisTime)) {
+ print('Mining expired.');
+ break;
+ }
+
+ // Determine the hash rate and print.
int hashRate = hashCheck ~/ (thisTime - lastTime);
print('HashRate: ' + hashRate.toString() + ' H/s Nonce: ' + work.nonce.toString());
lastTime = thisTime;
@@ -63,6 +70,6 @@ class Miner {
}
// Return the work response.
- return work.response();
+ return work.response(reverseWords);
}
}
View
208 lib/src/template.dart
@@ -0,0 +1,208 @@
+part of dartminer;
+
+class Template {
+
+ // The base58 bitcoin address.
+ String address;
+
+ // The block for this template.
+ Block block;
+
+ // The extra nonce.
+ int extranonce;
+
+ // The template object.
+ dynamic template;
+
+ // The created timestamp.
+ int created;
+
+ // The expiration of this template.
+ int expires;
+
+ // The coinbase data.
+ String coinbaseData;
+
+ // The list of mutations.
+ Map<String, bool> mutations;
+
+ /**
+ * Create a new template from JSON.
+ */
+ Template.fromJSON(dynamic this.template, {int this.extranonce: 0, String this.address: ''}) {
+
+ // Set the created timestamp.
+ created = now();
+
+ // Set the expiration time.
+ expires = template['expires'];
+ expires = (expires == null) ? 120 : expires;
+
+ // Set the mutations.
+ mutations = {};
+ template['mutable'].forEach((mutable) {
+ mutations[mutable] = true;
+ });
+ }
+
+ /**
+ * Determine if we have time left in our mining.
+ */
+ bool timeLeft() {
+
+ // If no expiration, then return true.
+ if (expires == 0) {
+ return true;
+ }
+
+ // Get the now time.
+ int timestamp = now();
+
+ // See how old this template is.
+ int age = timestamp - created;
+ return (age < expires);
+ }
+
+ /**
+ * Determine if there is more work to be done.
+ */
+ bool workLeft() {
+
+ // If these mutations are not available, then always return true.
+ if (!mutations.containsKey('coinbase/append') && !mutations.containsKey('coinbase')) {
+ return true;
+ }
+
+ // Return that the extranonce can be iterated.
+ return (extranonce < 0xffffffffffffffff);
+ }
+
+ /**
+ * Mine for gold.
+ */
+ Map<String, String> mine([int nonce = 0]) {
+
+ // Initialize the variables.
+ Map<String, String> result = null;
+ Uint32List data = null;
+ Miner miner = null;
+
+ // Iterate while we are still creating blocks.
+ while (createBlock()) {
+
+ // Create the miner.
+ miner = new Miner.fromHeader(
+ block.getHeader(false),
+ block.target,
+ nonce: nonce,
+ expires: (expires - (now() - created))
+ );
+
+ // Mine for gold.
+ result = miner.mine(false);
+ if (result != null) {
+ break;
+ }
+
+ // Increment the extranonce.
+ extranonce++;
+ }
+
+ // Reformat the result for a submitwork.
+ if (result != null) {
+
+ // Create a string buffer for our result.
+ StringBuffer buffer = new StringBuffer();
+ buffer.write(result['data'].substring(0, 152));
+ buffer.write(int2PaddedHex(miner.work.nonce, 8));
+
+ // If submit/truncate is not in the mutations.
+ if (!mutations.containsKey('submit/truncate')) {
+
+ // Add the length of transactions.
+ buffer.write(int2VarIntHex(block.tx.length));
+
+ // Add the coinbase transaction.
+ buffer.write(coinbaseData);
+
+ // Only execute if the submit/coinbase isn't set.
+ if (!mutations.containsKey('submit/coinbase')) {
+ for (int i = 1; i < template['transactions'].length; i++) {
+ buffer.write(template['transactions'][i]['data']);
+ }
+ }
+ }
+
+ // Set the new data.
+ result['data'] = buffer.toString();
+ }
+
+ // Return the result.
+ return result;
+ }
+
+ /**
+ * Returns more work to do.
+ */
+ bool createBlock([int nonce = 0]) {
+
+ // The coinbase.
+ Coinbase coinbase = null;
+
+ // See if there is more work to do.
+ if (workLeft() && timeLeft()) {
+
+ // Create the coinbase if it hasn't already been created.
+ if (coinbase == null) {
+
+ // Check to see if the template has the actual coinbase txn.
+ if (template.containsKey('coinbasetxn')) {
+
+ // Create the coinbase from existing data.
+ coinbase = new Coinbase.fromData(template['coinbasetxn']['data']);
+ }
+ else if (template.containsKey('coinbaseaux')) {
+
+ // We must have a bitcoin address to create a coinbase with address.
+ if (address != '') {
+
+ // Create a new coinbase from the aux, value and bitcoin address.
+ coinbase = new Coinbase(
+ template['coinbaseaux']['flags'],
+ template['coinbasevalue'],
+ address
+ );
+ }
+ else {
+
+ // Throw an error.
+ throw("You must define a bitcoin address to create coinbase.");
+ }
+ }
+ }
+
+ // Make sure we have a coinbase.
+ if (coinbase != null) {
+
+ // Make and insert the coinbase transaction.
+ coinbaseData = coinbase.getData(extranonce);
+ template['transactions'].insert(0, {
+ 'data': coinbaseData,
+ 'hash': doubleHash(coinbaseData)
+ });
+
+ // Set the nonce to 0.
+ template['nonce'] = nonce;
+
+ // Create a new block from the template.
+ block = new Block.fromTemplate(template);
+
+ // Return that we have more work to do.
+ return true;
+ }
+ }
+
+ // Return that work has been done.
+ return false;
+ }
+}
View
156 lib/src/util.dart
@@ -1,6 +1,22 @@
part of dartminer;
/**
+ * Reverses the bytes in a half word.
+ *
+ * @param int halfword
+ * The 16 bit integer word to reverse the bytes.
+ *
+ * @return int
+ * The integer with all of the bytes reversed.
+ */
+int reverseBytesInHalfWord(int halfword) {
+ return (
+ ((halfword >> 8) & 0xFF) |
+ ((halfword & 0xFF) << 8)
+ );
+}
+
+/**
* Reverses the bytes in an integer word.
*
* @param int word
@@ -19,6 +35,28 @@ int reverseBytesInWord(int word) {
}
/**
+ * Reverses the bytes in an 64 bit integer.
+ *
+ * @param int word
+ * The 64 bit integer word to reverse the bytes.
+ *
+ * @return int
+ * The integer with all of the bytes reversed.
+ */
+int reverseBytesInBigWord(int bigword) {
+ return (
+ ((bigword & 0xFF00000000000000) >> 56) |
+ ((bigword & 0x00FF000000000000) >> 40) |
+ ((bigword & 0x0000FF0000000000) >> 24) |
+ ((bigword & 0x000000FF00000000) >> 8) |
+ ((bigword & 0x00000000FF000000) << 8) |
+ ((bigword & 0x0000000000FF0000) << 24) |
+ ((bigword & 0x000000000000FF00) << 40) |
+ ((bigword & 0x00000000000000FF) << 56)
+ );
+}
+
+/**
* Add padding to a number string.
*
* @param num
@@ -45,7 +83,58 @@ String padNumString(String num, int len) {
// Convert functions.
String toHex(int num) => num.toRadixString(16).toLowerCase();
+String int2PaddedHex(int num, int len) => padNumString(toHex(num), len);
+String byte2LEHex(int byte) => padNumString(toHex((byte & 0xFF)), 2);
+String halfWord2LEHex(int halfword) => padNumString(toHex(reverseBytesInHalfWord(halfword)), 4);
String word2LEHex(int word) => padNumString(toHex(reverseBytesInWord(word)), 8);
+String bigword2LEHex(int bigword) => padNumString(toHex(reverseBytesInBigWord(bigword)), 16);
+
+/**
+ * Reverses the bytes of a hex string.
+ *
+ * @param String hex
+ * The original hex string.
+ *
+ * @return String
+ * The bytes in reversed form.
+ */
+String reverseBytes(String hex) {
+ return Crypto.CryptoUtils.bytesToHex(hex2CodeUnits(hex).reversed.toList());
+}
+
+/**
+ * Convert an unsigned integer to little endian varint ASCII Hex
+ *
+ * @param int value
+ * The integer to create the LE variant hex.
+ *
+ * @return String
+ * The LE variant hex.
+ */
+String int2VarIntHex(int x) {
+ if (x < 0xfd) {
+ return byte2LEHex(x);
+ }
+ else if (x <= 0xffff) {
+ return 'fd' + halfWord2LEHex(x);
+ }
+ else if (x <= 0xffffffff) {
+ return 'fe' + word2LEHex(x);
+ }
+ else {
+ return 'ff' + bigword2LEHex(x);
+ }
+}
+
+/**
+ * UNIX timestamp.
+ *
+ * @return int
+ * The UNIX timestamp.
+ */
+int now() {
+ return (new DateTime.now()).millisecondsSinceEpoch ~/ 1000;
+}
/**
* Convert a hexidecimal string to an array of 32bit unsigned integers.
@@ -91,21 +180,80 @@ Uint32List hexToReversedList(String hex, [bool reverseWords = true]) {
}
/**
+ * Convert a hexidecimal string to a list of codeUnits.
+ *
+ * @param String hex
+ * The hexidecimal string.
+ *
+ * @return List<int>
+ * The list of code units for the hex string.
+ */
+List<int> hex2CodeUnits(String hex) {
+ if ((hex.length % 2) != 0) {
+ hex = '0' + hex;
+ }
+ int listSize = hex.length ~/ 2;
+ List<int> arr = new List(listSize);
+ int index = 0;
+ int word = 0;
+ for (var i = 0; i < hex.length; i += 2) {
+ arr[index] = int.parse(hex.substring(i, (i + 2)), radix: 16);
+ index++;
+ }
+ return arr;
+}
+
+/**
* Convert a list to a hex string.
*/
-String listToHex(Uint32List list) {
+String listToHex(Uint32List list, [bool reverseWords = true]) {
StringBuffer buf = new StringBuffer();
for (int part in list) {
- buf.write(word2LEHex(part));
+ buf.write(reverseWords ? word2LEHex(part) : int2PaddedHex(part, 8));
}
return buf.toString();
}
-String listToReversedHex(Uint32List list) {
+String listToReversedHex(Uint32List list, [bool reverseWords = true]) {
StringBuffer buff = new StringBuffer();
int i = list.length;
while (i-- > 0) {
- buff.write(word2LEHex(list[i]));
+ buff.write(reverseWords ? word2LEHex(list[i]) : int2PaddedHex(list[i], 8));
}
return buff.toString();
}
+
+/**
+ * Reverse a string.
+ */
+String reverseString(String str) {
+ return new String.fromCharCodes(str.codeUnits.reversed.toList());
+}
+
+/**
+ * Perform a double hexidecimal hash.
+ *
+ * @param String hex
+ * The hexidecimal string to hash.
+ *
+ * @return String
+ * The double-hashed hexidecimal string.
+ */
+String doubleHash(String hex) {
+ Crypto.SHA256 h1 = new Crypto.SHA256();
+ Crypto.SHA256 h2 = new Crypto.SHA256();
+ h1.add(hex2CodeUnits(hex));
+ h2.add(h1.close());
+ return Crypto.CryptoUtils.bytesToHex(h2.close());
+}
+
+// Gets the JSON from a file.
+Future<dynamic> getJSON(String fileName) {
+ Completer completer = new Completer();
+ var file = new File(fileName);
+ Future<String> finishedReading = file.readAsString(encoding: ASCII);
+ finishedReading.then((String content) {
+ completer.complete(JSON.decode(content));
+ });
+ return completer.future;
+}
View
47 lib/src/work.dart
@@ -16,16 +16,22 @@ class Work {
// If the nonce is golden.
bool golden;
+ // The expiration of this work.
+ int expires;
+
+ // The created date.
+ int created;
+
/**
* Create a new work object from the json object of 'getwork'
*
* @param Map<String, String> work
* The JSON representation of a work object.
*/
- Work.fromJSON(Map<String, String> work, [int startNonce = 0]) {
+ Work.fromJSON(Map<String, String> work, {int this.expires: 0, int this.nonce: 0}) {
sha256 = new doubleSHA256();
- nonce = startNonce;
golden = false;
+ created = now();
sha256.midstate = hexToList(work["midstate"]);
half = hexToList(work["data"].substring(0, 128));
data = hexToList(work["data"].substring(128, 256));
@@ -35,9 +41,9 @@ class Work {
/**
* Create work from a single data string.
*/
- Work.fromData(String hexData, [int startNonce = 0]) {
- nonce = startNonce;
+ Work.fromData(String hexData, {int this.expires: 0, int this.nonce: 0}) {
golden = false;
+ created = now();
half = hexToList(hexData.substring(0, 128));
data = hexToList(hexData.substring(128, 256));
sha256 = new doubleSHA256(half);
@@ -50,15 +56,12 @@ class Work {
* @param Uint32List header
* The little-endian Uint32List header.
*
- * @param Uint32List target
- * The target.
- *
* @param int startNonce
* The nonce to start with.
*/
- Work.fromHeader(Uint32List header, Uint32List this.target, [int startNonce = 0]) {
- nonce = startNonce;
+ Work.fromHeader(Uint32List header, Uint32List this.target, {int this.expires: 0, int this.nonce: 0}) {
golden = false;
+ created = now();
// Get the first half of the header.
half = header.sublist(0, 16);
@@ -98,6 +101,28 @@ class Work {
}
/**
+ * Make sure our mining has not expired.
+ *
+ * @param int timestamp
+ * The timestamp to check the expiration on.
+ */
+ bool expired([int timestamp = 0]) {
+
+ // If there isn't a timestamp, then create one.
+ if (timestamp == 0) {
+ timestamp = now();
+ }
+
+ // If no expiration, then always return true.
+ if (expires == 0) {
+ return true;
+ }
+
+ // Return if we still have time to mine.
+ return (timestamp - created) > expires;
+ }
+
+ /**
* Check if the sha256 state is the golden ticket.
*/
bool isGolden() {
@@ -123,7 +148,7 @@ class Work {
/**
* Provide the response for the work performed.
*/
- Map<String, String> response() {
+ Map<String, String> response([bool reverseWords = true]) {
// Check if this is the golden hash.
if (golden) {
@@ -137,7 +162,7 @@ class Work {
return {
'nonce': reverseBytesInWord(nonce).toString(),
'hash': listToReversedHex(sha256.state),
- 'data': listToHex(resultData)
+ 'data': listToHex(resultData, reverseWords)
};
}
View
1  pubspec.yaml
@@ -4,5 +4,6 @@ version: 0.0.7
homepage: https://github.com/travist/dartminer
description: A Bitcoin miner with Dart language.
dependencies:
+ crypto: any
unittest: '>=0.10.0 <0.10.1'
utf: '>=0.9.0 <0.9.1'
View
42 test/dartminer.dart
@@ -1,22 +1,9 @@
import 'package:unittest/unittest.dart';
import 'package:dartminer/dartminer.dart';
-import 'dart:convert';
import 'dart:async';
-import 'dart:io';
void main() {
- // Gets the JSON from a file.
- Future<dynamic> getJSON(String fileName) {
- Completer completer = new Completer();
- var file = new File(fileName);
- Future<String> finishedReading = file.readAsString(encoding: ASCII);
- finishedReading.then((String content) {
- completer.complete(JSON.decode(content));
- });
- return completer.future;
- }
-
// Gets the mining result from a block.
Map<String, String> getResult(dynamic blockJSON) {
Block block = new Block.fromJSON(blockJSON);
@@ -68,15 +55,33 @@ void main() {
}));
});
+ test('Coinbase', () {
+ });
+
+ test('doubleHash', () {
+ String test = '020000003c48a294584f90e58325c60ca82896d071826b45680a661cec4d424d00000000de6433d46c0c7f50d84a05aec77be0199176cdd47f77e344b6f50c84380fddba66dc47501d00ffff00000000';
+ String hash = '913d5c7f6625529bde85c1d656591b64560b8bbf11213c4d22c4bad957c954ae';
+ expect(doubleHash(test), hash);
+ });
+
test('getwork Mining', () {
getJSON('getwork.json').then(expectAsync((dynamic res) {
String data = '0000000109a78d37203813d08b45854d51470fcdb588d6dfabbe946e92ad207e0000000038a8ae02f7471575aa120d0c85a10c886a1398ad821fadf5124c37200cb677854e0603871d07fff831952e35000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000';
- Miner miner = new Miner.fromJSON(res['result'], reverseBytesInWord(0x204e2e35));
+ Miner miner = new Miner.fromJSON(res['result'], nonce: reverseBytesInWord(0x204e2e35));
Map<String, String> result = miner.mine();
expect(result['data'], data);
}));
});
+ test('Data to Header to Miner', () {
+ String data = '00000002b15704f4ecae05d077e54f6ec36da7f20189ef73b77603225ae56d2b00000000b052cbbdeed2489ccb13a526b77fadceef4caf7d3bb82a9eb0b69ebb90f9f5a7510c27fd1c0e8a37fa531338000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000';
+ Block block = new Block.fromData(data);
+ Miner miner = new Miner.fromHeader(block.getHeader(), block.target);
+ miner.work.nonce = miner.work.data[3];
+ Map<String, String> result = miner.mine();
+ expect(result['data'], data);
+ });
+
test('Data to Miner', () {
String data = '00000002b15704f4ecae05d077e54f6ec36da7f20189ef73b77603225ae56d2b00000000b052cbbdeed2489ccb13a526b77fadceef4caf7d3bb82a9eb0b69ebb90f9f5a7510c27fd1c0e8a37fa531338000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000';
Miner miner = new Miner.fromWork(new Work.fromData(data));
@@ -93,4 +98,13 @@ void main() {
Map<String, String> result = miner.mine();
expect(result['data'], data);
});
+
+ test('Test getblocktemplate', () {
+ getJSON('template.json').then(expectAsync((dynamic tpl) {
+ String data = '020000003c48a294584f90e58325c60ca82896d071826b45680a661cec4d424d00000000de6433d46c0c7f50d84a05aec77be0199176cdd47f77e344b6f50c84380fddba66dc47501d00ffff000001000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1302955d0f00456c6967697573005047dc66085fffffffff02fff1052a010000001976a9144ebeb1cd26d6227635828d60d3e0ed7d0da248fb88ac01000000000000001976a9147c866aee1fa2f3b3d5effad576df3dbf1f07475588ac00000000';
+ Template template = new Template.fromJSON(tpl['result'], address: '1N438cAaGjY9cyZ5J5hgvixkch3hiu6XA1');
+ Map<String, String> result = template.mine();
+ expect(result['data'], data);
+ }));
+ });
}
View
22 test/template.json
@@ -0,0 +1,22 @@
+{
+ "result": {
+ "previousblockhash": "000000004d424dec1c660a68456b8271d09628a80cc62583e5904f5894a2483c",
+ "target": "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ "noncerange": "00000000ffffffff",
+ "transactions": [],
+ "sigoplimit": 20000,
+ "expires": 120,
+ "longpoll": "/LP",
+ "height": 23957,
+ "coinbasetxn": {
+ "data": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1302955d0f00456c6967697573005047dc66085fffffffff02fff1052a010000001976a9144ebeb1cd26d6227635828d60d3e0ed7d0da248fb88ac01000000000000001976a9147c866aee1fa2f3b3d5effad576df3dbf1f07475588ac00000000"
+ },
+ "version": 2,
+ "curtime": 1346886758,
+ "mutable": ["coinbase/append"],
+ "sizelimit": 1000000,
+ "bits": "1d00ffff"
+ },
+ "id": 0,
+ "error": null
+}
Please sign in to comment.
Something went wrong with that request. Please try again.