Initial import

This commit is contained in:
Luke Dashjr
2012-09-04 04:24:52 +00:00
commit 51d1469cd1
10 changed files with 758 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*~
*.so
*.o
a.*
todo*
example

21
Makefile Normal file
View File

@@ -0,0 +1,21 @@
LIBNAME := blkmaker
CFLAGS := -ggdb -O0 -Wall -Werror
all: lib$(LIBNAME).so lib$(LIBNAME)_jansson.so
example: example.o lib$(LIBNAME).so lib$(LIBNAME)_jansson.so
$(CC) $(LDFLAGS) -o $@ $^ -ljansson -lgcrypt -Wl,-rpath,.
lib$(LIBNAME).so: blkmaker.o blktemplate.o
lib$(LIBNAME)_jansson.so: blkmaker_jansson.o
%.so:
$(CC) -shared $(LDFLAGS) -o $@ $^
%.o: %.c *.h
$(CC) -c -fPIC -I. $(CFLAGS) -std=c99 -o $@ $<
clean:
rm -f *.so *.o

16
README Normal file
View File

@@ -0,0 +1,16 @@
Dependencies:
Jansson 2.0 with 'long long' support
Example dependencies:
Jansson 2.1 (to read JSON from stdin)
libgcrypt (for SHA256)
For usage, check out example.c
Note that you must assign blkmk_sha256_impl to a function pointer:
bool mysha256(void *hash_out, const void *data, size_t datasz)
hash_out must be able to overlap with data!
Also note that you should NOT roll ntime for data retrieved; while it will
probably work, there is no guarantee it won't fall outside the maxtime limits
for the blocktemplate. It is usually best to simply get more data as often
as it is needed.

96
blkmaker.c Normal file
View File

@@ -0,0 +1,96 @@
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <blkmaker.h>
#include <blktemplate.h>
static inline
void my_htole32(unsigned char *buf, uint32_t n) {
buf[0] = (n >> 0) % 256;
buf[1] = (n >> 8) % 256;
buf[2] = (n >> 16) % 256;
buf[3] = (n >> 24) % 256;
}
bool (*blkmk_sha256_impl)(void *, const void *, size_t) = NULL;
static
bool dblsha256(void *hash, const void *data, size_t datasz) {
return blkmk_sha256_impl(hash, data, datasz) && blkmk_sha256_impl(hash, hash, 32);
}
static
bool build_merkle_root(unsigned char *mrklroot_out, blktemplate_t *tmpl) {
if (!tmpl->cbtxn)
return false;
size_t hashcount = tmpl->txncount + 1;
unsigned char hashes[(hashcount + 1) * 32];
if (!dblsha256(&hashes[0], tmpl->cbtxn->data, tmpl->cbtxn->datasz))
return false;
for (int i = 0; i < tmpl->txncount; ++i)
if (!dblsha256(&hashes[32 * (i + 1)], tmpl->txns[i].data, tmpl->txns[i].datasz))
return false;
while (hashcount > 1)
{
if (hashcount % 2)
{
memcpy(&hashes[32 * hashcount], &hashes[32 * (hashcount - 1)], 32);
++hashcount;
}
for (int i = 0; i < hashcount; i += 2)
// This is where we overlap input and output, on the first pair
if (!dblsha256(&hashes[i / 2 * 32], &hashes[32 * i], 64))
return false;
hashcount /= 2;
}
memcpy(mrklroot_out, &hashes[0], 32);
return true;
}
size_t blkmk_get_data(blktemplate_t *tmpl, void *buf, size_t bufsz, time_t usetime, int16_t *out_expire) {
if (!(blkmk_time_left(tmpl, usetime) && blkmk_work_left(tmpl)))
return 0;
if (bufsz < 76)
return 76;
unsigned char *cbuf = buf;
my_htole32(&cbuf[0], tmpl->version);
memcpy(&cbuf[4], &tmpl->prevblk, 32);
if (!build_merkle_root(&cbuf[36], tmpl))
return 0;
blktime_t timehdr = tmpl->curtime + difftime(usetime, tmpl->_time_rcvd);
if (timehdr > tmpl->maxtime)
timehdr = tmpl->maxtime;
my_htole32(&cbuf[68], timehdr);
memcpy(&cbuf[72], &tmpl->diffbits, 4);
// TODO: set *out_expire if provided
// TEMPORARY HACK:
memcpy(tmpl->_mrklroot, &cbuf[36], 32);
return 76;
}
blktime_diff_t blkmk_time_left(const blktemplate_t *tmpl, time_t nowtime) {
double age = difftime(nowtime, tmpl->_time_rcvd);
if (age >= tmpl->expires)
return 0;
return tmpl->expires - age;
}
unsigned long blkmk_work_left(const blktemplate_t *tmpl) {
// TODO
// TEMPORARY HACK
return (tmpl->version && !tmpl->_mrklroot[0]) ? 1 : 0;
return BLKMK_UNLIMITED_WORK_COUNT;
}

