From 2035db91b62bbda72aafbab4b65500da851eaa1b Mon Sep 17 00:00:00 2001 From: kanoi Date: Thu, 26 Feb 2015 16:07:29 +1100 Subject: [PATCH 1/8] ckdb/php/sql - db storage of payout details and calculations --- pool/page_pplns2.php | 327 +++++++++++++ pool/prime.php | 1 + sql/ckdb.sql | 51 +- sql/initid.sh | 1 + sql/v0.9.6-v1.0.0.sql | 101 ++++ src/ckdb.c | 78 +++- src/ckdb.h | 125 ++++- src/ckdb_cmd.c | 366 +++++++++++---- src/ckdb_data.c | 1040 ++++++++++++++++++++++++++++++++++++++++- src/ckdb_dbio.c | 757 +++++++++++++++++++++++++----- 10 files changed, 2593 insertions(+), 254 deletions(-) create mode 100644 pool/page_pplns2.php create mode 100644 sql/v0.9.6-v1.0.0.sql diff --git a/pool/page_pplns2.php b/pool/page_pplns2.php new file mode 100644 index 00000000..01c4c4ca --- /dev/null +++ b/pool/page_pplns2.php @@ -0,0 +1,327 @@ + 99999999) + $b4 = ''; + else if ($num > 9999999) + $b4 = ''; + if ($b4 != '') + $af = ''; + return $b4.$fmt.$af; +} +# +# ... Of course ... check the output and add the txin ... etc. +function calctx($ans, $count, $miner_sat, $diffacc_total) +{ + $pg = '
'; + $pg .= '
'; + + $dust = getparam('dust', true); + if (nuem($dust) || $dust <= 0) + $dust = 10000; + + $fee = getparam('fee', true); + if (nuem($fee) || $fee < 0) + $fee = 0; + $fee *= 100000000; + + $adr = array(); + $ers = ''; + $unpaid = 0; + $change = $miner_sat; + $dust_amt = 0; # not included in $change + for ($i = 0; $i < $count; $i++) + { + $username = $ans['user:'.$i]; + $diffacc_user = $ans['diffacc:'.$i]; + $pay_sat = $ans['amount:'.$i]; + $payaddress = $ans['payaddress:'.$i]; + if ($payaddress == 'none') + { + $c0 = substr($username, 0, 1); + $parts = explode('.', $username); + $len = strlen($parts[0]); + if (($c0 == '1' || $c0 == '3') && $len > 26 && $len < 37) + $payaddress = $parts[0]; + else + { + if ($pay_sat > 0) + { + $dd = ''; + if ($pay_sat < $dust) + $dd = ' (dust)'; + $ers .= "No address for '$username'$dd
"; + } + $unpaid += $pay_sat; + continue; + } + } + if (isset($adr[$payaddress])) + $adr[$payaddress] += $pay_sat; + else + $adr[$payaddress] = $pay_sat; + + $change -= $pay_sat; + } + + $txout = ''; + $comma = ''; + foreach ($adr as $payaddress => $pay_sat) + { + if ($pay_sat < $dust) + $dust_amt += $pay_sat; + else + { + $txout .= "$comma\"$payaddress\":".btcfmt($pay_sat); + $comma = ', '; + } + } + + if ($change > 0 || $dust_amt > 0 || $change < $fee) + { + $pg .= "Dust limit = $dust = ".btcfmt($dust); + $pg .= ", Dust amount = $dust_amt = ".btcfmt($dust_amt); + $pg .= ",
Upaid = $unpaid = ".btcfmt($unpaid); + $pg .= ", Change = $change = ".btcfmt($change); + $pg .= ",
Fee = $fee = ".btcfmt($fee)."

"; + + if ($change < $fee) + $ers .= "Change ($change) is less than Fee ($fee)
"; + + if (($dust_amt + $change - $fee) > 0) + { + $txout .= "$comma\"<changeaddress>\":"; + $txout .= btcfmt($dust_amt + $change - $fee); + $comma = ', '; + } + } + + if (strlen($ers) > 0) + $pg .= "$ers
"; + + $txn = '[{"txid":"<txid1>","vout":<n>},'; + $txn .= '{"txid":"<txid2>","vout":<n>}] '; + $txn .= '{'.$txout.'}
'; + + $pg .= $txn.'
'; + return $pg; +} +# +function fmtdata($code, $val) +{ + switch ($code) + { + case ',': + $ret = number_format($val); + break; + case '.': + $ret = number_format($val, 1); + break; + default: + $ret = $val; + } + return $ret; +} +# +function dopplns2($data, $user) +{ + global $send_sep; + + $pg = '

CKPool

'; + + $blk = getparam('blk', true); + if (nuem($blk)) + { + $tx = ''; + # so can make a link + $blkuse = getparam('blkuse', true); + if (nuem($blkuse)) + $blkuse = ''; + else + $tx = 'y'; + $pg = '
'.makeForm('pplns2')." +Block: +  Tx: +  Dust (Satoshi): +  Fee (BTC): +"; + } + else + { + $tx = getparam('tx', true); + if (nuem($tx) || substr($tx, 0, 1) != 'y') + $dotx = false; + else + $dotx = true; + + $flds = array('height' => $blk); + $msg = msgEncode('pplns2', 'pplns2', $flds, $user); + $rep = sendsockreply('pplns2', $msg, 4); + if ($rep == false) + $ans = array(); + else + $ans = repDecode($rep); + + + if ($ans['ERROR'] != null) + return '
'.$ans['STATUS'].': '.$ans['ERROR'].'
'; + + if (!isset($ans['pplns_last'])) + return '
Partial data returned
'; + + $reward_sat = $ans['block_reward']; + $miner_sat = $ans['miner_reward']; + $ans['miner_sat'] = $miner_sat; + + $data = array( 'Block' => 'block', + 'Block Status' => 'block_status', + 'Block Hash' => 'block_hash', + 'Block Reward (Satoshis)' => 'block_reward', + 'Miner Reward (Satoshis)' => 'miner_sat', + 'PPLNS Wanted' => '.diff_want', + 'PPLNS Used' => '.diffacc_total', + 'Elapsed Seconds' => ',pplns_elapsed', + 'Users' => 'rows', + 'Oldest Workinfoid' => 'begin_workinfoid', +# 'Oldest Time' => 'begin_stamp', +# 'Oldest Epoch' => 'begin_epoch', + 'Block Workinfoid' => 'block_workinfoid', +# 'Block Time' => 'block_stamp', +# 'Block Epoch' => 'block_epoch', + 'Newest Workinfoid' => 'end_workinfoid', +# 'Newest Share Time' => 'end_stamp', +# 'Newest Share Epoch' => 'end_epoch', + 'Network Difficulty' => 'block_ndiff', + 'PPLNS Factor' => 'diff_times', + 'PPLNS Added' => 'diff_add', + 'Accepted Share Count' => ',acc_share_count', + 'Total Share Count' => ',total_share_count', + 'ShareSummary Count' => ',ss_count', + 'WorkMarkers Count' => ',wm_count', + 'MarkerSummary Count' => ',ms_count'); + + $pg = '
Blockchain '.$ans['block']."
\n"; + + if (strlen($ans['marks_status']) > 0) + { + $pg .= '
'; + $msg = $ans['marks_status']; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + if (strlen($ans['block_extra']) > 0) + { + $pg .= '
'; + $msg = $ans['block_status'].' - '.$ans['block_extra']; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + if (strlen($ans['share_status']) > 0) + { + $pg .= '
'; + $msg = $ans['share_status']." - Can't be paid out yet"; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + $pg .= "
\n"; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + $i = 0; + foreach ($data as $dsp => $name) + { + if (($i++ % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ""; + switch ($name[0]) + { + case ',': + case '.': + $nm = substr($name, 1); + $fmt = fmtdata($name[0], $ans[$nm]); + break; + default: + $fmt = $ans[$name]; + break; + } + $pg .= ""; + $pg .= "\n"; + } + + $pg .= "
NameValue
$dsp$fmt

\n"; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + + $diffacc_total = $ans['diffacc_total']; + if ($diffacc_total == 0) + $diffacc_total = pow(10,15); + $elapsed = $ans['pplns_elapsed']; + $count = $ans['rows']; + $tot_pay = 0; + for ($i = 0; $i < $count; $i++) + { + $diffacc_user = $ans['diffacc:'.$i]; + $diffacc_percent = number_format(100.0 * $diffacc_user / $diffacc_total, 3).'%'; + $avg_hash = number_format($diffacc_user / $elapsed * pow(2,32), 0); + $pay_sat = $ans['amount:'.$i]; + $payaddress = $ans['payaddress:'.$i]; + + if (($i % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ''; + $pg .= ""; + $pg .= ""; + $pg .= ""; + $pg .= ''; + $pg .= ""; + $pg .= "\n"; + + $tot_pay += $pay_sat; + } + if (($i % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + $pg .= "
UserDiff Accepted%Avg HashrateBTC -0.9%Address
'.$ans['user:'.$i].'$diffacc_user$diffacc_percent$avg_hash'.btcfmt($pay_sat).'$payaddress
'.btcfmt($tot_pay).'
\n"; + + if ($dotx === true) + $pg .= calctx($ans, $count, $miner_sat, $diffacc_total); + } + + return $pg; +} +# +function show_pplns2($info, $page, $menu, $name, $user) +{ + gopage($info, NULL, 'dopplns2', $page, $menu, $name, $user); +} +# +?> diff --git a/pool/prime.php b/pool/prime.php index ebecb23e..0cfe79af 100644 --- a/pool/prime.php +++ b/pool/prime.php @@ -19,6 +19,7 @@ function process($p, $user, $menu) if ($user == 'Kano' || $user == 'ckolivas') { $menu['Admin']['ckp'] = 'ckp'; + $menu['Admin']['PPLNS2'] = 'pplns2'; $menu['Admin']['PPLNS'] = 'pplns'; $menu['Admin']['AllWork'] = 'allwork'; } diff --git a/sql/ckdb.sql b/sql/ckdb.sql index 441fbc90..a14896e9 100644 --- a/sql/ckdb.sql +++ b/sql/ckdb.sql @@ -84,11 +84,14 @@ CREATE UNIQUE INDEX payadduserid ON paymentaddresses USING btree (userid, payadd CREATE TABLE payments ( paymentid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, userid bigint NOT NULL, + subname character varying(256) NOT NULL, paydate timestamp with time zone NOT NULL, payaddress character varying(256) DEFAULT ''::character varying NOT NULL, originaltxn character varying(256) DEFAULT ''::character varying NOT NULL, amount bigint NOT NULL, -- satoshis + diffacc float DEFAULT 0 NOT NULL, committxn character varying(256) DEFAULT ''::character varying NOT NULL, commitblockhash character varying(256) DEFAULT ''::character varying NOT NULL, createdate timestamp with time zone NOT NULL, @@ -98,10 +101,10 @@ CREATE TABLE payments ( expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', PRIMARY KEY (paymentid, expirydate) ); -CREATE UNIQUE INDEX payuserid ON payments USING btree (userid, payaddress, originaltxn, expirydate); +CREATE UNIQUE INDEX payuserid ON payments USING btree (payoutid, userid, subname, payaddress, originaltxn, expirydate); -CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments +CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments - RAM only userid bigint NOT NULL, confirmedpaid bigint DEFAULT 0 NOT NULL, -- satoshis confirmedunpaid bigint DEFAULT 0 NOT NULL, -- satoshis @@ -111,8 +114,11 @@ CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments createby character varying(64) DEFAULT ''::character varying NOT NULL, createcode character varying(128) DEFAULT ''::character varying NOT NULL, createinet character varying(128) DEFAULT ''::character varying NOT NULL, - expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', - PRIMARY KEY (userid, expirydate) + modifydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + modifyby character varying(64) DEFAULT ''::character varying NOT NULL, + modifycode character varying(128) DEFAULT ''::character varying NOT NULL, + modifyinet character varying(128) DEFAULT ''::character varying NOT NULL, + PRIMARY KEY (userid) ); @@ -120,6 +126,7 @@ CREATE TABLE accountadjustment ( -- manual corrections userid bigint NOT NULL, authority character varying(256) NOT NULL, reason text NOT NULL, + message character varying(256) NOT NULL, amount bigint DEFAULT 0 NOT NULL, -- satoshis createdate timestamp with time zone NOT NULL, createby character varying(64) DEFAULT ''::character varying NOT NULL, @@ -341,22 +348,42 @@ CREATE TABLE blocks ( ); --- calculation for the given block - orphans will be here also (not deleted later) --- rules for orphans/next block will be pool dependent CREATE TABLE miningpayouts ( - miningpayoutid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, userid bigint NOT NULL, + diffacc float DEFAULT 0 NOT NULL, + amount bigint DEFAULT 0 NOT NULL, -- satoshis + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, userid, expirydate) +); + + +CREATE TABLE payouts ( + payoutid bigint NOT NULL, -- unique per record height integer not NULL, - blockhash character varying(256) DEFAULT ''::character varying NOT NULL, - amount bigint DEFAULT 0 NOT NULL, + blockhash character varying(256) NOT NULL, + minerreward bigint NOT NULL, -- satoshis + workinfoidstart bigint NOT NULL, + workinfoidend bigint NOT NULL, -- should be block workinfoid + elapsed bigint NOT NULL, + status char DEFAULT ' ' NOT NULL, + diffwanted float DEFAULT 0 NOT NULL, + diffused float DEFAULT 0 NOT NULL, + shareacc float DEFAULT 0 NOT NULL, + lastshareacc timestamp with time zone NOT NULL, + stats text DEFAULT ''::text NOT NULL, createdate timestamp with time zone NOT NULL, createby character varying(64) DEFAULT ''::character varying NOT NULL, createcode character varying(128) DEFAULT ''::character varying NOT NULL, createinet character varying(128) DEFAULT ''::character varying NOT NULL, expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', - PRIMARY KEY (miningpayoutid, expirydate) + PRIMARY KEY (payoutid, expirydate) ); -CREATE UNIQUE INDEX minpayuserid ON miningpayouts USING btree (userid, blockhash, expirydate); +CREATE UNIQUE INDEX payoutsblock ON payouts USING btree (height, blockhash, expirydate); CREATE TABLE eventlog ( @@ -433,4 +460,4 @@ CREATE TABLE version ( PRIMARY KEY (vlock) ); -insert into version (vlock,version) values (1,'0.9.6'); +insert into version (vlock,version) values (1,'1.0.0'); diff --git a/sql/initid.sh b/sql/initid.sh index 31c119d9..278559a8 100755 --- a/sql/initid.sh +++ b/sql/initid.sh @@ -20,3 +20,4 @@ addid authid ${now}300000 addid userid ${now}400000 addid markerid ${now}500000 addid paymentaddressid ${now}600000 +addid payoutid ${now}700000 diff --git a/sql/v0.9.6-v1.0.0.sql b/sql/v0.9.6-v1.0.0.sql new file mode 100644 index 00000000..35b780dc --- /dev/null +++ b/sql/v0.9.6-v1.0.0.sql @@ -0,0 +1,101 @@ +SET SESSION AUTHORIZATION 'postgres'; + +BEGIN transaction; + +DO $$ +DECLARE ver TEXT; +BEGIN + + UPDATE version set version='1.0.0' where vlock=1 and version='0.9.6'; + + IF found THEN + RETURN; + END IF; + + SELECT version into ver from version + WHERE vlock=1; + + RAISE EXCEPTION 'Wrong DB version - expect "0.9.6" - found "%"', ver; + +END $$; + +DROP TABLE payments; +CREATE TABLE payments ( + paymentid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, + userid bigint NOT NULL, + subname character varying(256) NOT NULL, + paydate timestamp with time zone NOT NULL, + payaddress character varying(256) DEFAULT ''::character varying NOT NULL, + originaltxn character varying(256) DEFAULT ''::character varying NOT NULL, + amount bigint NOT NULL, -- satoshis + diffacc float DEFAULT 0 NOT NULL, + committxn character varying(256) DEFAULT ''::character varying NOT NULL, + commitblockhash character varying(256) DEFAULT ''::character varying NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (paymentid, expirydate) +); +CREATE UNIQUE INDEX payuserid ON payments USING btree (payoutid, userid, subname, payaddress, originaltxn, expirydate); + +DROP TABLE accountbalance; +CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments - RAM only + userid bigint NOT NULL, + confirmedpaid bigint DEFAULT 0 NOT NULL, -- satoshis + confirmedunpaid bigint DEFAULT 0 NOT NULL, -- satoshis + pendingconfirm bigint DEFAULT 0 NOT NULL, -- satoshis + heightupdate integer not NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + modifydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + modifyby character varying(64) DEFAULT ''::character varying NOT NULL, + modifycode character varying(128) DEFAULT ''::character varying NOT NULL, + modifyinet character varying(128) DEFAULT ''::character varying NOT NULL, + PRIMARY KEY (userid) +); + +DROP TABLE miningpayouts; +CREATE TABLE miningpayouts ( + payoutid bigint NOT NULL, + userid bigint NOT NULL, + diffacc float DEFAULT 0 NOT NULL, + amount bigint DEFAULT 0 NOT NULL, -- satoshis + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, userid, expirydate) +); + +CREATE TABLE payouts ( + payoutid bigint NOT NULL, -- unique per record + height integer not NULL, + blockhash character varying(256) NOT NULL, + minerreward bigint NOT NULL, -- satoshis + workinfoidstart bigint NOT NULL, + workinfoidend bigint NOT NULL, -- should be block workinfoid + elapsed bigint NOT NULL, + status char DEFAULT ' ' NOT NULL, + diffwanted float DEFAULT 0 NOT NULL, + diffused float DEFAULT 0 NOT NULL, + shareacc float DEFAULT 0 NOT NULL, + lastshareacc timestamp with time zone NOT NULL, + stats text DEFAULT ''::text NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, expirydate) +); +CREATE UNIQUE INDEX payoutsblock ON payouts USING btree (height, blockhash, expirydate); + +insert into idcontrol (idname,lastid,createdate,createby) values ('payoutid',999,now(),'1.0.0update'); + +END transaction; diff --git a/src/ckdb.c b/src/ckdb.c index ca6f1e33..824aef28 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -27,7 +27,7 @@ * and ckpool only verifies authorise responses * Thus we can queue all messages: * workinfo, shares, shareerror, ageworkinfo, poolstats, userstats - * and block + * and blocks * with an ok.queued reply to ckpool, to be processed after the reload * completes and just process authorise messages immediately while the * reload runs @@ -61,7 +61,7 @@ * it would break the synchronisation and could cause DB problems, so * ckdb aborting and needing a complete restart resolves it * The users table, required for the authorise messages, is always updated - * immediately and is not affected by ckpool messages until we + * immediately */ /* Reload data needed @@ -92,17 +92,17 @@ * already exist * DB+RAM blocks: resolved by workinfo - any unsaved blocks (if any) * will be after the last DB workinfo - * DB+RAM accountbalance (TODO): resolved by shares/workinfo/blocks * RAM workerstatus: all except last_idle are set at the end of the * CCL reload * Code currently doesn't use last_idle + * RAM accountbalance: TODO: created as data is loaded * * idcontrol: only userid reuse is critical and the user is added * immeditately to the DB before replying to the add message * * Tables that are/will be written straight to the DB, so are OK: * users, useraccounts, paymentaddresses, payments, - * accountadjustment, optioncontrol, miningpayouts, + * accountadjustment, optioncontrol, miningpayouts, payouts, * eventlog, workmarkers, markersummary * * The code deals with the issue of 'now' when reloading by: @@ -329,6 +329,7 @@ K_STORE *workers_store; // PAYMENTADDRESSES K_TREE *paymentaddresses_root; +K_TREE *paymentaddresses_create_root; K_LIST *paymentaddresses_free; K_STORE *paymentaddresses_store; @@ -337,7 +338,6 @@ K_TREE *payments_root; K_LIST *payments_free; K_STORE *payments_store; -/* unused yet // ACCOUNTBALANCE K_TREE *accountbalance_root; K_LIST *accountbalance_free; @@ -347,7 +347,6 @@ K_STORE *accountbalance_store; K_TREE *accountadjustment_root; K_LIST *accountadjustment_free; K_STORE *accountadjustment_store; -*/ // IDCONTROL // These are only used for db access - not stored in memory @@ -405,6 +404,13 @@ K_TREE *miningpayouts_root; K_LIST *miningpayouts_free; K_STORE *miningpayouts_store; +// PAYOUTS +K_TREE *payouts_root; +K_TREE *payouts_id_root; +K_LIST *payouts_free; +K_STORE *payouts_store; +cklock_t process_pplns_lock; + /* // EVENTLOG K_TREE *eventlog_root; @@ -742,6 +748,10 @@ static bool getdata3() goto sukamudai; if (!(ok = payments_fill(conn)) || everyone_die) goto sukamudai; + if (!(ok = miningpayouts_fill(conn)) || everyone_die) + goto sukamudai; + if (!(ok = payouts_fill(conn)) || everyone_die) + goto sukamudai; } if (!(ok = workinfo_fill(conn)) || everyone_die) goto sukamudai; @@ -953,6 +963,7 @@ static void alloc_storage() LIMIT_PAYMENTADDRESSES, true); paymentaddresses_store = k_new_store(paymentaddresses_free); paymentaddresses_root = new_ktree(); + paymentaddresses_create_root = new_ktree(); paymentaddresses_free->dsp_func = dsp_paymentaddresses; payments_free = k_new_list("Payments", sizeof(PAYMENTS), @@ -960,6 +971,11 @@ static void alloc_storage() payments_store = k_new_store(payments_free); payments_root = new_ktree(); + accountbalance_free = k_new_list("AccountBalance", sizeof(ACCOUNTBALANCE), + ALLOC_ACCOUNTBALANCE, LIMIT_ACCOUNTBALANCE, true); + accountbalance_store = k_new_store(accountbalance_free); + accountbalance_root = new_ktree(); + idcontrol_free = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); idcontrol_store = k_new_store(idcontrol_free); @@ -999,6 +1015,12 @@ static void alloc_storage() miningpayouts_store = k_new_store(miningpayouts_free); miningpayouts_root = new_ktree(); + payouts_free = k_new_list("Payouts", sizeof(PAYOUTS), + ALLOC_PAYOUTS, LIMIT_PAYOUTS, true); + payouts_store = k_new_store(payouts_free); + payouts_root = new_ktree(); + payouts_id_root = new_ktree(); + auths_free = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); auths_store = k_new_store(auths_free); @@ -1110,6 +1132,10 @@ static void dealloc_storage() FREE_ALL(poolstats); FREE_ALL(auths); + + FREE_TREE(payouts_id); + FREE_ALL(payouts); + FREE_ALL(miningpayouts); FREE_ALL(blocks); @@ -1131,7 +1157,10 @@ static void dealloc_storage() FREE_LIST_DATA(workinfo); FREE_LISTS(idcontrol); + FREE_ALL(accountbalance); FREE_ALL(payments); + + FREE_TREE(paymentaddresses_create); FREE_ALL(paymentaddresses); FREE_ALL(workers); @@ -1534,6 +1563,20 @@ static void check_blocks() btc_blockstatus(blocks); } +static void pplns_block(BLOCKS *blocks) +{ + if (sharesummary_marks_limit) { + LOGEMERG("%s() sharesummary marks limit, block %"PRId32" payout skipped", + __func__, blocks->height); + return; + } + + // Give it a sec after the block summarisation + sleep(1); + + process_pplns(blocks->height, blocks->blockhash, NULL); +} + static void summarise_blocks() { K_ITEM *b_item, *b_prev, *wi_item, ss_look, *ss_item; @@ -1733,17 +1776,15 @@ static void summarise_blocks() "%0.f/%.0f/%.0f/%.0f/%"PRId64, __func__, blocks->height, diffacc, diffinv, shareacc, shareinv, elapsed); + + // Now the summarisation is confirmed, generate the payout data + pplns_block(blocks); } else { LOGERR("%s() block %d, failed to confirm stats", __func__, blocks->height); } } -static void summarise_poolstats() -{ -// TODO -} - static void *summariser(__maybe_unused void *arg) { int i; @@ -1752,7 +1793,7 @@ static void *summariser(__maybe_unused void *arg) rename_proc("db_summariser"); - while (!everyone_die && !db_load_complete) + while (!everyone_die && !startup_complete) cksleep_ms(42); summariser_using_data = true; @@ -1764,12 +1805,8 @@ static void *summariser(__maybe_unused void *arg) } if (everyone_die) break; - else { - if (startup_complete) - check_blocks(); - if (!everyone_die) - summarise_blocks(); - } + else + check_blocks(); for (i = 0; i < 4; i++) { if (!everyone_die) @@ -1778,7 +1815,7 @@ static void *summariser(__maybe_unused void *arg) if (everyone_die) break; else - summarise_poolstats(); + summarise_blocks(); for (i = 0; i < 4; i++) { if (!everyone_die) @@ -2624,6 +2661,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_WORKERS: case CMD_PAYMENTS: case CMD_PPLNS: + case CMD_PPLNS2: case CMD_DSP: case CMD_BLOCKSTATUS: if (!startup_complete) { @@ -2841,6 +2879,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_DSP: case CMD_STATS: case CMD_PPLNS: + case CMD_PPLNS2: case CMD_USERSTATUS: case CMD_MARKS: LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored", @@ -4062,6 +4101,7 @@ int main(int argc, char **argv) ckp.main.processname = strdup("main"); cklock_init(&last_lock); + cklock_init(&process_pplns_lock); if (confirm_sharesummary) { // TODO: add a system lock to stop running 2 at once? diff --git a/src/ckdb.h b/src/ckdb.h index e8ecd940..9a48d42a 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -51,8 +51,8 @@ */ #define DB_VLOCK "1" -#define DB_VERSION "0.9.6" -#define CKDB_VERSION DB_VERSION"-0.920" +#define DB_VERSION "1.0.0" +#define CKDB_VERSION DB_VERSION"-1.000" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -94,6 +94,9 @@ extern const char *addrpatt; // BTC address size #define ADDR_MIN_LEN 26 #define ADDR_MAX_LEN 34 +/* All characters in a payaddress are less than this + * thus setting the 1st char to this will be greater than any payaddress */ +#define MAX_PAYADDR '~' typedef struct loadstatus { tv_t oldest_sharesummary_firstshare_n; @@ -336,6 +339,8 @@ enum cmd_values { CMD_DSP, CMD_STATS, CMD_PPLNS, + CMD_PPLNS2, + CMD_PAYOUT, CMD_USERSTATUS, CMD_MARKS, CMD_END @@ -816,6 +821,7 @@ typedef struct paymentaddresses { #define DATA_PAYMENTADDRESSES_NULL(_var, _item) DATA_GENERIC(_var, _item, paymentaddresses, false) extern K_TREE *paymentaddresses_root; +extern K_TREE *paymentaddresses_create_root; extern K_LIST *paymentaddresses_free; extern K_STORE *paymentaddresses_store; @@ -824,14 +830,18 @@ extern K_STORE *paymentaddresses_store; // PAYMENTS typedef struct payments { int64_t paymentid; + int64_t payoutid; int64_t userid; + char subname[TXT_BIG+1]; tv_t paydate; char payaddress[TXT_BIG+1]; char originaltxn[TXT_BIG+1]; int64_t amount; + double diffacc; char committxn[TXT_BIG+1]; char commitblockhash[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; + K_ITEM *old_item; // Non-db field } PAYMENTS; #define ALLOC_PAYMENTS 1024 @@ -844,7 +854,6 @@ extern K_TREE *payments_root; extern K_LIST *payments_free; extern K_STORE *payments_store; -/* unused yet // ACCOUNTBALANCE typedef struct accountbalance { int64_t userid; @@ -852,7 +861,7 @@ typedef struct accountbalance { int64_t confirmedunpaid; int64_t pendingconfirm; int32_t heightupdate; - HISTORYDATECONTROLFIELDS; + MODIFYDATECONTROLFIELDS; } ACCOUNTBALANCE; #define ALLOC_ACCOUNTBALANCE 1024 @@ -881,7 +890,6 @@ typedef struct accountadjustment { extern K_TREE *accountadjustment_root; extern K_LIST *accountadjustment_free; extern K_STORE *accountadjustment_store; -*/ // IDCONTROL typedef struct idcontrol { @@ -1126,23 +1134,62 @@ extern K_STORE *blocks_store; // MININGPAYOUTS typedef struct miningpayouts { - int64_t miningpayoutid; + int64_t payoutid; int64_t userid; - int32_t height; - char blockhash[TXT_BIG+1]; + double diffacc; int64_t amount; HISTORYDATECONTROLFIELDS; + K_ITEM *old_item; // Non-db field } MININGPAYOUTS; #define ALLOC_MININGPAYOUTS 1000 #define LIMIT_MININGPAYOUTS 0 #define INIT_MININGPAYOUTS(_item) INIT_GENERIC(_item, miningpayouts) #define DATA_MININGPAYOUTS(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, true) +#define DATA_MININGPAYOUTS_NULL(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, false) extern K_TREE *miningpayouts_root; extern K_LIST *miningpayouts_free; extern K_STORE *miningpayouts_store; +// PAYOUTS +typedef struct payouts { + int64_t payoutid; + int32_t height; + char blockhash[TXT_BIG+1]; + int64_t minerreward; + int64_t workinfoidstart; + int64_t workinfoidend; + int64_t elapsed; + char status[TXT_FLAG+1]; + double diffwanted; + double diffused; + double shareacc; + tv_t lastshareacc; + char *stats; + HISTORYDATECONTROLFIELDS; +} PAYOUTS; + +#define ALLOC_PAYOUTS 1000 +#define LIMIT_PAYOUTS 0 +#define INIT_PAYOUTS(_item) INIT_GENERIC(_item, payouts) +#define DATA_PAYOUTS(_var, _item) DATA_GENERIC(_var, _item, payouts, true) +#define DATA_PAYOUTS_NULL(_var, _item) DATA_GENERIC(_var, _item, payouts, false) + +extern K_TREE *payouts_root; +extern K_TREE *payouts_id_root; +extern K_LIST *payouts_free; +extern K_STORE *payouts_store; +extern cklock_t process_pplns_lock; + +#define PAYOUTS_GENERATED 'G' +#define PAYOUTS_GENERATED_STR "G" +#define PAYGENERATED(_status) ((_status)[0] == PAYOUTS_GENERATED) +// A processing payout must be ignored +#define PAYOUTS_PROCESSING 'P' +#define PAYOUTS_PROCESSING_STR "P" +#define PAYPROCESSING(_status) ((_status)[0] == PAYOUTS_PROCESSING) + /* // EVENTLOG typedef struct eventlog { @@ -1527,6 +1574,20 @@ extern PGconn *dbconnect(); // *** ckdb_data.c *** // *** +/* Blocks after 334106 were set to 5xN + * however, they cannot count back to include the workinfoid of 333809 + * due to the markersummaries that were created. + * Code checks that if the block is after FIVExSTT then it must stop + * counting back shares at - and not include - FIVExWID */ +#define FIVExSTT 334106 +#define FIVExLIM 333809 +// 333809 workinfoid +#define FIVExWID 6085620100361140756 + +// optioncontrol names for PPLNS N diff calculation +#define PPLNSDIFFTIMES "pplns_diff_times" +#define PPLNSDIFFADD "pplns_diff_add" + // Data free functions (first) extern void free_workinfo_data(K_ITEM *item); extern void free_sharesummary_data(K_ITEM *item); @@ -1635,12 +1696,19 @@ extern K_ITEM *new_default_worker(PGconn *conn, bool update, int64_t userid, cha char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern void dsp_paymentaddresses(K_ITEM *item, FILE *stream); extern cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_payaddr_create(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_paymentaddresses(int64_t userid, K_TREE_CTX *ctx); +extern K_ITEM *find_paymentaddresses_create(int64_t userid, K_TREE_CTX *ctx); extern K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx); extern K_ITEM *find_any_payaddress(char *payaddress); extern cmp_t cmp_payments(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_payments(int64_t payoutid, int64_t userid, char *subname); +extern K_ITEM *find_first_payments(int64_t userid, K_TREE_CTX *ctx); +extern K_ITEM *find_first_paypayid(int64_t userid, int64_t payoutid, K_TREE_CTX *ctx); +extern cmp_t cmp_accountbalance(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_accountbalance(int64_t userid); extern cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b); -extern K_ITEM *find_optioncontrol(char *optionname, tv_t *now); +extern K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height); extern cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b); #define coinbase1height(_cb1) _coinbase1height(_cb1, WHERE_FFL_HERE) extern int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS); @@ -1670,12 +1738,23 @@ void _dbhash2btchash(char *hash, char *buf, size_t siz, WHERE_FFL_ARGS); extern void _dsp_hash(char *hash, char *buf, size_t siz, WHERE_FFL_ARGS); extern void dsp_blocks(K_ITEM *item, FILE *stream); extern cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b); -extern K_ITEM *find_blocks(int32_t height, char *blockhash); +extern K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx); extern K_ITEM *find_prev_blocks(int32_t height); extern const char *blocks_confirmed(char *confirmed); extern void zero_on_new_block(); extern void set_block_share_counters(); extern cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_miningpayouts(int64_t payoutid, int64_t userid); +extern K_ITEM *first_miningpayouts(int64_t payoutid, K_TREE_CTX *ctx); +extern cmp_t cmp_mu(K_ITEM *a, K_ITEM *b); +extern K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, + double diffacc); +extern cmp_t cmp_payouts(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_payouts(int32_t height, char *blockhash); +extern K_ITEM *find_last_payouts(); +extern K_ITEM *find_payoutid(int64_t payoutid); +extern void process_pplns(int32_t height, char *blockhash, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b); extern void dsp_userstats(K_ITEM *item, FILE *stream); @@ -1751,6 +1830,12 @@ extern PGresult *_CKPQexecParams(PGconn *conn, const char *qry, #define PGLOGEMERG(_str, _rescode, _conn) PGLOG(LOGEMERG, _str, _rescode, _conn) extern char *pqerrmsg(PGconn *conn); +extern bool CKPQConn(PGconn **conn); +extern void CKPQDisco(PGconn **conn, bool conned); +extern bool _CKPQBegin(PGconn *conn, WHERE_FFL_ARGS); +#define CKPQBegin(_conn) _CKPQBegin(conn, WHERE_FFL_HERE) +extern void _CKPQEnd(PGconn *conn, bool commit, WHERE_FFL_ARGS); +#define CKPQEnd(_conn, _commit) _CKPQEnd(_conn, _commit, WHERE_FFL_HERE) extern int64_t nextid(PGconn *conn, char *idname, int64_t increment, tv_t *cd, char *by, char *code, char *inet); @@ -1783,6 +1868,11 @@ extern bool paymentaddresses_set(PGconn *conn, int64_t userid, K_LIST *pa_store, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern bool paymentaddresses_fill(PGconn *conn); +extern void payments_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, + tv_t *cd); +extern bool payments_add(PGconn *conn, bool add, K_ITEM *p_item, + K_ITEM **old_p_item, char *by, char *code, char *inet, + tv_t *cd, K_TREE *trf_root, bool already); extern bool payments_fill(PGconn *conn); extern bool idcontrol_add(PGconn *conn, char *idname, char *idvalue, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); @@ -1827,10 +1917,19 @@ extern bool blocks_add(PGconn *conn, char *height, char *blockhash, char *by, char *code, char *inet, tv_t *cd, bool igndup, char *id, K_TREE *trf_root); extern bool blocks_fill(PGconn *conn); -extern bool miningpayouts_add(PGconn *conn, char *username, char *height, - char *blockhash, char *amount, char *by, - char *code, char *inet, tv_t *cd, K_TREE *trf_root); +extern void miningpayouts_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, + tv_t *cd); +extern bool miningpayouts_add(PGconn *conn, bool add, K_ITEM *mp_item, + K_ITEM **old_mp_item, char *by, char *code, + char *inet, tv_t *cd, K_TREE *trf_root, + bool already); extern bool miningpayouts_fill(PGconn *conn); +extern void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, + tv_t *cd); +extern bool payouts_add(PGconn *conn, bool add, K_ITEM *p_item, + K_ITEM **old_p_item, char *by, char *code, char *inet, + tv_t *cd, K_TREE *trf_root, bool already); +extern bool payouts_fill(PGconn *conn); extern bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, char *useragent, char *preauth, char *by, char *code, diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index cdfb2e0b..5d1bcba0 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -971,7 +971,7 @@ static char *cmd_blockstatus(__maybe_unused PGconn *conn, char *cmd, char *id, action = transfer_data(i_action); K_RLOCK(blocks_free); - b_item = find_blocks(height, transfer_data(i_blockhash)); + b_item = find_blocks(height, transfer_data(i_blockhash), NULL); K_RUNLOCK(blocks_free); if (!b_item) { @@ -1092,9 +1092,9 @@ static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { - K_ITEM *i_username, look, *u_item, *p_item; + K_ITEM *i_username, *u_item, *p_item; K_TREE_CTX ctx[1]; - PAYMENTS lookpayments, *payments; + PAYMENTS *payments, curr; USERS *users; char reply[1024] = ""; char tmp[1024]; @@ -1116,33 +1116,62 @@ static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, return strdup("bad"); DATA_USERS(users, u_item); - lookpayments.userid = users->userid; - lookpayments.paydate.tv_sec = 0; - lookpayments.paydate.tv_usec = 0; - INIT_PAYMENTS(&look); - look.data = (void *)(&lookpayments); - p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); - DATA_PAYMENTS_NULL(payments, p_item); + bzero(&curr, sizeof(curr)); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; + + K_RLOCK(payments_free); + p_item = find_first_payments(users->userid, ctx); + DATA_PAYMENTS_NULL(payments, p_item); + /* TODO: allow to see details of a single payoutid + * if it has multiple items (percent payout user) */ while (p_item && payments->userid == users->userid) { - tv_to_buf(&(payments->paydate), reply, sizeof(reply)); + if (CURRENT(&(payments->expirydate))) { + if (curr.payoutid && curr.payoutid != payments->payoutid) { + tv_to_buf(&(curr.paydate), reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + str_to_buf(curr.payaddress, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + bigint_to_buf(curr.amount, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + rows++; + bzero(&curr, sizeof(curr)); + } + if (!curr.payoutid) { + curr.payoutid = payments->payoutid; + copy_tv(&(curr.paydate), &(payments->paydate)); + STRNCPY(curr.payaddress, payments->payaddress); + } else + STRNCPY(curr.payaddress, "*Multiple"); + curr.amount += payments->amount; + } + p_item = next_in_ktree(ctx); + DATA_PAYMENTS_NULL(payments, p_item); + } + K_RUNLOCK(payments_free); + if (curr.payoutid) { + tv_to_buf(&(curr.paydate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - str_to_buf(payments->payaddress, reply, sizeof(reply)); + str_to_buf(curr.payaddress, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - bigint_to_buf(payments->amount, reply, sizeof(reply)); + bigint_to_buf(curr.amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; - p_item = next_in_ktree(ctx); - DATA_PAYMENTS_NULL(payments, p_item); } + snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "paydate,payaddress,amount", FLDSEP); @@ -2220,7 +2249,7 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, i_preauth = &auth_preauth; K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd); + oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd, pool.height); K_RUNLOCK(optioncontrol_free); if (oc_item) { K_RLOCK(users_free); @@ -3112,7 +3141,7 @@ static char *cmd_getopts(__maybe_unused PGconn *conn, char *cmd, char *id, if (comma) *(comma++) = '\0'; K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(ptr, now); + oc_item = find_optioncontrol(ptr, now, pool.height); K_RUNLOCK(optioncontrol_free); /* web code must check the existance of the optionname * in the reply since it will be missing if it doesn't @@ -3305,81 +3334,12 @@ rollback: return strdup(reply); } -// order by userid asc -static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) -{ - MININGPAYOUTS *ma, *mb; - DATA_MININGPAYOUTS(ma, a); - DATA_MININGPAYOUTS(mb, b); - return CMP_BIGINT(ma->userid, mb->userid); -} - -static K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, int64_t diffacc) -{ - MININGPAYOUTS lookminingpayouts, *miningpayouts; - K_ITEM look, *mu_item; - K_TREE_CTX ctx[1]; - - lookminingpayouts.userid = userid; - INIT_MININGPAYOUTS(&look); - look.data = (void *)(&lookminingpayouts); - mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); - if (mu_item) { - DATA_MININGPAYOUTS(miningpayouts, mu_item); - miningpayouts->amount += diffacc; - } else { - K_WLOCK(mu_store); - mu_item = k_unlink_head(miningpayouts_free); - DATA_MININGPAYOUTS(miningpayouts, mu_item); - miningpayouts->userid = userid; - miningpayouts->amount = diffacc; - mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); - k_add_head(mu_store, mu_item); - K_WUNLOCK(mu_store); - } - - return mu_root; -} - -/* Find the block_workinfoid of the block requested - then add all it's diffacc shares - then keep stepping back shares until diffacc_total matches or exceeds - the number required (diff_want) - this is begin_workinfoid - (also summarising diffacc per user) - then keep stepping back until we complete the current begin_workinfoid - (also summarising diffacc per user) - While we are still below diff_want - find each workmarker and add on the full set of worksummary - diffacc shares (also summarising diffacc per user) - This will give us the total number of diff1 shares (diffacc_total) - to use for the payment calculations - The value of diff_want defaults to the block's network difficulty - (block_ndiff) but can be changed with diff_times and diff_add to: - block_ndiff * diff_times + diff_add - N.B. diff_times and diff_add can be zero, positive or negative - The pplns_elapsed time of the shares is from the createdate of the - begin_workinfoid that has shares accounted to the total, - up to the createdate of the last share - The user average hashrate would be: - diffacc_user * 2^32 / pplns_elapsed - PPLNS fraction of the payout would be: - diffacc_user / diffacc_total - - N.B. 'begin' means the oldest back in time and 'end' means the newest - 'end' should usually be the info of the found block with the pplns - data going back in time to 'begin' -*/ - -/* Blocks after 334106 were set to 5xN - * however, they cannot count back to include the workinfoid of 333809 - * due to the markersummaries that were created. - * Code checks that if the block is after FIVExSTT then it must stop - * counting back shares at - and not include - FIVExWID */ -#define FIVExSTT 334106 -#define FIVExLIM 333809 -// 333809 workinfoid -#define FIVExWID 6085620100361140756 - +/* Kept for reference/comparison to cmd_pplns2() + * This will get different results due to the fact that it uses the current + * contents of the payoutaddresses table + * However, the only differences should be the addresses, + * and the breakdown for percent address users, + * the totals per user and per payout should still be the same */ static char *cmd_pplns(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, @@ -3857,6 +3817,219 @@ shazbot: return strdup(reply); } +// Generated from the payouts, miningpayouts and payments data +static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, + __maybe_unused tv_t *now, __maybe_unused char *by, + __maybe_unused char *code, __maybe_unused char *inet, + __maybe_unused tv_t *notcd, K_TREE *trf_root) +{ + char reply[1024], tmp[1024], *buf; + char *block_extra, *marks_status = EMPTY; + size_t siz = sizeof(reply); + K_ITEM *i_height; + K_ITEM b_look, *b_item, *p_item, *mp_item, *pay_item, *u_item; + MININGPAYOUTS *miningpayouts; + PAYMENTS *payments; + PAYOUTS *payouts; + BLOCKS lookblocks, *blocks; + USERS *users; + int32_t height; + K_TREE_CTX b_ctx[1], mp_ctx[1], pay_ctx[1]; + size_t len, off; + int rows; + bool pok; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + if (sharesummary_marks_limit) + marks_status = "ckdb -w load value means pplns may be incorrect"; + + i_height = require_name(trf_root, "height", 1, NULL, reply, siz); + if (!i_height) + return strdup(reply); + TXT_TO_INT("height", transfer_data(i_height), height); + + LOGDEBUG("%s(): height %"PRId32, __func__, height); + + lookblocks.height = height; + lookblocks.blockhash[0] = '\0'; + INIT_BLOCKS(&b_look); + b_look.data = (void *)(&lookblocks); + K_RLOCK(blocks_free); + b_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + K_RUNLOCK(blocks_free); + DATA_BLOCKS_NULL(blocks, b_item); + if (!b_item || blocks->height != height) { + snprintf(reply, siz, "ERR.no block height %"PRId32, height); + return strdup(reply); + } + if (!CURRENT(&(blocks->expirydate))) { + snprintf(reply, siz, "ERR.no CURRENT block %d"PRId32, height); + return strdup(reply); + } + LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward); + switch (blocks->confirmed[0]) { + case BLOCKS_NEW: + block_extra = "Can't be paid out yet"; + break; + case BLOCKS_ORPHAN: + block_extra = "Can't be paid out"; + break; + default: + block_extra = EMPTY; + break; + } + + pok = false; + K_RLOCK(payouts_free); + p_item = find_payouts(height, blocks->blockhash); + DATA_PAYOUTS_NULL(payouts, p_item); + if (p_item && PAYGENERATED(payouts->status)) + pok = true; + K_RUNLOCK(payouts_free); + if (!p_item) { + snprintf(reply, siz, "ERR.no payout for %"PRId32"/%s", + height, blocks->blockhash); + return strdup(reply); + } + if (!pok) { + snprintf(reply, siz, "ERR.payout %"PRId64" status=%s " + "for %"PRId32"/%s", + payouts->payoutid, payouts->status, height, + blocks->blockhash); + return strdup(reply); + } + + LOGDEBUG("%s(): total %.1f want %.1f", + __func__, payouts->diffused, payouts->diffwanted); + + APPEND_REALLOC_INIT(buf, off, len); + APPEND_REALLOC(buf, off, len, "ok."); + snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_hash=%s%c", blocks->blockhash, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_reward=%"PRId64"%c", blocks->reward, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "miner_reward=%"PRId64"%c", payouts->minerreward, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_status=%s%c", + blocks_confirmed(blocks->confirmed), FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_extra=%s%c", block_extra, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "marks_status=%s%c", marks_status, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "workername=%s%c", blocks->workername, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "nonce=%s%c", blocks->nonce, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "begin_workinfoid=%"PRId64"%c", payouts->workinfoidstart, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", blocks->workinfoid, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", payouts->workinfoidend, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "diffacc_total=%.1f%c", payouts->diffused, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "pplns_elapsed=%"PRId64"%c", payouts->elapsed, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + rows = 0; + K_RLOCK(miningpayouts_free); + mp_item = first_miningpayouts(payouts->payoutid, mp_ctx); + K_RUNLOCK(miningpayouts_free); + DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); + while (mp_item && miningpayouts->payoutid == payouts->payoutid) { + if (CURRENT(&(miningpayouts->expirydate))) { + int out = 0; + K_RLOCK(users_free); + u_item = find_userid(miningpayouts->userid); + K_RUNLOCK(users_free); + if (!u_item) { + snprintf(reply, siz, + "ERR.unknown userid %"PRId64, + miningpayouts->userid); + goto shazbot; + } + DATA_USERS(users, u_item); + + K_RLOCK(payments_free); + pay_item = find_first_paypayid(miningpayouts->userid, + payouts->payoutid, + pay_ctx); + DATA_PAYMENTS_NULL(payments, pay_item); + while (pay_item && + payments->userid == miningpayouts->userid && + payments->payoutid == payouts->payoutid) { + if (CURRENT(&(payments->expirydate))) { + snprintf(tmp, sizeof(tmp), + "user:%d=%s%c" + "payaddress:%d=%s%c" + "amount:%d=%"PRId64"%c" + "diffacc:%d=%.1f%c", + rows, payments->subname, FLDSEP, + rows, payments->payaddress, FLDSEP, + rows, payments->amount, FLDSEP, + rows, payments->diffacc, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + rows++; + out++; + } + pay_item = next_in_ktree(pay_ctx); + DATA_PAYMENTS_NULL(payments, pay_item); + } + K_RUNLOCK(payments_free); + if (out == 0) { + snprintf(tmp, sizeof(tmp), + "user:%d=%s.0%c" + "payaddress:%d=%s%c" + "amount:%d=%"PRId64"%c" + "diffacc:%d=%.1f%c", + rows, users->username, FLDSEP, + rows, "none", FLDSEP, + rows, miningpayouts->amount, FLDSEP, + rows, miningpayouts->diffacc, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + rows++; + } + } + K_RLOCK(miningpayouts_free); + mp_item = next_in_ktree(mp_ctx); + K_RUNLOCK(miningpayouts_free); + DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); + } + + snprintf(tmp, sizeof(tmp), + "rows=%d%cflds=%s%c", + rows, FLDSEP, + "user,payaddress,amount,diffacc", FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", + "Users", FLDSEP, "", FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + snprintf(tmp, sizeof(tmp), "%s%c", payouts->stats, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "diff_want=%.1f%c", + payouts->diffwanted, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "acc_share_count=%.0f%c", + payouts->shareacc, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + // So web can always verify it received all data + APPEND_REALLOC(buf, off, len, "pplns_last=1"); + + LOGDEBUG("%s.ok.pplns.%s", id, buf); + return buf; + +shazbot: + return strdup(reply); +} + static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, @@ -3884,6 +4057,9 @@ static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, dsp_ktree(paymentaddresses_free, paymentaddresses_root, transfer_data(i_file), NULL); + dsp_ktree(paymentaddresses_create_free, paymentaddresses_root, + transfer_data(i_file), NULL); + dsp_ktree(sharesummary_free, sharesummary_root, transfer_data(i_file), NULL); @@ -3944,8 +4120,9 @@ static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, USEINFO(users, 1, 2); USEINFO(useratts, 1, 1); USEINFO(workers, 1, 1); - USEINFO(paymentaddresses, 1, 1); + USEINFO(paymentaddresses, 1, 2); USEINFO(payments, 1, 1); + USEINFO(accountbalance, 1, 1); USEINFO(idcontrol, 1, 0); USEINFO(optioncontrol, 1, 1); USEINFO(workinfo, 1, 1); @@ -3957,6 +4134,7 @@ static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, USEINFO(marks, 1, 1); USEINFO(blocks, 1, 1); USEINFO(miningpayouts, 1, 1); + USEINFO(payouts, 1, 2); USEINFO(auths, 1, 1); USEINFO(poolstats, 1, 1); USEINFO(userstats, 2, 1); @@ -4511,6 +4689,8 @@ struct CMDS ckdb_cmds[] = { { CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM ACCESS_WEB }, + { CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, ACCESS_SYSTEM ACCESS_WEB }, +// { CMD_PAYOUT, "payout", false, false, cmd_payout, ACCESS_SYSTEM }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, ACCESS_SYSTEM ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } diff --git a/src/ckdb_data.c b/src/ckdb_data.c index d8ef8b38..3d227af9 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -8,6 +8,7 @@ */ #include "ckdb.h" +#include // Data free functions (added here as needed) void free_workinfo_data(K_ITEM *item) @@ -1208,6 +1209,21 @@ cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b) return c; } +// order by userid asc,createdate asc,payaddress asc +cmp_t cmp_payaddr_create(K_ITEM *a, K_ITEM *b) +{ + PAYMENTADDRESSES *pa, *pb; + DATA_PAYMENTADDRESSES(pa, a); + DATA_PAYMENTADDRESSES(pb, b); + cmp_t c = CMP_BIGINT(pa->userid, pb->userid); + if (c == 0) { + c = CMP_TV(pa->createdate, pb->createdate); + if (c == 0) + c = CMP_STR(pa->payaddress, pb->payaddress); + } + return c; +} + /* Find the last CURRENT paymentaddresses for the given userid * N.B. there can be more than one * any more will be prev_in_ktree(ctx): CURRENT and userid matches */ @@ -1234,6 +1250,32 @@ K_ITEM *find_paymentaddresses(int64_t userid, K_TREE_CTX *ctx) return NULL; } +/* Find the first paymentaddresses for the given userid + * sorted by userid+createdate+... */ +K_ITEM *find_paymentaddresses_create(int64_t userid, K_TREE_CTX *ctx) +{ + PAYMENTADDRESSES paymentaddresses, *pa; + K_ITEM look, *item; + + paymentaddresses.userid = userid; + paymentaddresses.createdate.tv_sec = 0; + paymentaddresses.createdate.tv_usec = 0; + paymentaddresses.payaddress[0] = '\0'; + + INIT_PAYMENTADDRESSES(&look); + look.data = (void *)(&paymentaddresses); + item = find_after_in_ktree(paymentaddresses_create_root, &look, + cmp_payaddr_create, ctx); + if (item) { + DATA_PAYMENTADDRESSES(pa, item); + if (pa->userid == userid) + return item; + else + return NULL; + } else + return NULL; +} + K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx) { PAYMENTADDRESSES paymentaddresses; @@ -1254,7 +1296,10 @@ K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx) * that has EVER been seen before * However, also, cmd_userset() that uses it, effectively ensures * that 2 standard users, that mine to a username rather than - * a bitcoin address, cannot ever use the same bitcoin address */ + * a bitcoin address, cannot ever use the same bitcoin address + * N.B. this is faster than a bitcoind check, but still slow + * It needs a tree based on payaddress to speed it up + * N.B.2 paymentadresses_root doesn't contain addrauth usernames */ K_ITEM *find_any_payaddress(char *payaddress) { PAYMENTADDRESSES *pa; @@ -1271,7 +1316,7 @@ K_ITEM *find_any_payaddress(char *payaddress) return NULL; } -// order by userid asc,paydate asc,payaddress asc,expirydate desc +// order by userid asc,payoutid asc,subname asc,expirydate desc cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) { PAYMENTS *pa, *pb; @@ -1279,9 +1324,9 @@ cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) DATA_PAYMENTS(pb, b); cmp_t c = CMP_BIGINT(pa->userid, pb->userid); if (c == 0) { - c = CMP_TV(pa->paydate, pb->paydate); + c = CMP_BIGINT(pa->payoutid, pb->payoutid); if (c == 0) { - c = CMP_STR(pa->payaddress, pb->payaddress); + c = CMP_STR(pa->subname, pb->subname); if (c == 0) c = CMP_TV(pb->expirydate, pa->expirydate); } @@ -1289,6 +1334,87 @@ cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) return c; } +K_ITEM *find_payments(int64_t payoutid, int64_t userid, char *subname) +{ + PAYMENTS payments; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payments.payoutid = payoutid; + payments.userid = userid; + STRNCPY(payments.subname, subname); + payments.expirydate.tv_sec = default_expiry.tv_sec; + payments.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + return find_in_ktree(payments_root, &look, cmp_payments, ctx); +} + +K_ITEM *find_first_payments(int64_t userid, K_TREE_CTX *ctx) +{ + PAYMENTS payments; + K_TREE_CTX ctx0[1]; + K_ITEM look, *item; + + if (ctx == NULL) + ctx = ctx0; + + bzero(&payments, sizeof(payments)); + payments.userid = userid; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + // userid needs to be checked if item returned != NULL + item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); + return item; +} + +K_ITEM *find_first_paypayid(int64_t userid, int64_t payoutid, K_TREE_CTX *ctx) +{ + PAYMENTS payments; + K_TREE_CTX ctx0[1]; + K_ITEM look, *item; + + if (ctx == NULL) + ctx = ctx0; + + payments.userid = userid; + payments.payoutid = payoutid; + payments.subname[0] = '\0'; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + // userid+payoutid needs to be checked if item returned != NULL + item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); + return item; +} + +// order by userid asc +cmp_t cmp_accountbalance(K_ITEM *a, K_ITEM *b) +{ + PAYMENTS *aba, *abb; + DATA_PAYMENTS(aba, a); + DATA_PAYMENTS(abb, b); + return CMP_BIGINT(aba->userid, abb->userid); +} + +K_ITEM *find_accountbalance(int64_t userid) +{ + ACCOUNTBALANCE accountbalance; + K_TREE_CTX ctx[1]; + K_ITEM look, *item; + + accountbalance.userid = userid; + + INIT_ACCOUNTBALANCE(&look); + look.data = (void *)(&accountbalance); + K_RLOCK(accountbalance_free); + item = find_in_ktree(accountbalance_root, &look, cmp_accountbalance, ctx); + K_RUNLOCK(accountbalance_free); + return item; +} + // order by optionname asc,activationdate asc,activationheight asc,expirydate desc cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) { @@ -1308,16 +1434,16 @@ cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) } // Must be R or W locked before call -K_ITEM *find_optioncontrol(char *optionname, tv_t *now) +K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height) { OPTIONCONTROL optioncontrol, *oc, *ocbest; K_TREE_CTX ctx[1]; K_ITEM look, *item, *best; - /* Step through all records having optionaname and check: + /* Step through all records having optionname and check: * 1) activationdate is <= now * and - * 2) height <= current + * 2) height <= specified height (pool.height = current) * Remember the active record with the newest activationdate * If two records have the same activation date, then * remember the active record with the highest height @@ -1350,7 +1476,7 @@ K_ITEM *find_optioncontrol(char *optionname, tv_t *now) // Is oc active? if (CURRENT(&(oc->expirydate)) && - oc->activationheight <= pool.height && + oc->activationheight <= height && tv_newer_eq(&(oc->activationdate), now)) { // Is oc newer than ocbest? if (!ocbest || @@ -1972,12 +2098,15 @@ cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b) * or add new ones as required here */ // Must be R or W locked before call - gets current status (default_expiry) -K_ITEM *find_blocks(int32_t height, char *blockhash) +K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx) { BLOCKS blocks; - K_TREE_CTX ctx[1]; + K_TREE_CTX ctx0[1]; K_ITEM look; + if (ctx == NULL) + ctx = ctx0; + blocks.height = height; STRNCPY(blocks.blockhash, blockhash); blocks.expirydate.tv_sec = default_expiry.tv_sec; @@ -2221,14 +2350,14 @@ void set_block_share_counters() LOGWARNING("%s(): Update block counters complete", __func__); } -/* order by height asc,userid asc,expirydate asc +/* order by payoutid asc,userid asc,expirydate asc * i.e. only one payout amount per block per user */ cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) { MININGPAYOUTS *ma, *mb; DATA_MININGPAYOUTS(ma, a); DATA_MININGPAYOUTS(mb, b); - cmp_t c = CMP_INT(ma->height, mb->height); + cmp_t c = CMP_BIGINT(ma->payoutid, mb->payoutid); if (c == 0) { c = CMP_BIGINT(ma->userid, mb->userid); if (c == 0) @@ -2237,6 +2366,893 @@ cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) return c; } +K_ITEM *find_miningpayouts(int64_t payoutid, int64_t userid) +{ + MININGPAYOUTS miningpayouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + miningpayouts.payoutid = payoutid; + miningpayouts.userid = userid; + miningpayouts.expirydate.tv_sec = default_expiry.tv_sec; + miningpayouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&miningpayouts); + return find_in_ktree(miningpayouts_root, &look, cmp_miningpayouts, ctx); +} + +K_ITEM *first_miningpayouts(int64_t payoutid, K_TREE_CTX *ctx) +{ + MININGPAYOUTS miningpayouts; + K_TREE_CTX ctx0[1]; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + miningpayouts.payoutid = payoutid; + miningpayouts.userid = 0; + miningpayouts.expirydate.tv_sec = 0; + miningpayouts.expirydate.tv_usec = 0; + + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&miningpayouts); + return find_after_in_ktree(miningpayouts_root, &look, cmp_miningpayouts, ctx); +} + +/* Processing payouts uses it's own tree of miningpayouts keyed only on userid + * that is stored in the miningpayouts tree/db when the calculations are done + * cmp_mu() and upd_add_mu() are used for that */ + +// order by userid asc +cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) +{ + MININGPAYOUTS *ma, *mb; + DATA_MININGPAYOUTS(ma, a); + DATA_MININGPAYOUTS(mb, b); + return CMP_BIGINT(ma->userid, mb->userid); +} + +// update the userid record or add a new one if the userid isn't already present +K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, + double diffacc) +{ + MININGPAYOUTS lookminingpayouts, *miningpayouts; + K_ITEM look, *mu_item; + K_TREE_CTX ctx[1]; + + lookminingpayouts.userid = userid; + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&lookminingpayouts); + // No locking required since it's not a shared tree or store + mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); + if (mu_item) { + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts->diffacc += diffacc; + } else { + K_WLOCK(mu_store); + mu_item = k_unlink_head(miningpayouts_free); + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts->userid = userid; + miningpayouts->diffacc = diffacc; + mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); + k_add_head(mu_store, mu_item); + K_WUNLOCK(mu_store); + } + + return mu_root; +} + +// order by height asc,blockhash asc,expirydate asc +cmp_t cmp_payouts(K_ITEM *a, K_ITEM *b) +{ + PAYOUTS *pa, *pb; + DATA_PAYOUTS(pa, a); + DATA_PAYOUTS(pb, b); + cmp_t c = CMP_INT(pa->height, pb->height); + if (c == 0) { + c = CMP_STR(pa->blockhash, pb->blockhash); + if (c == 0) + c = CMP_TV(pa->expirydate, pb->expirydate); + } + return c; +} + +// order by payoutid asc,expirydate asc +cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b) +{ + PAYOUTS *pa, *pb; + DATA_PAYOUTS(pa, a); + DATA_PAYOUTS(pb, b); + cmp_t c = CMP_BIGINT(pa->payoutid, pb->payoutid); + if (c == 0) + c = CMP_TV(pa->expirydate, pb->expirydate); + return c; +} + +K_ITEM *find_payouts(int32_t height, char *blockhash) +{ + PAYOUTS payouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payouts.height = height; + STRNCPY(payouts.blockhash, blockhash); + payouts.expirydate.tv_sec = default_expiry.tv_sec; + payouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYOUTS(&look); + look.data = (void *)(&payouts); + return find_in_ktree(payouts_root, &look, cmp_payouts, ctx); +} + +// Last block payout calculated +K_ITEM *find_last_payouts() +{ + K_TREE_CTX ctx[1]; + PAYOUTS *payouts; + K_ITEM *p_item; + + p_item = last_in_ktree(payouts_root, ctx); + while (p_item) { + DATA_PAYOUTS(payouts, p_item); + if (CURRENT(&(payouts->expirydate))) + return p_item; + p_item = prev_in_ktree(ctx); + } + return p_item; +} + +K_ITEM *find_payoutid(int64_t payoutid) +{ + PAYOUTS payouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payouts.payoutid = payoutid; + payouts.expirydate.tv_sec = default_expiry.tv_sec; + payouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYOUTS(&look); + look.data = (void *)(&payouts); + return find_in_ktree(payouts_id_root, &look, cmp_payouts_id, ctx); +} + +/* Find the block_workinfoid of the block requested + then add all it's diffacc shares + then keep stepping back shares until diffacc_total matches or exceeds + the number required (diff_want) - this is begin_workinfoid + (also summarising diffacc per user) + then keep stepping back until we complete the current begin_workinfoid + (also summarising diffacc per user) + While we are still below diff_want + find each workmarker and add on the full set of worksummary + diffacc shares (also summarising diffacc per user) + This will give us the total number of diff1 shares (diffacc_total) + to use for the payment calculations + The value of diff_want defaults to the block's network difficulty + (block_ndiff) but can be changed with diff_times and diff_add to: + block_ndiff * diff_times + diff_add + N.B. diff_times and diff_add can be zero, positive or negative + The pplns_elapsed time of the shares is from the createdate of the + begin_workinfoid that has shares accounted to the total, + up to the createdate of the last share + The user average hashrate would be: + diffacc_user * 2^32 / pplns_elapsed + PPLNS fraction of the payout would be: + diffacc_user / diffacc_total + + N.B. 'begin' means the oldest back in time and 'end' means the newest + 'end' should usually be the info of the found block with the pplns + data going back in time to 'begin' + + The data processing procedure is to create a separate tree/store of + miningpayouts, create a seperate store of payments, + then actually create the payout record and transfer the miningpayouts + and payments to the ram table and the db + + TODO: recheck the payout if it already exists + + N.B. process_pplns() is only automatically triggered once after the block + summarisation is verified, so it can always report all errors +*/ +void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) +{ + K_TREE_CTX b_ctx[1], ss_ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1], mu_ctx[1]; + bool allow_aged = true, conned = false, begun = false; + bool countbacklimit, ok; + PGconn *conn = NULL; + MININGPAYOUTS *miningpayouts; + OPTIONCONTROL *optioncontrol; + PAYMENTS *payments; + WORKINFO *workinfo; + PAYOUTS *payouts, *payouts2; + BLOCKS *blocks, *blocks2; + USERS *users; + K_ITEM *p_item, *old_p_item, *b_item, *b2_item, *w_item, *wb_item; + K_ITEM *u_item, *mu_item, *oc_item, *pay_item, *p2_item, *old_p2_item; + SHARESUMMARY looksharesummary, *sharesummary; + WORKMARKERS lookworkmarkers, *workmarkers; + MARKERSUMMARY lookmarkersummary, *markersummary; + K_ITEM ss_look, *ss_item, wm_look, *wm_item, ms_look, *ms_item; + int64_t amount, used, d64, g64, begin_workinfoid, end_workinfoid; + int64_t total_share_count, acc_share_count; + int64_t ss_count, wm_count, ms_count; + K_STORE *mu_store = NULL, *pay_store = NULL, *addr_store = NULL; + K_TREE *mu_root = NULL; + int usercount; + double ndiff, total_diff, diff_want, elapsed; + char ndiffbin[TXT_SML+1]; + double diff_times, diff_add; + char cd_buf[CDATE_BUFSIZ]; + tv_t begin_tv, end_tv, now; + char buf[1024]; + + /* + * Only allow one process_pplns() at a time + * This ensures that a payout can't be processed twice at the same time + * and simply avoids the problems that would cause without much more + * strict locking than is used already + */ + ck_wlock(&process_pplns_lock); + + setnow(&now); + + K_RLOCK(payouts_free); + p_item = find_payouts(height, blockhash); + K_RUNLOCK(payouts_free); + // TODO: regenerate miningpayouts and payments if required or missing? + if (p_item) { + DATA_PAYOUTS(payouts, p_item); + tv_to_buf(&(payouts->createdate), cd_buf, sizeof(cd_buf)); + LOGERR("%s(): payout for block %"PRId32"/%s already exists" + "%"PRId64"/%"PRId64"/%"PRId64"/%s", + __func__, height, blockhash, payouts->payoutid, + payouts->workinfoidstart, payouts->workinfoidend, + cd_buf); + goto oku; + } + + // Check the block status + K_RLOCK(blocks_free); + b_item = find_blocks(height, blockhash, b_ctx); + if (!b_item) { + K_RUNLOCK(blocks_free); + LOGERR("%s(): no block %"PRId32"/%s for payout", + __func__, height, blockhash); + goto oku; + } + DATA_BLOCKS(blocks, b_item); + // If addr_cd is null, use the block NEW createdate + if (!addr_cd) { + b2_item = b_item; + DATA_BLOCKS(blocks2, b2_item); + while (b2_item && blocks2->height == height && + strcmp(blocks2->blockhash, blockhash) == 0) { + if (blocks2->confirmed[0] == BLOCKS_NEW) { + addr_cd = &(blocks2->createdate); + break; + } + b2_item = next_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks2, b2_item); + } + if (!addr_cd) { + K_RUNLOCK(blocks_free); + LOGEMERG("%s(): missing NEW record for block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } + } + K_RUNLOCK(blocks_free); + + LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward); + + switch (blocks->confirmed[0]) { + case BLOCKS_NEW: + case BLOCKS_ORPHAN: + LOGERR("%s(): can't process block %"PRId32"/%" + PRId64"/%s/%"PRId64" status: %s/%s", + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->reward, + blocks->confirmed, + blocks_confirmed(blocks->confirmed)); + goto oku; + } + w_item = find_workinfo(blocks->workinfoid, NULL); + if (!w_item) { + LOGEMERG("%s(): missing block workinfoid %"PRId32"/%"PRId64 + "/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } + DATA_WORKINFO(workinfo, w_item); + + // Get the PPLNS N values + K_RLOCK(optioncontrol_free); + oc_item = find_optioncontrol(PPLNSDIFFTIMES, &(blocks->createdate), + height); + K_RUNLOCK(optioncontrol_free); + if (!oc_item) { + tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", + __func__, PPLNSDIFFTIMES, cd_buf, blocks->height); + goto oku; + } + DATA_OPTIONCONTROL(optioncontrol, oc_item); + diff_times = atof(optioncontrol->optionvalue); + + K_RLOCK(optioncontrol_free); + oc_item = find_optioncontrol(PPLNSDIFFADD, &(blocks->createdate), + height); + K_RUNLOCK(optioncontrol_free); + if (!oc_item) { + tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", + __func__, PPLNSDIFFADD, cd_buf, blocks->height); + goto oku; + } + DATA_OPTIONCONTROL(optioncontrol, oc_item); + diff_add = atof(optioncontrol->optionvalue); + + hex2bin(ndiffbin, workinfo->bits, 4); + ndiff = diff_from_nbits(ndiffbin); + diff_want = ndiff * diff_times + diff_add; + if (diff_want < 1.0) { + LOGERR("%s(): invalid diff_want %.1f, block %"PRId32"/%" + PRId64"/%s/%s/%"PRId64, + __func__, diff_want, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward); + goto oku; + } + + // Check for the hard coded limit + if (blocks->height > FIVExSTT) + countbacklimit = true; + else + countbacklimit = false; + LOGDEBUG("%s(): ndiff %.1f limit %c", + __func__, ndiff, countbacklimit ? 'Y' : 'N'); + + // add up all the shares ... + begin_workinfoid = end_workinfoid = 0; + total_share_count = acc_share_count = 0; + total_diff = 0; + ss_count = wm_count = ms_count = 0; + + mu_store = k_new_store(miningpayouts_free); + mu_root = new_ktree(); + + looksharesummary.workinfoid = blocks->workinfoid; + looksharesummary.userid = MAXID; + looksharesummary.workername = EMPTY; + INIT_SHARESUMMARY(&ss_look); + ss_look.data = (void *)(&looksharesummary); + K_RLOCK(sharesummary_free); + K_RLOCK(workmarkers_free); + K_RLOCK(markersummary_free); + ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, + cmp_sharesummary_workinfoid, ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + if (ss_item) + end_workinfoid = sharesummary->workinfoid; + /* Add up all sharesummaries until >= diff_want + * also record the latest lastshare - that will be the end pplns time + * which will be >= blocks->createdate */ + while (total_diff < diff_want && ss_item) { + switch (sharesummary->complete[0]) { + case SUMMARY_CONFIRM: + break; + case SUMMARY_COMPLETE: + if (allow_aged) + break; + default: + // Release ASAP + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + LOGERR("%s(): sharesummary not ready %" + PRId64"/%s/%"PRId64"/%s. allow_aged=%s", + __func__, sharesummary->userid, + sharesummary->workername, + sharesummary->workinfoid, + sharesummary->complete, + allow_aged ? "true" : "false"); + goto shazbot; + } + + // Stop before FIVExWID if necessary + if (countbacklimit && sharesummary->workinfoid <= FIVExWID) + break; + + ss_count++; + total_share_count += sharesummary->sharecount; + acc_share_count += sharesummary->shareacc; + total_diff += sharesummary->diffacc; + begin_workinfoid = sharesummary->workinfoid; + // TODO: add lastshareacc to sharesummary and markersummary + if (sharesummary->shareacc > 0 && + tv_newer(&end_tv, &(sharesummary->lastshare))) + copy_tv(&end_tv, &(sharesummary->lastshare)); + mu_root = upd_add_mu(mu_root, mu_store, + sharesummary->userid, + sharesummary->diffacc); + ss_item = prev_in_ktree(ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + } + + // Include the rest of the sharesummaries matching begin_workinfoid + while (ss_item && sharesummary->workinfoid == begin_workinfoid) { + switch (sharesummary->complete[0]) { + case SUMMARY_CONFIRM: + break; + case SUMMARY_COMPLETE: + if (allow_aged) + break; + default: + // Release ASAP + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + LOGERR("%s(): sharesummary2 not ready %" + PRId64"/%s/%"PRId64"/%s. allow_aged=%s", + __func__, sharesummary->userid, + sharesummary->workername, + sharesummary->workinfoid, + sharesummary->complete, + allow_aged ? "true" : "false"); + goto shazbot; + } + ss_count++; + total_share_count += sharesummary->sharecount; + acc_share_count += sharesummary->shareacc; + total_diff += sharesummary->diffacc; + mu_root = upd_add_mu(mu_root, mu_store, + sharesummary->userid, + sharesummary->diffacc); + ss_item = prev_in_ktree(ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + } + LOGDEBUG("%s(): ss %"PRId64" total %.1f want %.1f", + __func__, ss_count, total_diff, diff_want); + + /* If we haven't met or exceeded the required N, + * move on to the markersummaries ... this is now mandatory */ + if (total_diff < diff_want) { + lookworkmarkers.expirydate.tv_sec = default_expiry.tv_sec; + lookworkmarkers.expirydate.tv_usec = default_expiry.tv_usec; + if (begin_workinfoid != 0) + lookworkmarkers.workinfoidend = begin_workinfoid; + else + lookworkmarkers.workinfoidend = blocks->workinfoid + 1; + INIT_WORKMARKERS(&wm_look); + wm_look.data = (void *)(&lookworkmarkers); + wm_item = find_before_in_ktree(workmarkers_workinfoid_root, &wm_look, + cmp_workmarkers_workinfoid, wm_ctx); + DATA_WORKMARKERS_NULL(workmarkers, wm_item); + LOGDEBUG("%s(): workmarkers < %"PRId64, __func__, lookworkmarkers.workinfoidend); + while (total_diff < diff_want && wm_item && CURRENT(&(workmarkers->expirydate))) { + if (WMPROCESSED(workmarkers->status)) { + // Stop before FIVExWID if necessary + if (countbacklimit && workmarkers->workinfoidstart <= FIVExWID) + break; + + wm_count++; + lookmarkersummary.markerid = workmarkers->markerid; + lookmarkersummary.userid = MAXID; + lookmarkersummary.workername = EMPTY; + INIT_MARKERSUMMARY(&ms_look); + ms_look.data = (void *)(&lookmarkersummary); + ms_item = find_before_in_ktree(markersummary_root, &ms_look, + cmp_markersummary, ms_ctx); + DATA_MARKERSUMMARY_NULL(markersummary, ms_item); + // add the whole markerid + while (ms_item && markersummary->markerid == workmarkers->markerid) { + if (end_workinfoid == 0) + end_workinfoid = workmarkers->workinfoidend; + ms_count++; + total_share_count += markersummary->sharecount; + acc_share_count += markersummary->shareacc; + total_diff += markersummary->diffacc; + begin_workinfoid = workmarkers->workinfoidstart; + if (markersummary->shareacc > 0 && + tv_newer(&end_tv, &(markersummary->lastshare))) + copy_tv(&end_tv, &(markersummary->lastshare)); + mu_root = upd_add_mu(mu_root, mu_store, + markersummary->userid, + markersummary->diffacc); + ms_item = prev_in_ktree(ms_ctx); + DATA_MARKERSUMMARY_NULL(markersummary, ms_item); + } + } + wm_item = prev_in_ktree(wm_ctx); + DATA_WORKMARKERS_NULL(workmarkers, wm_item); + } + LOGDEBUG("%s(): wm %"PRId64" ms %"PRId64" total %.1f want %.1f", + __func__, wm_count, ms_count, total_diff, diff_want); + } + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + + usercount = mu_store->count; + + if (wm_count < 1) { + /* Problem means either workmarkers are not being processed + * or if they are, then when the shifts are later created, + * they almost certainly won't match the begin_workinfo + * calculated + * i.e. the payout N is too small, it's less than the time + * needed to create and process any workmarkers for this + * block - so abort + * The fix is to create the marks and summaries needed via + * cmd_marks() then manually trigger the payout generation + * TODO: via cmd_payouts() ... which isn't available yet */ + LOGEMERG("%s(): payout had < 1 (%"PRId64") workmarkers for " + "block %"PRId32"/%"PRId64"/%s/%s/%"PRId64 + " beginwi=%"PRId64" ss=%"PRId64" diff=%.1f", + __func__, wm_count, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward, + begin_workinfoid, ss_count, total_diff); + goto shazbot; + } + + LOGDEBUG("%s(): total %.1f want %.1f", __func__, total_diff, diff_want); + if (total_diff == 0.0) { + LOGERR("%s(): total share diff zero before block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto shazbot; + } + + wb_item = find_workinfo(begin_workinfoid, NULL); + if (!wb_item) { + LOGEMERG("%s(): missing begin workinfo record %"PRId64 + " payout of block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, + __func__, begin_workinfoid, blocks->height, + blocks->workinfoid, blocks->workername, + blocks->confirmed, blocks->reward); + goto shazbot; + } + DATA_WORKINFO(workinfo, wb_item); + + copy_tv(&begin_tv, &(workinfo->createdate)); + /* Elapsed is from the start of the first workinfoid used, + * to the time of the last share accepted - + * which can be after the block, but must have the same workinfoid as + * the block, if it is after the block + * Any shares accepted in all workinfoids after the block's workinfoid + * will not be creditied to this block no matter what the height + * of their workinfoid - but will be candidates for subsequent blocks */ + elapsed = tvdiff(&end_tv, &begin_tv); + + // Create the payout + K_WLOCK(payouts_free); + p_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + DATA_PAYOUTS(payouts, p_item); + + bzero(payouts, sizeof(*payouts)); + payouts->height = height; + STRNCPY(payouts->blockhash, blockhash); + d64 = blocks->reward * 9 / 1000; + g64 = blocks->reward - d64; + payouts->minerreward = g64; + payouts->workinfoidstart = begin_workinfoid; + payouts->workinfoidend = end_workinfoid; + payouts->elapsed = elapsed; + STRNCPY(payouts->status, PAYOUTS_PROCESSING_STR); + payouts->diffwanted = diff_want; + payouts->diffused = total_diff; + payouts->shareacc = acc_share_count; + copy_tv(&(payouts->lastshareacc), &end_tv); + + ctv_to_buf(addr_cd, cd_buf, sizeof(cd_buf)); + snprintf(buf, sizeof(buf), + "diff_times=%f%cdiff_add=%f%ctotal_share_count=%"PRId64 + "%css_count=%"PRId64"%cwm_count=%"PRId64"%cms_count=%"PRId64 + "%caddr_cd=%s", + diff_times, FLDSEP, diff_add, FLDSEP, total_share_count, + FLDSEP, ss_count, FLDSEP, wm_count, FLDSEP, ms_count, + FLDSEP, cd_buf); + payouts->stats = buf; + + conned = CKPQConn(&conn); + begun = CKPQBegin(conn); + if (!begun) + goto shazbot; + + // begun is true + ok = payouts_add(conn, true, p_item, &old_p_item, (char *)by_default, + (char *)__func__, (char *)inet_default, &now, NULL, + begun); + if (!ok) + goto shazbot; + + // Update and store the miningpayouts and payments + pay_store = k_new_store(payments_free); + mu_item = first_in_ktree(mu_root, mu_ctx); + while (mu_item) { + DATA_MININGPAYOUTS(miningpayouts, mu_item); + + K_RLOCK(users_free); + u_item = find_userid(miningpayouts->userid); + K_RUNLOCK(users_free); + if (!u_item) { + LOGEMERG("%s(): unknown userid %"PRId64"/%.1f in " + "payout for block %"PRId32, + __func__, miningpayouts->userid, + miningpayouts->diffacc, blocks->height); + goto shazbot; + } + DATA_USERS(users, u_item); + + K_ITEM *pa_item, *pa_item2; + PAYMENTADDRESSES *pa, *pa2; + int64_t paytotal = 0; + int count = 0; + + used = 0; + amount = floor((double)(payouts->minerreward) * + miningpayouts->diffacc / payouts->diffused); + + /* Get the paymentaddresses active as at *addr_cd + * which defaults to when the block was found */ + addr_store = k_new_store(paymentaddresses_free); + K_WLOCK(paymentaddresses_free); + pa_item = find_paymentaddresses_create(miningpayouts->userid, + pay_ctx); + if (pa_item) { + DATA_PAYMENTADDRESSES(pa, pa_item); + while (pa_item && pa->userid == miningpayouts->userid && + tv_newer(&(pa->createdate), addr_cd)) { + if (tv_newer_eq(addr_cd, &(pa->expirydate))) { + paytotal += pa->payratio; + + /* Duplicate it to a new store - + * thus changes to paymentaddresses + * can't affect the code below + * and we don't need to keep + * paymentaddresses locked until we + * have completed the db + * additions/updates */ + pa_item2 = k_unlink_head(paymentaddresses_free); + DATA_PAYMENTADDRESSES(pa2, pa_item2); + pa2->userid = pa->userid; + STRNCPY(pa2->payaddress, pa->payaddress); + pa2->payratio = pa->payratio; + k_add_tail(addr_store, pa_item2); + + pa_item = next_in_ktree(pay_ctx); + DATA_PAYMENTADDRESSES_NULL(pa, pa_item); + } + } + } + K_WUNLOCK(paymentaddresses_free); + + pa_item = addr_store->head; + if (pa_item) { + // Normal user with at least 1 paymentaddress + while (pa_item) { + DATA_PAYMENTADDRESSES(pa, pa_item); + K_WLOCK(payments_free); + pay_item = k_unlink_head(payments_free); + K_WUNLOCK(payments_free); + DATA_PAYMENTS(payments, pay_item); + bzero(payments, sizeof(*payments)); + payments->payoutid = payouts->payoutid; + payments->userid = miningpayouts->userid; + snprintf(payments->subname, + sizeof(payments->subname), + "%s.%d", users->username, ++count); + STRNCPY(payments->payaddress, pa->payaddress); + d64 = floor((double)amount * + (double)(pa->payratio) / + (double)paytotal); + payments->amount = d64; + payments->diffacc = miningpayouts->diffacc * + (double)(pa->payratio) / + (double)paytotal; + used += d64; + k_add_tail(pay_store, pay_item); + ok = payments_add(conn, true, pay_item, + &(payments->old_item), + (char *)by_default, + (char *)__func__, + (char *)inet_default, &now, + NULL, begun); + if (!ok) + goto shazbot; + + pa_item = pa_item->next; + } + } else { + /* Address user or normal user without a paymentaddress + * TODO: user table needs a flag to say which it is ... + * for now use a simple test */ + bool gotaddr = false; + size_t len; + + switch (users->username[0]) { + case '1': + case '3': + len = strlen(users->username); + if (len >= ADDR_MIN_LEN && len <= ADDR_MAX_LEN) + gotaddr = true; + } + if (gotaddr) { + K_WLOCK(payments_free); + pay_item = k_unlink_head(payments_free); + K_WUNLOCK(payments_free); + DATA_PAYMENTS(payments, pay_item); + bzero(payments, sizeof(*payments)); + payments->payoutid = payouts->payoutid; + payments->userid = miningpayouts->userid; + snprintf(payments->subname, + sizeof(payments->subname), + "%s.0", users->username); + STRNCPY(payments->payaddress, users->username); + payments->amount = amount; + used = amount; + k_add_head(pay_store, pay_item); + ok = payments_add(conn, true, pay_item, + &(payments->old_item), + (char *)by_default, + (char *)__func__, + (char *)inet_default, &now, + NULL, begun); + if (!ok) + goto shazbot; + } // else they go to their dust balance + } + + /* N.B. there will, of course, be a miningpayouts record without + * any payments record if the paymentaddress was missing */ + miningpayouts->payoutid = payouts->payoutid; + if (used == 0) + miningpayouts->amount = amount; + else + miningpayouts->amount = used; + + ok = miningpayouts_add(conn, true, mu_item, + &(miningpayouts->old_item), + (char *)by_default, (char *)__func__, + (char *)inet_default, &now, NULL, begun); + if (!ok) + goto shazbot; + + if (addr_store->count) { + K_WLOCK(paymentaddresses_free); + k_list_transfer_to_head(addr_store, paymentaddresses_free); + K_WUNLOCK(addr_store); + } + addr_store = k_free_store(addr_store); + + mu_item = next_in_ktree(mu_ctx); + } + + // begun is true + CKPQEnd(conn, begun); + + payouts_add_ram(true, p_item, old_p_item, &now); + + mu_root = free_ktree(mu_root, NULL); + mu_item = k_unlink_head(mu_store); + while (mu_item) { + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts_add_ram(true, mu_item, miningpayouts->old_item, &now); + mu_item = k_unlink_head(mu_store); + } + mu_store = k_free_store(mu_store); + + pay_item = k_unlink_head(pay_store); + while (pay_item) { + DATA_PAYMENTS(payments, pay_item); + payments_add_ram(true, pay_item, payments->old_item, &now); + pay_item = k_unlink_head(pay_store); + } + pay_store = k_free_store(pay_store); + + ctv_to_buf(addr_cd, cd_buf, sizeof(cd_buf)); + LOGWARNING("%s(): payout %"PRId64" setup for block %"PRId32"/%"PRId64 + "/%s/%"PRId64" ss=%"PRId64" wm=%"PRId64" ms=%"PRId64 + " users=%d times=%.1f add=%.1f addr_cd=%s", + __func__, payouts->payoutid, blocks->height, + blocks->workinfoid, blocks->confirmed, blocks->reward, + ss_count, wm_count, ms_count, usercount, diff_times, + diff_add, cd_buf); + + // convert the stack memory to heap memeory + payouts->stats = strdup(payouts->stats); + + K_WLOCK(payouts_free); + p2_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + DATA_PAYOUTS(payouts2, p2_item); + bzero(payouts2, sizeof(*payouts2)); + payouts2->payoutid = payouts->payoutid; + payouts2->height = payouts->height; + STRNCPY(payouts2->blockhash, payouts->blockhash); + payouts2->minerreward = payouts->minerreward; + payouts2->workinfoidstart = payouts->workinfoidstart; + payouts2->workinfoidend = payouts->workinfoidend; + payouts2->elapsed = payouts->elapsed; + STRNCPY(payouts2->status, PAYOUTS_GENERATED_STR); + payouts2->diffwanted = payouts->diffwanted; + payouts2->diffused = payouts->diffused; + payouts2->shareacc = payouts->shareacc; + copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); + payouts2->stats = strdup(payouts->stats); + + setnow(&now); + /* N.B. the PROCESSING payouts could have expirydate = createdate + * if the code above executes faster than the pgsql time resolution */ + ok = payouts_add(conn, true, p2_item, &old_p2_item, (char *)by_default, + (char *)__func__, (char *)inet_default, &now, NULL, + false); + if (!ok) { + LOGEMERG("%s(): payout %"PRId64" for block %"PRId32"/%s " + "NOT set generated - it needs to be set manually", + __func__, payouts->payoutid, blocks->height, + blocks->blockhash); + } + + CKPQDisco(&conn, conned); + + goto oku; + +shazbot: + if (begun) + CKPQEnd(conn, false); + CKPQDisco(&conn, conned); + + if (p_item) { + K_WLOCK(payouts_free); + k_add_head(payouts_free, p_item); + K_WUNLOCK(payouts_free); + } + +oku: + ; + ck_wunlock(&process_pplns_lock); + if (mu_root) + mu_root = free_ktree(mu_root, NULL); + if (mu_store) { + if (mu_store->count) { + K_WLOCK(mu_store); + k_list_transfer_to_head(mu_store, miningpayouts_free); + K_WUNLOCK(mu_store); + } + mu_store = k_free_store(mu_store); + } + if (pay_store) { + if (pay_store->count) { + K_WLOCK(pay_store); + k_list_transfer_to_head(pay_store, payments_free); + K_WUNLOCK(pay_store); + } + pay_store = k_free_store(pay_store); + } + if (addr_store) { + if (addr_store->count) { + K_WLOCK(addr_store); + k_list_transfer_to_head(addr_store, paymentaddresses_free); + K_WUNLOCK(addr_store); + } + addr_store = k_free_store(addr_store); + } + return; +} + // order by userid asc,createdate asc,authid asc,expirydate desc cmp_t cmp_auths(K_ITEM *a, K_ITEM *b) { diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index 4f29c233..73573095 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -167,6 +167,8 @@ char *pqerrmsg(PGconn *conn) #define PQPARAM14 PQPARAM8 ",$9,$10,$11,$12,$13,$14" #define PQPARAM15 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15" #define PQPARAM16 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15,$16" +#define PQPARAM17 PQPARAM16 ",$17" +#define PQPARAM18 PQPARAM16 ",$17,$18" #define PQPARAM22 PQPARAM16 ",$17,$18,$19,$20,$21,$22" #define PQPARAM26 PQPARAM22 ",$23,$24,$25,$26" #define PQPARAM27 PQPARAM26 ",$27" @@ -222,6 +224,70 @@ PGresult *_CKPQexecParams(PGconn *conn, const char *qry, #define PQexec CKPQexec #define PQexecParams CKPQexecParams +// TODO: switch all to use this +bool CKPQConn(PGconn **conn) +{ + if (*conn == NULL) { + LOGDEBUG("%s(): connecting", __func__); + *conn = dbconnect(); + return true; + } + return false; +} + +// TODO: switch all to use this +void CKPQDisco(PGconn **conn, bool conned) +{ + if (conned) { + LOGDEBUG("%s(): disco", __func__); + PQfinish(*conn); + } +} + +// TODO: switch all to use this +bool _CKPQBegin(PGconn *conn, WHERE_FFL_ARGS) +{ + ExecStatusType rescode; + PGresult *res; + + res = PQexec(conn, "Begin", CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + char *buf = pqerrmsg(conn); + LOGEMERG("%s(): Begin failed (%d) '%s'" WHERE_FFL, + __func__, (int)rescode, buf, WHERE_FFL_PASS); + free(buf); + return false; + } + LOGDEBUG("%s(): begin", __func__); + return true; +} + +// TODO: switch all to use this +void _CKPQEnd(PGconn *conn, bool commit, WHERE_FFL_ARGS) +{ + ExecStatusType rescode; + PGresult *res; + + if (commit) { + LOGDEBUG("%s(): commit", __func__); + res = PQexec(conn, "Commit", CKPQ_WRITE); + } else { + LOGDEBUG("%s(): rollback", __func__); + res = PQexec(conn, "Rollback", CKPQ_WRITE); + } + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + char *buf = pqerrmsg(conn); + LOGEMERG("%s(): %s failed (%d) '%s'" WHERE_FFL, + __func__, commit ? "commit" : "rollback", + (int)rescode, buf, WHERE_FFL_PASS); + free(buf); + } +} + int64_t nextid(PGconn *conn, char *idname, int64_t increment, tv_t *cd, char *by, char *code, char *inet) { @@ -1642,9 +1708,13 @@ unparam: n++; paymentaddresses_root = remove_from_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_create_root = remove_from_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); copy_tv(&(row->expirydate), cd); paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); } item = prev; DATA_PAYMENTADDRESSES_NULL(row, item); @@ -1658,6 +1728,8 @@ unparam: if (!pa->match) { paymentaddresses_root = add_to_ktree(paymentaddresses_root, match, cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + match, cmp_payaddr_create); k_unlink_item(pa_store, match); k_add_head(paymentaddresses_store, match); count++; @@ -1745,7 +1817,10 @@ bool paymentaddresses_fill(PGconn *conn) if (!ok) break; - paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, + cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); k_add_head(paymentaddresses_store, item); } if (!ok) @@ -1762,30 +1837,169 @@ bool paymentaddresses_fill(PGconn *conn) return ok; } +// The timing of the memory table updates depends on 'already' +void payments_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, tv_t *cd) +{ + PAYMENTS *oldp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(payments_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(payments_free, p_item); + } else { + if (old_p_item) { + DATA_PAYMENTS(oldp, old_p_item); + payments_root = remove_from_ktree(payments_root, old_p_item, cmp_payments); + copy_tv(&(oldp->expirydate), cd); + payments_root = add_to_ktree(payments_root, old_p_item, cmp_payments); + } + payments_root = add_to_ktree(payments_root, p_item, cmp_payments); + k_add_head(payments_store, p_item); + } + K_WUNLOCK(payments_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +bool payments_add(PGconn *conn, bool add, K_ITEM *p_item, K_ITEM **old_p_item, + char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, + bool already) +{ + ExecStatusType rescode; + bool conned = false; + PGresult *res; + bool ok = false, begun = false; + PAYMENTS *row, *oldp = NULL; + char *upd, *ins; + char *params[11 + HISTORYDATECOUNT]; + int n, par = 0; + + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); + + DATA_PAYMENTS(row, p_item); + K_RLOCK(payments_free); + *old_p_item = find_payments(row->payoutid, row->userid, row->subname); + K_RUNLOCK(payments_free); + + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; + } + + if (*old_p_item) { + LOGDEBUG("%s(): updating old", __func__); + + DATA_PAYMENTS(oldp, *old_p_item); + + upd = "update payments set expirydate=$1 where paymentid=$2" + " and expirydate=$3"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldp->paymentid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 3, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; + + // Expiring an old record + row->paymentid = oldp->paymentid; + } else { + if (add) { + // Creating a new record + row->paymentid = nextid(conn, "paymentid", (int64_t)1, cd, by, code, inet); + if (row->paymentid == 0) + goto rollback; + } + } + + if (add) { + LOGDEBUG("%s(): adding new", __func__); + + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); + + par = 0; + params[par++] = bigint_to_buf(row->paymentid, NULL, 0); + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = bigint_to_buf(row->userid, NULL, 0); + params[par++] = str_to_buf(row->subname, NULL, 0); + params[par++] = tv_to_buf(&(row->paydate), NULL, 0); + params[par++] = str_to_buf(row->payaddress, NULL, 0); + params[par++] = str_to_buf(row->originaltxn, NULL, 0); + params[par++] = bigint_to_buf(row->amount, NULL, 0); + params[par++] = double_to_buf(row->diffacc, NULL, 0); + params[par++] = str_to_buf(row->committxn, NULL, 0); + params[par++] = str_to_buf(row->commitblockhash, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into payments " + "(paymentid,payoutid,userid,subname,paydate,payaddress," + "originaltxn,amount,diffacc,committxn,commitblockhash" + HISTORYDATECONTROL ") values (" PQPARAM16 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } + } + + ok = true; +rollback: + if (begun) + CKPQEnd(conn, ok); +unparam: + for (n = 0; n < par; n++) + free(params[n]); + + CKPQDisco(&conn, conned); + + if (!already) + payments_add_ram(ok, p_item, *old_p_item, cd); + + return ok; +} + bool payments_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; PAYMENTS *row; - char *params[1]; - int n, i, par = 0; + int n, i; char *field; char *sel; - int fields = 8; + int fields = 11; bool ok; LOGDEBUG("%s(): select", __func__); - // TODO: handle selecting a subset, eg 20 per web page (in blocklist also) sel = "select " - "userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" + "paymentid,payoutid,userid,subname,paydate,payaddress," + "originaltxn,amount,diffacc,committxn,commitblockhash" HISTORYDATECONTROL - ",paymentid from payments where expirydate=$1"; - par = 0; - params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); - PARCHK(par, params); - res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); + " from payments"; + res = PQexec(conn, sel, CKPQ_READ); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); @@ -1814,11 +2028,26 @@ bool payments_fill(PGconn *conn) break; } + PQ_GET_FLD(res, i, "paymentid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("paymentid", field, row->paymentid); + + PQ_GET_FLD(res, i, "payoutid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("payoutid", field, row->payoutid); + PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); + PQ_GET_FLD(res, i, "subname", field, ok); + if (!ok) + break; + TXT_TO_STR("subname", field, row->subname); + PQ_GET_FLD(res, i, "paydate", field, ok); if (!ok) break; @@ -1839,6 +2068,11 @@ bool payments_fill(PGconn *conn) break; TXT_TO_BIGINT("amount", field, row->amount); + PQ_GET_FLD(res, i, "diffacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffacc", field, row->diffacc); + PQ_GET_FLD(res, i, "committxn", field, ok); if (!ok) break; @@ -1853,11 +2087,6 @@ bool payments_fill(PGconn *conn) if (!ok) break; - PQ_GET_FLD(res, i, "paymentid", field, ok); - if (!ok) - break; - TXT_TO_BIGINT("paymentid", field, row->paymentid); - payments_root = add_to_ktree(payments_root, item, cmp_payments); k_add_head(payments_store, item); } @@ -3653,7 +3882,7 @@ bool blocks_stats(PGconn *conn, int32_t height, char *blockhash, dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); K_RLOCK(blocks_free); - old_b_item = find_blocks(height, blockhash); + old_b_item = find_blocks(height, blockhash, NULL); K_RUNLOCK(blocks_free); if (!old_b_item) { @@ -3811,7 +4040,7 @@ bool blocks_add(PGconn *conn, char *height, char *blockhash, dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); K_RLOCK(blocks_free); - old_b_item = find_blocks(row->height, blockhash); + old_b_item = find_blocks(row->height, blockhash, NULL); K_RUNLOCK(blocks_free); DATA_BLOCKS_NULL(oldblocks, old_b_item); @@ -4297,96 +4526,128 @@ bool blocks_fill(PGconn *conn) return ok; } -bool miningpayouts_add(PGconn *conn, char *username, char *height, - char *blockhash, char *amount, char *by, - char *code, char *inet, tv_t *cd, K_TREE *trf_root) +// The timing of the memory table updates depends on 'already' +void miningpayouts_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, tv_t *cd) +{ + MININGPAYOUTS *oldmp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(miningpayouts_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(miningpayouts_free, mp_item); + } else { + if (old_mp_item) { + DATA_MININGPAYOUTS(oldmp, old_mp_item); + miningpayouts_root = remove_from_ktree(miningpayouts_root, old_mp_item, cmp_miningpayouts); + copy_tv(&(oldmp->expirydate), cd); + miningpayouts_root = add_to_ktree(miningpayouts_root, old_mp_item, cmp_miningpayouts); + } + miningpayouts_root = add_to_ktree(miningpayouts_root, mp_item, cmp_miningpayouts); + k_add_head(miningpayouts_store, mp_item); + } + K_WUNLOCK(miningpayouts_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +bool miningpayouts_add(PGconn *conn, bool add, K_ITEM *mp_item, + K_ITEM **old_mp_item, char *by, char *code, char *inet, + tv_t *cd, K_TREE *trf_root, bool already) { ExecStatusType rescode; bool conned = false; PGresult *res; - K_ITEM *m_item, *u_item; - bool ok = false; - MININGPAYOUTS *row; - USERS *users; - char *ins; - char *params[5 + HISTORYDATECOUNT]; + bool ok = false, begun = false; + MININGPAYOUTS *row, *oldmp = NULL; + char *upd, *ins; + char *params[4 + HISTORYDATECOUNT]; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); - K_WLOCK(miningpayouts_free); - m_item = k_unlink_head(miningpayouts_free); - K_WUNLOCK(miningpayouts_free); - - DATA_MININGPAYOUTS(row, m_item); + DATA_MININGPAYOUTS(row, mp_item); + K_RLOCK(miningpayouts_free); + *old_mp_item = find_miningpayouts(row->payoutid, row->userid); + K_RUNLOCK(miningpayouts_free); - if (conn == NULL) { - conn = dbconnect(); - conned = true; + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; } + + if (*old_mp_item) { + LOGDEBUG("%s(): updating old", __func__); - row->miningpayoutid = nextid(conn, "miningpayoutid", (int64_t)1, cd, by, code, inet); - if (row->miningpayoutid == 0) - goto unitem; + DATA_MININGPAYOUTS(oldmp, *old_mp_item); - K_RLOCK(users_free); - u_item = find_users(username); - K_RUNLOCK(users_free); - if (!u_item) { - char *txt; - LOGERR("%s(): unknown user '%s'", - __func__, - txt = safe_text(username)); - free(txt); - goto unitem; + upd = "update miningpayouts set expirydate=$1 where payoutid=$2" + " and userid=$3 and expirydate=$4"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldmp->payoutid, NULL, 0); + params[par++] = bigint_to_buf(oldmp->userid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 4, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; } - DATA_USERS(users, u_item); - row->userid = users->userid; - TXT_TO_INT("height", height, row->height); - STRNCPY(row->blockhash, blockhash); - TXT_TO_BIGINT("amount", amount, row->amount); + if (add) { + LOGDEBUG("%s(): adding new", __func__); - HISTORYDATEINIT(row, cd, by, code, inet); - HISTORYDATETRANSFER(trf_root, row); + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); - par = 0; - params[par++] = bigint_to_buf(row->miningpayoutid, NULL, 0); - params[par++] = bigint_to_buf(row->userid, NULL, 0); - params[par++] = int_to_buf(row->height, NULL, 0); - params[par++] = str_to_buf(row->blockhash, NULL, 0); - params[par++] = bigint_to_buf(row->amount, NULL, 0); - HISTORYDATEPARAMS(params, par, row); - PARCHK(par, params); + par = 0; + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = bigint_to_buf(row->userid, NULL, 0); + params[par++] = double_to_buf(row->diffacc, NULL, 0); + params[par++] = bigint_to_buf(row->amount, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); - ins = "insert into miningpayouts " - "(miningpayoutid,userid,height,blockhash,amount" - HISTORYDATECONTROL ") values (" PQPARAM10 ")"; + ins = "insert into miningpayouts " + "(payoutid,userid,diffacc,amount" + HISTORYDATECONTROL ") values (" PQPARAM9 ")"; - res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); - rescode = PQresultStatus(res); - if (!PGOK(rescode)) { - PGLOGERR("Insert", rescode, conn); - goto unparam; + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } } ok = true; - +rollback: + if (begun) + CKPQEnd(conn, ok); unparam: - PQclear(res); for (n = 0; n < par; n++) free(params[n]); -unitem: - if (conned) - PQfinish(conn); - K_WLOCK(miningpayouts_free); - if (!ok) - k_add_head(miningpayouts_free, m_item); - else { - miningpayouts_root = add_to_ktree(miningpayouts_root, m_item, cmp_miningpayouts); - k_add_head(miningpayouts_store, m_item); - } - K_WUNLOCK(miningpayouts_free); + + CKPQDisco(&conn, conned); + + if (!already) + miningpayouts_add_ram(ok, mp_item, *old_mp_item, cd); return ok; } @@ -4397,23 +4658,19 @@ bool miningpayouts_fill(PGconn *conn) PGresult *res; K_ITEM *item; MININGPAYOUTS *row; - char *params[1]; - int n, i, par = 0; + int n, i; char *field; char *sel; - int fields = 5; + int fields = 4; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " - "miningpayoutid,userid,height,blockhash,amount" + "payoutid,userid,diffacc,amount" HISTORYDATECONTROL - " from miningpayouts where expirydate=$1"; - par = 0; - params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); - PARCHK(par, params); - res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); + " from miningpayouts"; + res = PQexec(conn, sel, CKPQ_READ); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); @@ -4442,25 +4699,20 @@ bool miningpayouts_fill(PGconn *conn) break; } - PQ_GET_FLD(res, i, "miningpayoutid", field, ok); + PQ_GET_FLD(res, i, "payoutid", field, ok); if (!ok) break; - TXT_TO_BIGINT("miningpayoutid", field, row->miningpayoutid); + TXT_TO_BIGINT("payoutid", field, row->payoutid); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); - PQ_GET_FLD(res, i, "height", field, ok); - if (!ok) - break; - TXT_TO_INT("height", field, row->height); - - PQ_GET_FLD(res, i, "blockhash", field, ok); + PQ_GET_FLD(res, i, "diffacc", field, ok); if (!ok) break; - TXT_TO_STR("blockhash", field, row->blockhash); + TXT_TO_DOUBLE("diffacc", field, row->diffacc); PQ_GET_FLD(res, i, "amount", field, ok); if (!ok) @@ -4490,6 +4742,292 @@ bool miningpayouts_fill(PGconn *conn) return ok; } +// The timing of the memory table updates depends on 'already' +void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, tv_t *cd) +{ + PAYOUTS *oldp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(payouts_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(payouts_free, p_item); + } else { + if (old_p_item) { + DATA_PAYOUTS(oldp, old_p_item); + payouts_root = remove_from_ktree(payouts_root, old_p_item, cmp_payouts); + payouts_id_root = remove_from_ktree(payouts_id_root, old_p_item, cmp_payouts_id); + copy_tv(&(oldp->expirydate), cd); + payouts_root = add_to_ktree(payouts_root, old_p_item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, old_p_item, cmp_payouts_id); + } + payouts_root = add_to_ktree(payouts_root, p_item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, p_item, cmp_payouts_id); + k_add_head(payouts_store, p_item); + } + K_WUNLOCK(payouts_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +bool payouts_add(PGconn *conn, bool add, K_ITEM *p_item, K_ITEM **old_p_item, + char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, + bool already) +{ + ExecStatusType rescode; + bool conned = false; + PGresult *res; + bool ok = false, begun = false; + PAYOUTS *row, *oldpayouts = NULL; + char *upd, *ins; + char *params[13 + HISTORYDATECOUNT]; + int n, par = 0; + + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); + + DATA_PAYOUTS(row, p_item); + K_RLOCK(payouts_free); + *old_p_item = find_payouts(row->height, row->blockhash); + K_RUNLOCK(payouts_free); + + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; + } + + if (*old_p_item) { + LOGDEBUG("%s(): updating old", __func__); + + DATA_PAYOUTS(oldpayouts, *old_p_item); + + upd = "update payouts set expirydate=$1 where payoutid=$2" + " and expirydate=$3"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldpayouts->payoutid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 3, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; + + // Expiring an old record + row->payoutid = oldpayouts->payoutid; + } else { + if (add) { + // Creating a new record + row->payoutid = nextid(conn, "payoutid", (int64_t)1, cd, by, code, inet); + if (row->payoutid == 0) + goto rollback; + } + } + + if (add) { + LOGDEBUG("%s(): adding new", __func__); + + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); + + par = 0; + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = int_to_buf(row->height, NULL, 0); + params[par++] = str_to_buf(row->blockhash, NULL, 0); + params[par++] = bigint_to_buf(row->minerreward, NULL, 0); + params[par++] = bigint_to_buf(row->workinfoidstart, NULL, 0); + params[par++] = bigint_to_buf(row->workinfoidend, NULL, 0); + params[par++] = bigint_to_buf(row->elapsed, NULL, 0); + params[par++] = str_to_buf(row->status, NULL, 0); + params[par++] = double_to_buf(row->diffwanted, NULL, 0); + params[par++] = double_to_buf(row->diffused, NULL, 0); + params[par++] = double_to_buf(row->shareacc, NULL, 0); + params[par++] = tv_to_buf(&(row->lastshareacc), NULL, 0); + params[par++] = str_to_buf(row->stats, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into payouts " + "(payoutid,height,blockhash,minerreward,workinfoidstart," + "workinfoidend,elapsed,status,diffwanted,diffused,shareacc," + "lastshareacc,stats" + HISTORYDATECONTROL ") values (" PQPARAM18 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } + } + + ok = true; +rollback: + if (begun) + CKPQEnd(conn, ok); +unparam: + for (n = 0; n < par; n++) + free(params[n]); + + CKPQDisco(&conn, conned); + + if (!already) + payouts_add_ram(ok, p_item, *old_p_item, cd); + + return ok; +} + +bool payouts_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + PAYOUTS *row; + int n, i; + char *field; + char *sel; + int fields = 13; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + sel = "select " + "payoutid,height,blockhash,minerreward,workinfoidstart,workinfoidend," + "elapsed,status,diffwanted,diffused,shareacc,lastshareacc,stats" + HISTORYDATECONTROL + " from payouts"; + res = PQexec(conn, sel, CKPQ_READ); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + K_WLOCK(payouts_free); + for (i = 0; i < n; i++) { + item = k_unlink_head(payouts_free); + DATA_PAYOUTS(row, item); + + if (everyone_die) { + ok = false; + break; + } + + PQ_GET_FLD(res, i, "payoutid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("payoutid", field, row->payoutid); + + PQ_GET_FLD(res, i, "height", field, ok); + if (!ok) + break; + TXT_TO_INT("height", field, row->height); + + PQ_GET_FLD(res, i, "blockhash", field, ok); + if (!ok) + break; + TXT_TO_STR("blockhash", field, row->blockhash); + + PQ_GET_FLD(res, i, "minerreward", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("minerreward", field, row->minerreward); + + PQ_GET_FLD(res, i, "workinfoidstart", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidstart", field, row->workinfoidstart); + + PQ_GET_FLD(res, i, "workinfoidend", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidend", field, row->workinfoidend); + + PQ_GET_FLD(res, i, "elapsed", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("elapsed", field, row->elapsed); + + PQ_GET_FLD(res, i, "status", field, ok); + if (!ok) + break; + TXT_TO_STR("status", field, row->status); + + PQ_GET_FLD(res, i, "diffwanted", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffwanted", field, row->diffwanted); + + PQ_GET_FLD(res, i, "diffused", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffused", field, row->diffused); + + PQ_GET_FLD(res, i, "shareacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("shareacc", field, row->shareacc); + + PQ_GET_FLD(res, i, "lastshareacc", field, ok); + if (!ok) + break; + TXT_TO_TV("lastshareacc", field, row->lastshareacc); + + PQ_GET_FLD(res, i, "stats", field, ok); + if (!ok) + break; + TXT_TO_BLOB("stats", field, row->stats); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + payouts_root = add_to_ktree(payouts_root, item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, item, cmp_payouts_id); + k_add_head(payouts_store, item); + + tick(); + } + if (!ok) + k_add_head(payouts_free, item); + + K_WUNLOCK(payouts_free); + PQclear(res); + + if (ok) { + LOGDEBUG("%s(): built", __func__); + LOGWARNING("%s(): loaded %d payout records", __func__, n); + } + + return ok; +} + // TODO: discard them from RAM bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, @@ -5312,7 +5850,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, bool ok = false, begun = false; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); if (markerid == 0) { K_RLOCK(workmarkers_free); @@ -5324,6 +5863,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, K_RUNLOCK(workmarkers_free); } if (old_wm_item) { + LOGDEBUG("%s(): updating old", __func__); + DATA_WORKMARKERS(oldworkmarkers, old_wm_item); if (!conn) { conn = dbconnect(); @@ -5363,6 +5904,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, } if (add) { + LOGDEBUG("%s(): adding new", __func__); + if (poolinstance == NULL || description == NULL || status == NULL) { LOGEMERG("%s(): NULL field(s) passed:%s%s%s" @@ -5639,12 +6182,14 @@ bool _marks_process(PGconn *conn, bool add, char *poolinstance, bool ok = false, begun = false; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c", __func__, add ? 'Y' : 'N'); K_RLOCK(marks_free); old_m_item = find_marks(workinfoid); K_RUNLOCK(marks_free); if (old_m_item) { + LOGDEBUG("%s(): updating old", __func__); + DATA_MARKS(oldmarks, old_m_item); if (!conn) { conn = dbconnect(); @@ -5682,6 +6227,8 @@ bool _marks_process(PGconn *conn, bool add, char *poolinstance, } if (add) { + LOGDEBUG("%s(): adding new", __func__); + if (poolinstance == NULL || description == NULL || extra == NULL || marktype == NULL || status == NULL) { LOGEMERG("%s(): NULL field(s) passed:%s%s%s%s%s" From 1380acddfb085d06b708ec3bb871e201f08ba3bf Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 00:06:46 +1100 Subject: [PATCH 2/8] ckdb - cmd_payouts for setting up old payouts --- src/ckdb.c | 2 + src/ckdb.h | 7 ++- src/ckdb_cmd.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++- src/ckdb_data.c | 51 +++++++-------- 4 files changed, 193 insertions(+), 28 deletions(-) diff --git a/src/ckdb.c b/src/ckdb.c index 4dca76ad..222682db 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -2662,6 +2662,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_PAYMENTS: case CMD_PPLNS: case CMD_PPLNS2: + case CMD_PAYOUTS: case CMD_DSP: case CMD_BLOCKSTATUS: if (!startup_complete) { @@ -2880,6 +2881,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_STATS: case CMD_PPLNS: case CMD_PPLNS2: + case CMD_PAYOUTS: case CMD_USERSTATUS: case CMD_MARKS: LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored", diff --git a/src/ckdb.h b/src/ckdb.h index 7f65e817..3a529a49 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.000" +#define CKDB_VERSION DB_VERSION"-1.001" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -340,7 +340,7 @@ enum cmd_values { CMD_STATS, CMD_PPLNS, CMD_PPLNS2, - CMD_PAYOUT, + CMD_PAYOUTS, CMD_USERSTATUS, CMD_MARKS, CMD_END @@ -1182,6 +1182,7 @@ extern K_LIST *payouts_free; extern K_STORE *payouts_store; extern cklock_t process_pplns_lock; +// N.B. status should be checked under r/w lock #define PAYOUTS_GENERATED 'G' #define PAYOUTS_GENERATED_STR "G" #define PAYGENERATED(_status) ((_status)[0] == PAYOUTS_GENERATED) @@ -1754,7 +1755,7 @@ extern cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_payouts(int32_t height, char *blockhash); extern K_ITEM *find_last_payouts(); extern K_ITEM *find_payoutid(int64_t payoutid); -extern void process_pplns(int32_t height, char *blockhash, tv_t *now); +extern bool process_pplns(int32_t height, char *blockhash, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b); extern void dsp_userstats(K_ITEM *item, FILE *stream); diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 5d1bcba0..c7f89e79 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4030,6 +4030,165 @@ shazbot: return strdup(reply); } +static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, + char *by, char *code, char *inet, + __maybe_unused tv_t *cd, K_TREE *trf_root) +{ + char reply[1024] = ""; + size_t siz = sizeof(reply); + char msg[1024] = ""; + K_ITEM *i_action, *i_payoutid, *i_height, *i_blockhash, *i_addrdate; + K_ITEM *p_item, *p2_item, *old_p2_item; + PAYOUTS *payouts, *payouts2, *old_payouts2; + char *action; + int64_t payoutid = -1; + int32_t height = 0; + char blockhash[TXT_BIG+1]; + tv_t addrdate; + bool ok = true; + + LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + + i_action = require_name(trf_root, "action", 1, NULL, reply, siz); + if (!i_action) + return strdup(reply); + action = transfer_data(i_action); + + if (strcasecmp(action, "generated") == 0) { + /* Change the status of a processing payout to generated + * Require payoutid + * Use this if the payout process completed but the end txn, + * that only updates the payout to generated, failed */ + i_payoutid = require_name(trf_root, "payoutid", 1, + (char *)intpatt, reply, siz); + if (!i_payoutid) + return strdup(reply); + TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); + + K_WLOCK(payouts_free); + p_item = find_payoutid(payoutid); + if (!p_item) { + K_WUNLOCK(payouts_free); + snprintf(reply, siz, + "no payout with id %"PRId64, payoutid); + return strdup(reply); + } + DATA_PAYOUTS(payouts, p_item); + if (!PAYPROCESSING(payouts->status)) { + K_WUNLOCK(payouts_free); + snprintf(reply, siz, + "status !processing (%s) for payout %"PRId64, + payouts->status, payoutid); + return strdup(reply); + } + p2_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + + /* There is a risk of the p_item changing while it's unlocked, + * but since this is a manual interface it's not really likely + * and there'll be an error if something goes wrong + * It reports the old and new status */ + DATA_PAYOUTS(payouts2, p2_item); + bzero(payouts2, sizeof(*payouts2)); + payouts2->payoutid = payouts->payoutid; + payouts2->height = payouts->height; + STRNCPY(payouts2->blockhash, payouts->blockhash); + payouts2->minerreward = payouts->minerreward; + payouts2->workinfoidstart = payouts->workinfoidstart; + payouts2->workinfoidend = payouts->workinfoidend; + payouts2->elapsed = payouts->elapsed; + STRNCPY(payouts2->status, PAYOUTS_GENERATED_STR); + payouts2->diffwanted = payouts->diffwanted; + payouts2->diffused = payouts->diffused; + payouts2->shareacc = payouts->shareacc; + copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); + payouts2->stats = strdup(payouts->stats); + + ok = payouts_add(conn, true, p2_item, &old_p2_item, + by, code, inet, now, NULL, false); + if (!ok) { + snprintf(reply, siz, "failed payout %"PRId64, payoutid); + return strdup(reply); + } + DATA_PAYOUTS(payouts2, p2_item); + DATA_PAYOUTS(old_payouts2, old_p2_item); + snprintf(msg, sizeof(msg), + "payout %"PRId64" changed from '%s' to '%s' for" + "%"PRId32"/%s", + payoutid, payouts2->status, old_payouts2->status, + payouts2->height, payouts2->blockhash); +/* + } else if (strcasecmp(action, "expire") == 0) { + / TODO: Expire the payout - effectively deletes it + * Require payoutid + * If any payments are paid then don't allow it / + i_payoutid = require_name(trf_root, "payoutid", 1, + (char *)intpatt, reply, siz); + if (!i_payoutid) + return strdup(reply); + TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); + + K_WLOCK(payouts_free); + p_item = find_payoutid(payoutid); + if (!p_item) { + K_WUNLOCK(payouts_free); + snprintf(reply, siz, + "no payout with id %"PRId64, payoutid); + return strdup(reply); + } + p2_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + + DATA_PAYOUTS(payouts2, p2_item); + bzero(payouts2, sizeof(*payouts2)); + payouts2->payoutid = payouts->payoutid; + + ... +*/ + } else if (strcasecmp(action, "process") == 0) { + /* Generate a payout + * Require height, blockhash and addrdate + * addrdate is an epoch integer + * and 0 means uses the default = block NEW createdate + * this is the date to use for payoutaddresses + * Check the console for processing messages */ + i_height = require_name(trf_root, "height", 6, + (char *)intpatt, reply, siz); + if (!i_height) + return strdup(reply); + TXT_TO_INT("height", transfer_data(i_height), height); + + i_blockhash = require_name(trf_root, "blockhash", 64, + (char *)hashpatt, reply, siz); + if (!i_blockhash) + return strdup(reply); + TXT_TO_STR("blockhash", transfer_data(i_blockhash), blockhash); + + i_addrdate = require_name(trf_root, "addrdate", 1, + (char *)intpatt, reply, siz); + if (!i_addrdate) + return strdup(reply); + TXT_TO_CTV("addrdate", transfer_data(i_addrdate), addrdate); + + if (addrdate.tv_sec == 0) + ok = process_pplns(height, blockhash, NULL); + else + ok = process_pplns(height, blockhash, &addrdate); + + } else { + snprintf(reply, siz, "unknown action '%s'", action); + LOGERR("%s.%s", id, reply); + return strdup(reply); + } + + snprintf(reply, siz, "%s.%s%s%s", + ok ? "ok" : "ERR", + action, + msg[0] ? " " : EMPTY, + msg[0] ? msg : EMPTY); + LOGWARNING("%s.%s", id, reply); + return strdup(reply); +} static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, @@ -4690,7 +4849,7 @@ struct CMDS ckdb_cmds[] = { { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, ACCESS_SYSTEM ACCESS_WEB }, -// { CMD_PAYOUT, "payout", false, false, cmd_payout, ACCESS_SYSTEM }, + { CMD_PAYOUTS, "payouts", false, false, cmd_payouts, ACCESS_SYSTEM }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, ACCESS_SYSTEM ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 3d227af9..ce38a11a 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2557,11 +2557,11 @@ K_ITEM *find_payoutid(int64_t payoutid) N.B. process_pplns() is only automatically triggered once after the block summarisation is verified, so it can always report all errors */ -void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) +bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) { K_TREE_CTX b_ctx[1], ss_ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1], mu_ctx[1]; bool allow_aged = true, conned = false, begun = false; - bool countbacklimit, ok; + bool countbacklimit, ok = false; PGconn *conn = NULL; MININGPAYOUTS *miningpayouts; OPTIONCONTROL *optioncontrol; @@ -2586,7 +2586,8 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) char ndiffbin[TXT_SML+1]; double diff_times, diff_add; char cd_buf[CDATE_BUFSIZ]; - tv_t begin_tv, end_tv, now; + tv_t end_tv = { 0L, 0L }; + tv_t begin_tv, now; char buf[1024]; /* @@ -2624,30 +2625,30 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) goto oku; } DATA_BLOCKS(blocks, b_item); - // If addr_cd is null, use the block NEW createdate - if (!addr_cd) { - b2_item = b_item; - DATA_BLOCKS(blocks2, b2_item); - while (b2_item && blocks2->height == height && - strcmp(blocks2->blockhash, blockhash) == 0) { - if (blocks2->confirmed[0] == BLOCKS_NEW) { + b2_item = b_item; + DATA_BLOCKS(blocks2, b2_item); + while (b2_item && blocks2->height == height && + strcmp(blocks2->blockhash, blockhash) == 0) { + if (blocks2->confirmed[0] == BLOCKS_NEW) { + copy_tv(&end_tv, &(blocks->createdate)); + if (!addr_cd) addr_cd = &(blocks2->createdate); - break; - } - b2_item = next_in_ktree(b_ctx); - DATA_BLOCKS_NULL(blocks2, b2_item); - } - if (!addr_cd) { - K_RUNLOCK(blocks_free); - LOGEMERG("%s(): missing NEW record for block %"PRId32 - "/%"PRId64"/%s/%s/%"PRId64, - __func__, blocks->height, blocks->workinfoid, - blocks->workername, blocks->confirmed, - blocks->reward); - goto oku; + break; } + b2_item = next_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks2, b2_item); } K_RUNLOCK(blocks_free); + // If addr_cd was null it should've been set to the block NEW createdate + if (!addr_cd || end_tv.tv_sec == 0) { + LOGEMERG("%s(): missing %s record for block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks_confirmed(BLOCKS_NEW_STR), + blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, __func__, blocks->height, blocks->workinfoid, @@ -3211,6 +3212,8 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) goto oku; shazbot: + ok = false; + if (begun) CKPQEnd(conn, false); CKPQDisco(&conn, conned); @@ -3250,7 +3253,7 @@ oku: } addr_store = k_free_store(addr_store); } - return; + return ok; } // order by userid asc,createdate asc,authid asc,expirydate desc From c451119f258c06c9d076ddb07c6bdab509b9a3e9 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 00:27:35 +1100 Subject: [PATCH 3/8] ckdb - correct status order in cmd_payouts message --- src/ckdb.h | 2 +- src/ckdb_cmd.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 3a529a49..53a09f2d 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.001" +#define CKDB_VERSION DB_VERSION"-1.002" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index c7f89e79..2bc67399 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4115,7 +4115,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, snprintf(msg, sizeof(msg), "payout %"PRId64" changed from '%s' to '%s' for" "%"PRId32"/%s", - payoutid, payouts2->status, old_payouts2->status, + payoutid, old_payouts2->status, payouts2->status, payouts2->height, payouts2->blockhash); /* } else if (strcasecmp(action, "expire") == 0) { From cda0724c6d993022ab2c9db37c1d679fc830dec6 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 11:56:32 +1100 Subject: [PATCH 4/8] =?UTF-8?q?ckdb=20-=20=E2=88=9E=20loops=20are=20bad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ckdb.h | 2 +- src/ckdb_data.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 53a09f2d..3481ba62 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.002" +#define CKDB_VERSION DB_VERSION"-1.003" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index ce38a11a..10f5625e 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -3032,10 +3032,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) STRNCPY(pa2->payaddress, pa->payaddress); pa2->payratio = pa->payratio; k_add_tail(addr_store, pa_item2); - - pa_item = next_in_ktree(pay_ctx); - DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } + pa_item = next_in_ktree(pay_ctx); + DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } } K_WUNLOCK(paymentaddresses_free); From 5afedffc5accb53580e938628c733c0af7050c94 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 13:10:07 +1100 Subject: [PATCH 5/8] ckdb - store missing diffacc in addressuser payments --- src/ckdb.h | 2 +- src/ckdb_cmd.c | 2 +- src/ckdb_data.c | 31 ++++++++++++++++++++++++------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 3481ba62..d817ef9b 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.003" +#define CKDB_VERSION DB_VERSION"-1.004" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 2bc67399..14606a80 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4150,7 +4150,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, * Require height, blockhash and addrdate * addrdate is an epoch integer * and 0 means uses the default = block NEW createdate - * this is the date to use for payoutaddresses + * this is the date to use to determine payoutaddresses * Check the console for processing messages */ i_height = require_name(trf_root, "height", 6, (char *)intpatt, reply, siz); diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 10f5625e..b835798a 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2527,13 +2527,15 @@ K_ITEM *find_payoutid(int64_t payoutid) then keep stepping back until we complete the current begin_workinfoid (also summarising diffacc per user) While we are still below diff_want - find each workmarker and add on the full set of worksummary + find each next workmarker and add on the full set of worksummary diffacc shares (also summarising diffacc per user) This will give us the total number of diff1 shares (diffacc_total) to use for the payment calculations The value of diff_want defaults to the block's network difficulty (block_ndiff) but can be changed with diff_times and diff_add to: block_ndiff * diff_times + diff_add + they are stored in the optioncontrol table and thus can use the + block number to change their values over time N.B. diff_times and diff_add can be zero, positive or negative The pplns_elapsed time of the shares is from the createdate of the begin_workinfoid that has shares accounted to the total, @@ -2547,12 +2549,19 @@ K_ITEM *find_payoutid(int64_t payoutid) 'end' should usually be the info of the found block with the pplns data going back in time to 'begin' - The data processing procedure is to create a separate tree/store of - miningpayouts, create a seperate store of payments, - then actually create the payout record and transfer the miningpayouts - and payments to the ram table and the db + The data processing procedure is to: + create a separate tree/store of miningpayouts during the diff_used + calculation, + store the payout in the db with a 'processing' status, + create a seperate store of payments per miningpayout that are stored + in the db, + store each mininging payout in the db after storing the payments for the + given miningpayout, + commit that all and if it succeeds then update the ram tables for all + of the above + then update the payout status, in the db and ram, to 'generated' - TODO: recheck the payout if it already exists + TODO: recheck the payout if it already exists? N.B. process_pplns() is only automatically triggered once after the block summarisation is verified, so it can always report all errors @@ -3014,6 +3023,13 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) pay_ctx); if (pa_item) { DATA_PAYMENTADDRESSES(pa, pa_item); + /* The tv_newer and tv_newer_eq are critical since: + * when a record is replaced, the expirydate is set + * to 'now' and the new record will have the same + * createdate of 'now', so to avoid possibly selecting + * both records, we get the one that was created + * before addr_cd and expires on or after addr_cd + */ while (pa_item && pa->userid == miningpayouts->userid && tv_newer(&(pa->createdate), addr_cd)) { if (tv_newer_eq(addr_cd, &(pa->expirydate))) { @@ -3102,8 +3118,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) "%s.0", users->username); STRNCPY(payments->payaddress, users->username); payments->amount = amount; + payments->diffacc = miningpayouts->diffacc; used = amount; - k_add_head(pay_store, pay_item); + k_add_tail(pay_store, pay_item); ok = payments_add(conn, true, pay_item, &(payments->old_item), (char *)by_default, From e30cc81cad143b168e4ff1088446247172aa882a Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 14:10:23 +1100 Subject: [PATCH 6/8] ckdb/php - add extra pplns2 epoch information like pplns --- pool/page_pplns2.php | 12 ++++---- src/ckdb.h | 2 +- src/ckdb_cmd.c | 71 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/pool/page_pplns2.php b/pool/page_pplns2.php index 01c4c4ca..781fab51 100644 --- a/pool/page_pplns2.php +++ b/pool/page_pplns2.php @@ -188,14 +188,14 @@ Block: 'Elapsed Seconds' => ',pplns_elapsed', 'Users' => 'rows', 'Oldest Workinfoid' => 'begin_workinfoid', -# 'Oldest Time' => 'begin_stamp', -# 'Oldest Epoch' => 'begin_epoch', + 'Oldest Time' => 'begin_stamp', + 'Oldest Epoch' => 'begin_epoch', 'Block Workinfoid' => 'block_workinfoid', -# 'Block Time' => 'block_stamp', -# 'Block Epoch' => 'block_epoch', + 'Block Time' => 'block_stamp', + 'Block Epoch' => 'block_epoch', 'Newest Workinfoid' => 'end_workinfoid', -# 'Newest Share Time' => 'end_stamp', -# 'Newest Share Epoch' => 'end_epoch', + 'Newest Share Time' => 'end_stamp', + 'Newest Share Epoch' => 'end_epoch', 'Network Difficulty' => 'block_ndiff', 'PPLNS Factor' => 'diff_times', 'PPLNS Added' => 'diff_add', diff --git a/src/ckdb.h b/src/ckdb.h index d817ef9b..b11bb0a9 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.004" +#define CKDB_VERSION DB_VERSION"-1.005" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 14606a80..a9c32c77 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -3828,13 +3828,19 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, size_t siz = sizeof(reply); K_ITEM *i_height; K_ITEM b_look, *b_item, *p_item, *mp_item, *pay_item, *u_item; + K_ITEM *w_item; MININGPAYOUTS *miningpayouts; PAYMENTS *payments; PAYOUTS *payouts; BLOCKS lookblocks, *blocks; + tv_t block_tv = { 0L, 0L }; + WORKINFO *bworkinfo, *workinfo; + char ndiffbin[TXT_SML+1]; + double ndiff; USERS *users; int32_t height; K_TREE_CTX b_ctx[1], mp_ctx[1], pay_ctx[1]; + char tv_buf[DATE_BUFSIZ]; size_t len, off; int rows; bool pok; @@ -3851,18 +3857,38 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, LOGDEBUG("%s(): height %"PRId32, __func__, height); - lookblocks.height = height; + lookblocks.height = height + 1; lookblocks.blockhash[0] = '\0'; INIT_BLOCKS(&b_look); b_look.data = (void *)(&lookblocks); K_RLOCK(blocks_free); - b_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + b_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + if (!b_item) { + K_RUNLOCK(blocks_free); + snprintf(reply, siz, "ERR.no block height %"PRId32, height); + return strdup(reply); + } + DATA_BLOCKS(blocks, b_item); + while (b_item && blocks->height == height) { + if (blocks->confirmed[0] == BLOCKS_NEW) + copy_tv(&block_tv, &(blocks->createdate)); + // Allow any state, but report it + if (CURRENT(&(blocks->expirydate))) + break; + b_item = prev_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks, b_item); + } K_RUNLOCK(blocks_free); - DATA_BLOCKS_NULL(blocks, b_item); if (!b_item || blocks->height != height) { snprintf(reply, siz, "ERR.no block height %"PRId32, height); return strdup(reply); } + if (block_tv.tv_sec == 0) { + snprintf(reply, siz, "ERR.block %"PRId32" missing '%s' record", + height, + blocks_confirmed(BLOCKS_NEW_STR)); + return strdup(reply); + } if (!CURRENT(&(blocks->expirydate))) { snprintf(reply, siz, "ERR.no CURRENT block %d"PRId32, height); return strdup(reply); @@ -3882,6 +3908,15 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, break; } + w_item = find_workinfo(blocks->workinfoid, NULL); + if (!w_item) { + snprintf(reply, siz, "ERR.missing block workinfo record!" + " %"PRId64, + blocks->workinfoid); + return strdup(reply); + } + DATA_WORKINFO(bworkinfo, w_item); + pok = false; K_RLOCK(payouts_free); p_item = find_payouts(height, blocks->blockhash); @@ -3905,6 +3940,15 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, LOGDEBUG("%s(): total %.1f want %.1f", __func__, payouts->diffused, payouts->diffwanted); + w_item = find_workinfo(payouts->workinfoidstart, NULL); + if (!w_item) { + snprintf(reply, siz, "ERR.missing begin workinfo record!" + " %"PRId64, + payouts->workinfoidstart); + return strdup(reply); + } + DATA_WORKINFO(workinfo, w_item); + APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP); @@ -4012,8 +4056,29 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, "Users", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + tv_to_buf(&(workinfo->createdate), tv_buf, sizeof(tv_buf)); + snprintf(tmp, sizeof(tmp), "begin_stamp=%s%c", tv_buf, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "begin_epoch=%ld%c", + workinfo->createdate.tv_sec, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + tv_to_buf(&block_tv, tv_buf, sizeof(tv_buf)); + snprintf(tmp, sizeof(tmp), "block_stamp=%s%c", tv_buf, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", block_tv.tv_sec, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + tv_to_buf(&(payouts->lastshareacc), tv_buf, sizeof(tv_buf)); + snprintf(tmp, sizeof(tmp), "end_stamp=%s%c", tv_buf, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "end_epoch=%ld%c", + payouts->lastshareacc.tv_sec, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%s%c", payouts->stats, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + hex2bin(ndiffbin, bworkinfo->bits, 4); + ndiff = diff_from_nbits(ndiffbin); + snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", ndiff, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_want=%.1f%c", payouts->diffwanted, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); From 59e7d2b7bc0de8d748630a77246fb5c3dc8d089e Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 16:33:29 +1100 Subject: [PATCH 7/8] ckdb - pplns2 include the trailing shares in the tv_end check --- src/ckdb.h | 2 +- src/ckdb_data.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ckdb.h b/src/ckdb.h index b11bb0a9..9e84b3da 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.005" +#define CKDB_VERSION DB_VERSION"-1.006" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index b835798a..f7cc3653 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2824,6 +2824,10 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) total_share_count += sharesummary->sharecount; acc_share_count += sharesummary->shareacc; total_diff += sharesummary->diffacc; + // TODO: add lastshareacc to sharesummary and markersummary + if (sharesummary->shareacc > 0 && + tv_newer(&end_tv, &(sharesummary->lastshare))) + copy_tv(&end_tv, &(sharesummary->lastshare)); mu_root = upd_add_mu(mu_root, mu_store, sharesummary->userid, sharesummary->diffacc); From 122d6f38bac92363457dab30e0082e8e2a1449e3 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 16:34:45 +1100 Subject: [PATCH 8/8] ckdb - correct tv_end for pplns2 block --- src/ckdb.h | 2 +- src/ckdb_data.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 9e84b3da..34c91f7c 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.006" +#define CKDB_VERSION DB_VERSION"-1.007" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index f7cc3653..96649aa8 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2639,7 +2639,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) while (b2_item && blocks2->height == height && strcmp(blocks2->blockhash, blockhash) == 0) { if (blocks2->confirmed[0] == BLOCKS_NEW) { - copy_tv(&end_tv, &(blocks->createdate)); + copy_tv(&end_tv, &(blocks2->createdate)); if (!addr_cd) addr_cd = &(blocks2->createdate); break;