17
blkmaker.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef BLKMAKER_H
#define BLKMAKER_H
#include <stdbool.h>
#include <blktemplate.h>
#define BLKMAKER_VERSION (0L)
extern bool (*blkmk_sha256_impl)(void *hash_out, const void *data, size_t datasz);
extern size_t blkmk_get_data(blktemplate_t *, void *buf, size_t bufsz, time_t usetime, int16_t *out_expire);
extern blktime_diff_t blkmk_time_left(const blktemplate_t *, time_t nowtime);
extern unsigned long blkmk_work_left(const blktemplate_t *);
#define BLKMK_UNLIMITED_WORK_COUNT ULONG_MAX
#endif

302
blkmaker_jansson.c Normal file
View File

@@ -0,0 +1,302 @@
#define _BSD_SOURCE
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <jansson.h>
#include <blktemplate.h>
#ifndef JSON_INTEGER_IS_LONG_LONG
# error "Jansson 2.0 with long long support required!"
#endif
json_t *blktmpl_request_jansson(gbt_capabilities_t caps) {
json_t *req, *jcaps, *jstr, *reqf, *reqa;
if (!(req = json_object()))
return NULL;
jstr = reqa = jcaps = NULL;
if (!(reqf = json_object()))
goto err;
if (!(reqa = json_array()))
goto err;
if (!(jcaps = json_array()))
goto err;
for (int i = 0; i < GBT_CAPABILITY_COUNT; ++i)
if (caps & (1 << i))
{
jstr = json_string(blktmpl_capabilityname(1 << i));
if (!jstr)
goto err;
if (json_array_append_new(jcaps, jstr))
goto err;
}
if (!(jstr = json_integer(0)))
goto err;
if (json_object_set_new(req, "capabilities", jcaps))
goto err;
if (json_object_set_new(reqf, "id", jstr))
goto err;
if (!(jstr = json_string("getblocktemplate")))
goto err;
if (json_object_set_new(reqf, "method", jstr))
goto err;
if (json_array_append_new(reqa, req))
goto err;
if (json_object_set_new(reqf, "params", reqa))
goto err;
return reqf;
err:
if (req ) json_decref(req );
if (reqa ) json_decref(reqa );
if (reqf ) json_decref(reqf );
if (jcaps) json_decref(jcaps);
if (jstr ) json_decref(jstr );
return NULL;
}
static bool my_hex2bin(void *o, const char *x, size_t len) {
unsigned char *oc = o;
unsigned char c, hc = 0x10;
len *= 2;
while (len)
{
switch (x[0]) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
c = x[0] - '0';
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
c = x[0] - 'A' + 10;
break;
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
c = x[0] - 'a' + 10;
break;
default:
return false;
}
++x;
if (hc < 0x10)
{
(oc++)[0] = (hc << 4) | c;
hc = 0x10;
}
else
hc = c;
--len;
}
return !x[0];
}
#define GET(key, type) do { \
if (!(v = json_object_get(json, #key))) \
return "Missing '" #key "'"; \
if (!json_is_ ## type(v)) \
return "Wrong type for '" #key "'"; \
} while(0)
#define GETHEX(key, skey) do { \
GET(key, string); \
if (!my_hex2bin(tmpl->skey, json_string_value(v), sizeof(tmpl->skey))) \
return "Error decoding '" #key "'"; \
} while(0)
#define GETNUM(key) do { \
GET(key, number); \
tmpl->key = json_integer_value(v); \
} while(0)
static
const char *parse_txn(struct blktxn_t *txn, json_t *txnj) {
json_t *vv;
if (!((vv = json_object_get(txnj, "data")) && json_is_string(vv)))
return "Missing or invalid type for transaction data";
const char *hexdata = json_string_value(vv);
size_t datasz = strlen(hexdata) / 2;
txn->data = malloc(datasz);
txn->datasz = datasz;
if (!my_hex2bin(txn->data, hexdata, datasz))
return "Error decoding transaction data";
if ((vv = json_object_get(txnj, "hash")) && json_is_string(vv))
{
hexdata = json_string_value(vv);
txn->hash = malloc(sizeof(*txn->hash));
if (!my_hex2bin(*txn->hash, hexdata, sizeof(*txn->hash)))
{
free(txn->hash);
txn->hash = NULL;
}
}
// TODO: dependcount/depends, fee, required, sigops
return NULL;
}
static
void my_flip(void *data, size_t datasz) {
char *cdata = (char*)data;
--datasz;
size_t hds = datasz / 2;
for (int i = 0; i <= hds; ++i)
{
int altp = datasz - i;
char c = cdata[i];
cdata[i] = cdata[altp];
cdata[altp] = c;
}
}
const char *blktmpl_add_jansson(blktemplate_t *tmpl, json_t *json, time_t time_rcvd) {
if (tmpl->version)
return false;
json_t *v;
const char *s;
if ((v = json_object_get(json, "result")))
{
json_t *je;
if ((je = json_object_get(json, "error")) && !json_is_null(je))
return "JSON result is error";
json = v;
}
GETHEX(bits, diffbits);
GETNUM(curtime);
GETNUM(height);
GETHEX(previousblockhash, prevblk);
my_flip(tmpl->prevblk, 32);
GETNUM(sigoplimit);
GETNUM(sizelimit);
GETNUM(version);
if ((v = json_object_get(json, "coinbasevalue")) && json_is_number(v))
tmpl->cbvalue = json_integer_value(v);
if ((v = json_object_get(json, "workid")) && json_is_string(v))
if (!(tmpl->workid = strdup(json_string_value(v))))
return "Error copying 'workid'";
v = json_object_get(json, "transactions");
size_t txns = tmpl->txncount = json_array_size(v);
tmpl->txns = calloc(txns, sizeof(*tmpl->txns));
for (size_t i = 0; i < txns; ++i)
if ((s = parse_txn(&tmpl->txns[i], json_array_get(v, i))))
return s;
if ((v = json_object_get(json, "coinbasetxn")) && json_is_object(v))
{
tmpl->cbtxn = calloc(1, sizeof(*tmpl->cbtxn));
if ((s = parse_txn(tmpl->cbtxn, v)))
return s;
}
// TODO: coinbaseaux
tmpl->_time_rcvd = time_rcvd;
return NULL;
}
static
char varintEncode(unsigned char *out, uint64_t n) {
if (n < 0xfd)
{
out[0] = n;
return 1;
}
char L;
if (n <= 0xffff)
{
out[0] = '\xfd';
L = 3;
}
else
if (n <= 0xffffffff)
{
out[0] = '\xfe';
L = 5;
}
else
{
out[0] = '\xff';
L = 9;
}
for (unsigned char i = 1; i < L; ++i)
out[i] = (n >> ((i - 1) * 8)) % 256;
return L;
}
json_t *blkmk_submit_jansson(blktemplate_t *tmpl, const unsigned char *data, blknonce_t nonce) {
unsigned char blk[80 + 8 + 1000000];
memcpy(blk, data, 76);
*(uint32_t*)(&blk[76]) = htonl(nonce);
size_t offs = 80;
offs += varintEncode(&blk[offs], 1 + tmpl->txncount);
memcpy(&blk[offs], tmpl->cbtxn->data, tmpl->cbtxn->datasz);
offs += tmpl->cbtxn->datasz;
for (int i = 0; i < tmpl->txncount; ++i)
{
memcpy(&blk[offs], tmpl->txns[i].data, tmpl->txns[i].datasz);
offs += tmpl->txns[i].datasz;
}
char blkhex[(offs * 2) + 1];
blkhex[offs * 2] = '\0';
static char hex[] = "0123456789abcdef";
for (size_t i = 0; i < offs; ++i)
{
blkhex[ i*2 ] = hex[blk[i] >> 4];
blkhex[(i*2)+1] = hex[blk[i] & 15];
}
json_t *rv = json_array(), *ja, *jb;
jb = NULL;
if (!(ja = json_string(blkhex)))
goto err;
if (json_array_append_new(rv, ja))
goto err;
if (!(ja = json_object()))
goto err;
if (tmpl->workid)
{
if (!(jb = json_string(tmpl->workid)))
goto err;
if (json_object_set_new(ja, "workid", jb))
goto err;
jb = NULL;
}
if (json_array_append_new(rv, ja))
goto err;
if (!(ja = json_object()))
goto err;
if (!(jb = json_integer(0)))
goto err;
if (json_object_set_new(ja, "id", jb))
goto err;
if (!(jb = json_string("submitblock")))
goto err;
if (json_object_set_new(ja, "method", jb))
goto err;
jb = NULL;
if (json_object_set_new(ja, "params", rv))
goto err;
return ja;
err:
json_decref(rv);
if (ja) json_decref(ja);
if (jb) json_decref(jb);
return NULL;
}

12
blkmaker_jansson.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef BLKMAKER_JANSSON_H
#define BLKMAKER_JANSSON_H
#include <jansson.h>
#include <blktemplate.h>
extern json_t *blktmpl_request_jansson(gbt_capabilities_t extracaps);
extern const char *blktmpl_add_jansson(blktemplate_t *, json_t *, time_t time_rcvd);
extern json_t *blkmk_submit_jansson(blktemplate_t *, const unsigned char *data, blknonce_t);
#endif

86
blktemplate.c Normal file
View File

@@ -0,0 +1,86 @@
#define _BSD_SOURCE
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <blktemplate.h>
static const char *capnames[] = {
"coinbasetxn",
"coinbasevalue",
"workid",
"longpoll",
"proposal",
"serverlist",
NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
"coinbase/append",
"coinbase",
"generate",
"time/increment",
"time/decrement",
"transactions/add",
"prevblock",
NULL,
"submit/hash",
"submit/coinbase",
"submit/truncate",
"share/coinbase",
"share/merkle",
"share/truncate",
};
const char *blktmpl_capabilityname(gbt_capabilities_t caps) {
for (int i = 0; i < sizeof(capnames); ++i)
if (caps & (1 << i))
return capnames[i];
return NULL;
}
blktemplate_t *blktmpl_create() {
blktemplate_t *tmpl;
tmpl = calloc(1, sizeof(*tmpl));
tmpl->maxtime = 0xffffffff;
tmpl->maxtimeoff = 0x7fff;
tmpl->mintimeoff = -0x7fff;
tmpl->maxnonce = 0xffffffff;
tmpl->expires = 0x7fff;
return tmpl;
}
gbt_capabilities_t blktmpl_addcaps(const blktemplate_t *tmpl) {
// TODO: make this a lot more flexible for merging
// For now, it's a simple "filled" vs "not filled"
if (tmpl->version)
return 0;
return GBT_CBTXN | GBT_WORKID | BMM_CBAPPEND | BMM_CBSET | BMM_TIMEINC | BMM_TIMEDEC;
}
static
void blktxn_free(struct blktxn_t *bt) {
free(bt->data);
free(bt->hash);
free(bt->depends);
}
void blktmpl_free(blktemplate_t *tmpl) {
for (int i = 0; i < tmpl->txncount; ++i)
blktxn_free(&tmpl->txns[i]);
free(tmpl->txns);
if (tmpl->cbtxn)
{
blktxn_free(tmpl->cbtxn);
free(tmpl->cbtxn);
}
// TODO: maybe free auxnames[0..n]? auxdata too
free(tmpl->auxnames);
free(tmpl->auxdata);
free(tmpl->workid);
free(tmpl);
}

117
blktemplate.h Normal file
View File

@@ -0,0 +1,117 @@
#ifndef BLKTEMPLATE_H
#define BLKTEMPLATE_H
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
typedef uint32_t blkheight_t;
typedef uint32_t libblkmaker_hash_t[8];
typedef libblkmaker_hash_t blkhash_t;
typedef libblkmaker_hash_t txnhash_t;
typedef uint32_t blktime_t;
typedef int16_t blktime_diff_t;
typedef uint32_t blknonce_t;
struct blktxn_t {
unsigned char *data;
size_t datasz;
txnhash_t *hash;
signed long dependcount;
unsigned long *depends;
uint64_t fee;
bool required;
int16_t sigops;
};
// BIP 23: Long Polling
struct blktmpl_longpoll_req {
char *id;
char *uri;
};
typedef enum {
GBT_CBTXN = 1 << 0,
GBT_CBVALUE = 1 << 1,
GBT_WORKID = 1 << 2,
GBT_LONGPOLL = 1 << 3, // BIP 22: Long Polling
GBT_PROPOSAL = 1 << 4, // BIP 23: Block Proposal
GBT_SERVICE = 1 << 5, // BIP 23: Logical Services
// BIP 23: Mutations
BMM_CBAPPEND = 1 << 0x10,
BMM_CBSET = 1 << 0x11,
BMM_GENERATE = 1 << 0x12,
BMM_TIMEINC = 1 << 0x13,
BMM_TIMEDEC = 1 << 0x14,
BMM_TXNADD = 1 << 0x15,
BMM_PREVBLK = 1 << 0x16,
// BIP 23: Submission Abbreviation
BMA_TXNHASH = 1 << 0x18,
BMAb_COINBASE = 1 << 0x19,
BMAb_TRUNCATE = 1 << 0x1a,
BMAs_COINBASE = 1 << 0x1b,
BMAs_MERKLE = 1 << 0x1c,
BMAs_TRUNCATE = 1 << 0x1d,
} gbt_capabilities_t;
#define GBT_CAPABILITY_COUNT (0x1e)
extern const char *blktmpl_capabilityname(gbt_capabilities_t);
#define BLKTMPL_LONGEST_CAPABILITY_NAME (16)
typedef gbt_capabilities_t blkmutations_t;
typedef struct {
uint32_t version;
unsigned char diffbits[4];
blkheight_t height;
blkhash_t prevblk;
unsigned short sigoplimit;
unsigned long sizelimit;
unsigned long txncount;
struct blktxn_t *txns;
struct blktxn_t *cbtxn;
uint64_t cbvalue;
time_t _time_rcvd;
blktime_t curtime;
char auxcount;
char **auxnames;
unsigned char **auxdata;
char *workid;
// BIP 22: Long Polling
struct blktmpl_longpoll_req lp;
bool submitold;
// BIP 23: Basic Pool Extensions
int16_t expires;
blkhash_t target;
// BIP 23: Mutations
blkmutations_t mutations;
blktime_t maxtime;
blktime_diff_t maxtimeoff;
blktime_t mintime;
blktime_diff_t mintimeoff;
blknonce_t minnonce;
blknonce_t maxnonce;
// TEMPORARY HACK
libblkmaker_hash_t _mrklroot;
} blktemplate_t;
extern blktemplate_t *blktmpl_create();
extern gbt_capabilities_t blktmpl_addcaps(const blktemplate_t *);
extern void blktmpl_free(blktemplate_t *);
#endif

85
example.c Normal file
View File

@@ -0,0 +1,85 @@
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <gcrypt.h>
#include <blkmaker.h>
#include <blkmaker_jansson.h>
static
void send_json(json_t *req) {
char *s = json_dumps(req, JSON_INDENT(2));
puts(s);
free(s);
}
static
bool my_sha256(void *digest, const void *buffer, size_t length) {
gcry_md_hash_buffer(GCRY_MD_SHA256, digest, buffer, length);
return true;
}
int main(int argc, char**argv) {
blktemplate_t *tmpl;
json_t *req;
json_error_t jsone;
const char *err;
blkmk_sha256_impl = my_sha256;
tmpl = blktmpl_create();
assert(tmpl);
req = blktmpl_request_jansson(blktmpl_addcaps(tmpl));
assert(req);
// send req to server and parse response into req
send_json(req);
json_decref(req);
req = json_loadf(stdin, JSON_DISABLE_EOF_CHECK, &jsone);
assert(req);
err = blktmpl_add_jansson(tmpl, req, time(NULL));
json_decref(req);
if (err)
{
fprintf(stderr, "Error adding block template: %s", err);
assert(0 && "Error adding block template");
}
while (blkmk_time_left(tmpl, time(NULL)) && blkmk_work_left(tmpl))
{
unsigned char data[80], hash[32];
size_t datasz;
uint32_t nonce;
datasz = blkmk_get_data(tmpl, data, sizeof(data), time(NULL), NULL);
assert(datasz <= sizeof(data));
// mine the right nonce
// this is iterating in native order, even though SHA256 is big endian, because we don't implement noncerange
// however, the nonce is always interpreted as big endian, so we need to convert it as if it were big endian
for (nonce = 0; nonce < 0xffffffff; ++nonce)
{
*(uint32_t*)(&data[76]) = nonce;
assert(my_sha256(hash, data, 80));
assert(my_sha256(hash, hash, 32));
if (!*(uint32_t*)(&hash[28]))
break;
if (!(nonce % 0x1000))
{
printf("0x%8" PRIx32 " hashes done...\r", nonce);
fflush(stdout);
}
}
printf("Found nonce: 0x%8" PRIx32 " \n", nonce);
nonce = ntohl(nonce);
req = blkmk_submit_jansson(tmpl, data, nonce);
assert(req);
// send req to server
send_json(req);
}
blktmpl_free(tmpl);
}