diff --git a/README b/README index 0cf3c908..630b6e29 100644 --- a/README +++ b/README @@ -80,12 +80,15 @@ sudo apt-get install build-essential make -Building with ckdb requires installation of the postgresql development library. +Building with ckdb requires installation of the postgresql, gsl and ssl +development libraries. -sudo apt-get install build-essential libpq-dev +sudo apt-get install build-essential libpq-dev libgsl0ldbl libgsl0-dev ./configure make +N.B. ckdb also requires libssl-dev but libpq-dev depends on it and installs it + Building from git also requires autoconf and automake diff --git a/pool/address.php b/pool/address.php index 625f08fb..01c840cd 100644 --- a/pool/address.php +++ b/pool/address.php @@ -8,7 +8,8 @@ include_once('param.php'); function go() { $a = getparam('a', true); - if (substr($a, 0, 1) != '1') + $f = substr($a, 0, 1); + if ($f != '1' and $f != '3') return; if (strlen($a) < 24) return; diff --git a/pool/base.php b/pool/base.php index e388a538..828454a3 100644 --- a/pool/base.php +++ b/pool/base.php @@ -28,6 +28,42 @@ function dq($str) return str_replace('"', "\\\"", $str); } # +function daysago($val) +{ + if ($val < -13) + return ''; + + if ($val < 60) + $des = number_format($val,0).'s'; + else + { + $val = $val/60; + if ($val < 60) + $des = number_format($val,1).'min'; + else + { + $val = $val/60; + if ($val < 24) + $des = number_format($val,1).'hrs'; + else + { + $val = $val/24; + if ($val < 43) + $des = number_format($val,1).'days'; + else + { + $val = $val/7; + if ($val < 10000) + $des = number_format($val,1).'wks'; + else + $des = ''; + } + } + } + } + return $des; +} +# function howlongago($sec) { if ($sec < 60) diff --git a/pool/email.php b/pool/email.php index db4f5540..1e1d36f2 100644 --- a/pool/email.php +++ b/pool/email.php @@ -186,7 +186,7 @@ function twofaSetup($to, $whoip, $emailinfo) return false; $message = "2FA is ready to be tested.$eol"; - $message = "It will be enabled once you test it.$eol$eol"; + $message .= "It will be enabled once you test it.$eol$eol"; $message .= $ret; return sendnoheader($to, "2FA is Ready to be Enabled", $message, $emailinfo); @@ -211,6 +211,46 @@ function twofaEnabled($to, $whoip, $emailinfo) return sendnoheader($to, "2FA is Enabled", $message, $emailinfo); } # +function twofaCancel($to, $whoip, $emailinfo) +{ + global $eol; + + if (!isset($emailinfo['KWebURL'])) + return false; + + $web = $emailinfo['KWebURL']; + + $ret = emailEnd('2fa change', $whoip, $emailinfo); + if ($ret === false) + return false; + + $message = "2FA setup was cancelled on your account.$eol"; + $message .= "You can set it up later if you want.$eol$eol"; + $message .= $ret; + + return sendnoheader($to, "2FA was Cancelled", $message, $emailinfo); +} +# +function twofaRemove($to, $whoip, $emailinfo) +{ + global $eol; + + if (!isset($emailinfo['KWebURL'])) + return false; + + $web = $emailinfo['KWebURL']; + + $ret = emailEnd('2fa change', $whoip, $emailinfo); + if ($ret === false) + return false; + + $message = "2FA was removed from your account.$eol"; + $message .= "You can set it up again later if you want.$eol$eol"; + $message .= $ret; + + return sendnoheader($to, "2FA was Removed", $message, $emailinfo); +} +# # getOpts required for email # If they aren't all setup in the DB then email functions will return false function emailOptList() diff --git a/pool/inc.php b/pool/inc.php index 75d91b97..c855da5f 100644 --- a/pool/inc.php +++ b/pool/inc.php @@ -6,6 +6,7 @@ function GBaseJS() function sep(d){ans={};var ar=d.split('\\t');var l=ar.length;for(var i=0;i(zm-0.5)){return(zm-0.5)}return z} function gchx(c,x){return gch(x*c['xm']+c['xo'],c['ctx'].canvas.width)} function gchy(c,y){return gch((1-y)*c['ym']+c['yo'],c['ctx'].canvas.height)} @@ -27,7 +28,7 @@ function gfl(c){c['ctx'].fill()} function gst(c){c['ctx'].stroke()} function gfi(c){gle(c);gst(c)} function gbd(c){gbe(c,0,0);gln(c,1,0);gln(c,1,1);gln(c,0,1);gle(c);gfl(c);gst(c)} -function ggr(c,xs,ys,yt,xn,x0,x1,y0,y1,ar,nx,vx,vy,av,w,cols){gtso(c,xs,ys);gss(c,'black');glw(c,1.5);gbe(c,0,1);gln(c,0,0);gln(c,1,0);gst(c);glw(c,0.2);var hi=c['ctx'].measureText('M').width, wi=c['ctx'].measureText('1').width;for(var i=0;i<11;i++){var y=i/10.0;gbe(c,-0.01,y);gln(c,1,y);gst(c);var t=''+(((y1-y0)*i/10+y0).toFixed(2));gfz(c,0,y,-wi,0,t,'black','end')}gfz(c,gx0(c),0.55,wi,0,yt,'#0080ff','left');var m=Math.round(0.5+xn/20.0);for(var i=0;i=x0;i-=hrs){var n=dfmt(c,i);var xo=(i-x0)/(x1-x0);if(xo<=1 && c['tkey'] && ((l%hlv)==0)){gbe(c,xo,0);gln(c,xo,-0.02);gst(c);gfz(c,xo,0,0,-hi*tpos,n,'brown','center')}if(xo<=1 && c['tlines']){gbe(c,xo,0);gln(c,xo,1);gst(c)}l++}}glw(c,1);if(c['smooth']){for(var j=1;j0){gss(c,'red');var y=(av[j-1]-y0)/(y1-y0);gbe(c,0,y);gln(c,1,y);gst(c);var t=''+av[j-1].toFixed(2)+'av';gfz(c,1,y,1,0,t,cols[j-1],'left')}}if(c['tkey']){var col,hrl=c['hrs'].length;for(var i=0;i=x0;i-=hrs){var n=dfmt(c,i);var xo=(i-x0)/(x1-x0);if(xo<=1 && c['tkey'] && ((l%hlv)==0)){gbe(c,xo,0);gln(c,xo,-0.02);gst(c);gfz(c,xo,0,0,-hi*tpos,n,'brown','center')}if(xo<=1 && c['tlines']){gbe(c,xo,0);gln(c,xo,1);gst(c)}l++}}glw(c,1);if(c['smooth']){for(var j=1;j0 && c['lin'+j]){gss(c,'red');var y=(av[j-1]-y0)/(y1-y0);gbe(c,0,y);gln(c,1,y);gst(c);var t=''+av[j-1].toFixed(2)+'av';gfz(c,1,y,1,0,t,cols[j-1],'left')}}if(c['tkey']){var col,hrl=c['hrs'].length;for(var i=0;ic['xm']){c['ym']=c['xm']}gc2(c)} @@ -36,6 +37,7 @@ function opts(t,i){var e=document.getElementById(i);if(t.checked){e.style.visibi function ghrs(c){c['hrs']=[1,2,3,4,6,8,12,24,48];c['hln']=[1,2,3,4,6]} function ghg(c,dx){var tl=dx/(gchx(c,1)/50)/3600;for(var j=c['hrs'].length-1;j>=0;j--){if(tl"; + $ret .= "".makeLink($item)."$menu"; $ret .= '
'; continue; } diff --git a/pool/page_2fa.php b/pool/page_2fa.php index 4ec7330a..6687846d 100644 --- a/pool/page_2fa.php +++ b/pool/page_2fa.php @@ -11,7 +11,7 @@ function app_txt($ones) return $app; } # -function set_2fa($data, $user, $tfa, $ans, $err) +function set_2fa($data, $user, $tfa, $ans, $err, $msg) { $draw = false; @@ -20,10 +20,12 @@ function set_2fa($data, $user, $tfa, $ans, $err) if ($err !== null and $err != '') $pg .= "$err

"; + if ($msg !== null and $msg != '') + $pg .= "$msg

"; + $pg .= '
'; $pg .= '
'; - $pg .= makeForm('2fa'); $pg .= ''; $pg .= ''; + $pg .= ''; break; case 'test': $pg .= ''; + $pg .= ''; $pg .= ''; + $pg .= ''; break; case 'ok': $pg .= ''; + $pg .= ''; break; } - $pg .= '
'; switch ($tfa) @@ -33,9 +35,10 @@ function set_2fa($data, $user, $tfa, $ans, $err) $pg .= "You don't have Two Factor Authentication (2FA) setup yet

"; $pg .= 'To use 2FA you need an App on your phone/tablet
'; $pg .= app_txt('ones'); + $pg .= makeForm('2fa'); $pg .= 'Click here to begin the setup process for 2FA: '; $pg .= ''; - $pg .= '
'; @@ -66,8 +69,9 @@ function set_2fa($data, $user, $tfa, $ans, $err) $pg .= '
'; $pg .= 'A qrcode will show here if your browser supports html5/canvas'; $pg .= "

"; + $pg .= makeForm('2fa'); $pg .= 'Then enter your App 2FA Value: '; - $pg .= '
'; $pg .= app_txt('2FA apps'); $pg .= 'N.B. if you wish to setup 2FA on more than one device,
'; @@ -79,20 +83,32 @@ function set_2fa($data, $user, $tfa, $ans, $err) $pg .= 'so your should copy it and store it somewhere securely.
'; $pg .= 'For security reasons, the site will not show you an active 2FA Secret Key.
'; $pg .= '
'; + $pg .= makeForm('2fa'); + $pg .= '
If you wish to cancel setting up 2FA, click here: '; + $pg .= '
'; $pg .= '2FA is enabled on your account.

'; $pg .= 'If you wish to replace your Secret Key with a new one:

'; + $pg .= makeForm('2fa'); $pg .= 'Current 2FA Value: '; - $pg .= '*

'; + $pg .= '*'; + $pg .= '

'; $pg .= '*WARNING: replacing the Secret Key will disable 2FA
'; $pg .= 'until you successfully test the new key,
'; $pg .= 'thus getting a new key is effectively the same as disabling 2FA.

'; $pg .= '
'; + $pg .= makeForm('2fa'); + $pg .= 'If you wish to remove 2FA from your account,
'; + $pg .= 'enter your App 2FA Value:
'; + $pg .= 'then click remove: '; + $pg .= '
'; + $pg .= '
'; $pg .= ''; $pg .= ''; @@ -147,34 +163,53 @@ function set_2fa($data, $user, $tfa, $ans, $err) # function do2fa($data, $user) { + $mailmode = ''; $err = ''; + $msg = ''; $setup = getparam('Setup', false); - $testemail = false; if ($setup === 'Setup') { // rand() included as part of the entropy $ans = get2fa($user, 'setup', rand(1073741824,2147483647), 0); - $testemail = true; + $mailmode = 'Setup'; } else { - $value = getparam('Value', false); - $test = getparam('Test', false); - if ($test === 'Test' and $value !== null) + $can = getparam('Cancel', false); + if ($can === 'Cancel') { - $ans = get2fa($user, 'test', 0, $value); - $testemail = true; + $ans = get2fa($user, 'untest', 0, 0); + $mailmode = 'Cancel'; } else { - $nw = getparam('New', false); - if ($nw === 'New' and $value !== null) + $value = getparam('Value', false); + $test = getparam('Test', false); + if ($test === 'Test' and $value !== null) { - $ans = get2fa($user, 'new', rand(1073741824,2147483647), $value); - $testemail = true; + $ans = get2fa($user, 'test', 0, $value); + $mailmode = 'Test'; } else - $ans = get2fa($user, '', 0, 0); + { + $nw = getparam('New', false); + if ($nw === 'New' and $value !== null) + { + $ans = get2fa($user, 'new', rand(1073741824,2147483647), $value); + $mailmode = 'New'; + } + else + { + $rem = getparam('Remove', false); + if ($rem === 'Remove' and $value !== null) + { + $ans = get2fa($user, 'remove', 0, $value); + $mailmode = 'Remove'; + } + else + $ans = get2fa($user, '', 0, 0); + } + } } } if ($ans['STATUS'] != 'ok') @@ -184,7 +219,7 @@ function do2fa($data, $user) if (isset($ans['2fa_error'])) $err = $ans['2fa_error']; - if ($testemail and $err == '') + if ($mailmode != '' and $err == '') { $ans2 = userSettings($user); if ($ans2['STATUS'] != 'ok') @@ -199,12 +234,16 @@ function do2fa($data, $user) $err = 'An error occurred, check your details below'; else { - if ($setup === 'Setup') + if ($mailmode === 'Setup') twofaSetup($email, zeip(), $emailinfo); - else if ($test === 'Test') + else if ($mailmode === 'Test') twofaEnabled($email, zeip(), $emailinfo); - else if ($nw === 'New') + else if ($mailmode === 'New') twofaSetup($email, zeip(), $emailinfo); + else if ($mailmode === 'Cancel') + twofaCancel($email, zeip(), $emailinfo); + else if ($mailmode === 'Remove') + twofaRemove($email, zeip(), $emailinfo); } } } @@ -213,7 +252,10 @@ function do2fa($data, $user) $tfa = null; else $tfa = $ans['2fa_status']; - $pg = set_2fa($data, $user, $tfa, $ans, $err); + if (isset($ans['2fa_msg'])) + $msg = $ans['2fa_msg']; + + $pg = set_2fa($data, $user, $tfa, $ans, $err, $msg); return $pg; } # diff --git a/pool/page_blocks.php b/pool/page_blocks.php index 68d04704..e8d473bd 100644 --- a/pool/page_blocks.php +++ b/pool/page_blocks.php @@ -93,12 +93,15 @@ function doblocks($data, $user) $pg .= "\n"; $pg .= ""; $pg .= ""; + $pg .= ""; $pg .= ""; $pg .= ""; $pg .= ""; $pg .= ""; $pg .= "\n"; + $since = $data['info']['lastblock']; + $count = $ans['s_rows']; for ($i = 0; $i < $count; $i++) { @@ -108,6 +111,7 @@ function doblocks($data, $user) $row = 'odd'; $desc = $ans['s_desc:'.$i]; + $age = daysago($since - $ans['s_prevcreatedate:'.$i]); $diff = number_format(100 * $ans['s_diffratio:'.$i], 2); $mean = number_format(100 * $ans['s_diffmean:'.$i], 2); @@ -120,6 +124,7 @@ function doblocks($data, $user) $pg .= ""; $pg .= ""; + $pg .= ""; $pg .= ""; $pg .= ""; $pg .= ""; diff --git a/pool/page_mpayouts.php b/pool/page_mpayouts.php index 18e0df0b..ca962854 100644 --- a/pool/page_mpayouts.php +++ b/pool/page_mpayouts.php @@ -14,6 +14,7 @@ function dompayouts($data, $user) $pg .= "
DescriptionTimeDiff%Mean%CDF[Erl]Luck%
$desc Blocks$age$diff%$mean%$cdferldsp
\n"; $pg .= ""; $pg .= ""; + $pg .= ""; $pg .= ""; $pg .= ""; $pg .= ""; @@ -36,6 +37,7 @@ function dompayouts($data, $user) $pg .= ""; $pg .= ''; + $pg .= ''; $pg .= ''; $diffused = $ans['diffused:'.$i]; $pg .= ''; @@ -63,7 +65,7 @@ function dompayouts($data, $user) $pg .= ""; $pg .= ''; - $pg .= ''; + $pg .= ''; $pg .= ''; $pg .= "\n"; } diff --git a/pool/page_psperf.php b/pool/page_psperf.php index 8943d5cf..62672cc3 100644 --- a/pool/page_psperf.php +++ b/pool/page_psperf.php @@ -6,6 +6,7 @@ $g = "function gdrw(c,d,cbx){gc(c);ghrs(c);gopt(c,cbx); gfs(c,'white');gss(c,'#0000c0');glw(c,2);gbd(c); var rows=d['rows'],ymin=-1,ymax=0,xmin=-1,xmax=0,tda=[]; var w=d['arp'].split(',');var cols=d['cols'].split(','); +gsh(c,w); for(var j=1;js){xmin=s}if(xmaxths){ymin=ths}if(ths>ymax)ymax=ths} diff --git a/pool/page_reg.php b/pool/page_reg.php index 66af608d..1b84b0da 100644 --- a/pool/page_reg.php +++ b/pool/page_reg.php @@ -65,7 +65,8 @@ function doregres($data, $u) - +
BlockBlock UTCMiner RewardN DiffN Range
'.$ans['height:'.$i].''.gmdate('j/M H:i',$ans['blockcreatedate:'.$i]).''.btcfmt($ans['minerreward:'.$i]).''.difffmt($diffused).'
Total:'.btcfmt($totamt).'

* All fields are required
Your Username can't be a BTC address

". passrequires() . "

Note: your username is upper/lowercase sensitive,
+and you must also have upper/lowercase correct on all your miners

" . passrequires() . "
"; diff --git a/pool/page_shifts.php b/pool/page_shifts.php index e8efac11..1be0d587 100644 --- a/pool/page_shifts.php +++ b/pool/page_shifts.php @@ -14,6 +14,7 @@ function doshifts($data, $user) $pg .= "Avg Hs"; $pg .= "Shares"; $pg .= "Avg Share"; + $pg .= "Rewards"; $pg .= "\n"; if (($ans['STATUS'] != 'ok') || !isset($ans['prefix_all'])) @@ -26,10 +27,14 @@ function doshifts($data, $user) $pg = '

Last '.($count+1).' Shifts

'.$pg; for ($i = 0; $i < $count; $i++) { + $u = ''; + if (isset($ans['lastpayoutstart:'.$i]) + && $ans['lastpayoutstart:'.$i] != '') + $u = 'u'; if (($i % 2) == 0) - $row = 'even'; + $row = "even$u"; else - $row = 'odd'; + $row = "odd$u"; $pg .= ""; $shifname = $ans['shift:'.$i]; @@ -66,6 +71,7 @@ function doshifts($data, $user) else $avgsh = 0; $pg .= ''.number_format($avgsh, 2).''; + $pg .= ''.$ans['rewards:'.$i].''; $pg .= "\n"; } } diff --git a/pool/page_usperf.php b/pool/page_usperf.php index f16030a6..e73a800e 100644 --- a/pool/page_usperf.php +++ b/pool/page_usperf.php @@ -6,6 +6,7 @@ $g = "function gdrw(c,d,cbx){gc(c);ghrs(c);gopt(c,cbx); gfs(c,'white');gss(c,'#0000c0');glw(c,2);gbd(c); var rows=d['rows'],ymin=-1,ymax=0,xmin=-1,xmax=0,tda=[]; var w=d['arp'].split(',');var cols=d['cols'].split(','); +gsh(c,w); for(var j=1;js){xmin=s}if(xmaxths){ymin=ths}if(ths>ymax)ymax=ths;document.getElementById('worker'+j).value=d[pre+'worker']} @@ -72,7 +73,8 @@ function dousperf($data, $user) $pg .= '
'; $tt = "
  • all = all workers
  • noname = worker with no workername
  • "; - $tt .= "
  • or full workername without the username i.e. .worker or _worker
"; + $tt .= "
  • or full workername without the username i.e. .worker or _worker
  • "; + $tt .= "
  • add a '*' on the end to match multiple workers e.g. .S3*
  • "; $pg .= "?"; $pg .= "$tt"; @@ -82,7 +84,8 @@ function dousperf($data, $user) foreach ($cols as $col) { $i++; - $pg .= " Worker$i:"; + $pg .= " Worker$i"; + $pg .= ":"; $pg .= " "; if ($i > 1) diff --git a/pool/worker.php b/pool/worker.php index dc7ef16c..5cc393cf 100644 --- a/pool/worker.php +++ b/pool/worker.php @@ -8,7 +8,8 @@ include_once('param.php'); function go() { $a = getparam('a', true); - if (substr($a, 0, 1) != '1') + $f = substr($a, 0, 1); + if ($f != '1' and $f != '3') return; if (preg_match('/^[a-zA-Z0-9]{24,}[\._]?[a-zA-Z0-9\._]*$/', $a) === false) return; diff --git a/src/ckdb.c b/src/ckdb.c index 263b4b70..935c8aa6 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -36,30 +36,7 @@ * much larger DB tables so that ckdb is effectively ready for messages * almost immediately * The first ckpool message allows us to know where ckpool is up to - * in the CCLs and thus where to stop processing the CCLs to stay in - * sync with ckpool - * If ckpool isn't running, then the reload will complete at the end of - * the last CCL file, however if the 1st message arrives from ckpool while - * processing the CCLs, that will mark the point where to stop processing - * but can also produce a fatal error at the end of processing, reporting - * the ckpool message, if the message was not found in the CCL processing - * after the message was received - * This can be caused by two circumstances: - * 1) the disk had not yet written it to the CCL when ckdb read EOF and - * ckpool was started at about the same time as the reload completed. - * This can be seen if the message displayed in the fatal error IS NOT - * in ckdb's message logfile. - * A ckdb restart will resolve this - * 2) ckpool was started at the time of the end of the reload, but the - * message was written to disk and found in the CCL before it was - * processed in the message queue. - * This can be seen if the message displayed in the fatal error IS in - * ckdb's message logfile and means the messages after it in ckdb's - * message logfile have already been processed. - * Again, a ckdb restart will resolve this - * In both the above (very rare) cases, if ckdb was to continue running, - * it would break the synchronisation and could cause DB problems, so - * ckdb aborting and needing a complete restart resolves it + * in the CCLs - see reload_from() for how this is handled * The users table, required for the authorise messages, is always updated * immediately */ @@ -301,6 +278,7 @@ static sem_t socketer_sem; char *btc_server = "http://127.0.0.1:8330"; char *btc_auth; int btc_timeout = 5; +cklock_t btc_lock; char *by_default = "code"; char *inet_default = "127.0.0.1"; @@ -442,6 +420,7 @@ K_STORE *miningpayouts_store; // PAYOUTS K_TREE *payouts_root; K_TREE *payouts_id_root; +K_TREE *payouts_wid_root; K_LIST *payouts_free; K_STORE *payouts_store; cklock_t process_pplns_lock; @@ -794,12 +773,11 @@ static bool getdata3() if (!confirm_sharesummary) { if (!(ok = paymentaddresses_fill(conn)) || everyone_die) goto sukamudai; + /* FYI must be after blocks */ 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; @@ -808,6 +786,11 @@ static bool getdata3() /* must be after workinfo */ if (!(ok = workmarkers_fill(conn)) || everyone_die) goto sukamudai; + if (!confirm_sharesummary) { + /* must be after workmarkers */ + if (!(ok = payouts_fill(conn)) || everyone_die) + goto sukamudai; + } if (!(ok = markersummary_fill(conn)) || everyone_die) goto sukamudai; if (!confirm_sharesummary && !everyone_die) @@ -1104,6 +1087,7 @@ static void alloc_storage() payouts_store = k_new_store(payouts_free); payouts_root = new_ktree(); payouts_id_root = new_ktree(); + payouts_wid_root = new_ktree(); auths_free = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); @@ -1322,6 +1306,7 @@ static void dealloc_storage() FREE_ALL(poolstats); FREE_ALL(auths); + FREE_TREE(payouts_wid); FREE_TREE(payouts_id); FREE_TREE(payouts); FREE_STORE_DATA(payouts); @@ -1498,8 +1483,7 @@ static bool setup_data() if (workinfo_current) { DATA_WORKINFO(wic, workinfo_current); STRNCPY(wi.coinbase1, wic->coinbase1); - wi.createdate.tv_sec = 0L; - wi.createdate.tv_usec = 0L; + DATE_ZERO(&(wi.createdate)); INIT_WORKINFO(&look); look.data = (void *)(&wi); // Find the first workinfo for this height @@ -1539,7 +1523,7 @@ static bool setup_data() (_seqset)->seqdata[_i].firsttime.tv_sec = \ (_seqset)->seqdata[_i].firsttime.tv_usec = \ (_seqset)->seqdata[_i].lasttime.tv_sec = \ - (_seqset)->seqdata[_i].lasttime.tv_usec = 0; \ + (_seqset)->seqdata[_i].lasttime.tv_usec = 0L; \ } \ } while (0); @@ -2917,7 +2901,7 @@ static void summarise_blocks() K_RUNLOCK(blocks_free); if (!b_prev) { wi_start = 0; - elapsed_start.tv_sec = elapsed_start.tv_usec = 0L; + DATE_ZERO(&elapsed_start); prev_hi = 0; } else { DATA_BLOCKS(prev_blocks, b_prev); @@ -2936,7 +2920,7 @@ static void summarise_blocks() copy_tv(&elapsed_start, &(prev_workinfo->createdate)); prev_hi = prev_blocks->height; } - elapsed_finish.tv_sec = elapsed_finish.tv_usec = 0L; + DATE_ZERO(&elapsed_finish); // Add up the sharesummaries, abort if any SUMMARY_NEW looksharesummary.workinfoid = wi_finish; @@ -3958,6 +3942,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_USERSTATUS: case CMD_SHSTA: case CMD_USERINFO: + case CMD_BTCSET: ans = ckdb_cmds[msgline->which_cmds].func(NULL, msgline->cmd, msgline->id, @@ -4297,6 +4282,7 @@ static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_PSHIFT: case CMD_SHSTA: case CMD_USERINFO: + case CMD_BTCSET: LOGERR("%s() INVALID message line %"PRIu64 " ignored '%.42s...", __func__, count, @@ -5134,7 +5120,7 @@ static void confirm_reload() } } else { if (confirm_first_workinfoid == 0) { - start.tv_sec = start.tv_usec = 0; + DATE_ZERO(&start); LOGWARNING("%s() no start workinfo found ... " "using time 0", __func__); } else { @@ -5610,6 +5596,7 @@ int main(int argc, char **argv) ckp.main.processname = strdup("main"); cklock_init(&last_lock); + cklock_init(&btc_lock); cklock_init(&seq_lock); cklock_init(&process_pplns_lock); diff --git a/src/ckdb.h b/src/ckdb.h index a1f713bc..af0ff79e 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -55,7 +55,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.2" -#define CKDB_VERSION DB_VERSION"-1.222" +#define CKDB_VERSION DB_VERSION"-1.241" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -270,6 +270,8 @@ extern const tv_t date_eot; #define DATE_BEGIN 1388620800L extern const tv_t date_begin; +#define DATE_ZERO(_tv) (_tv)->tv_sec = (_tv)->tv_usec = 0L + #define BTC_TO_D(_amt) ((double)((_amt) / 100000000.0)) // argv -y - don't run in ckdb mode, just confirm sharesummaries @@ -335,6 +337,8 @@ extern cklock_t last_lock; extern char *btc_server; extern char *btc_auth; extern int btc_timeout; +// Lock access to the above variables so they can be changed +extern cklock_t btc_lock; #define EDDB "expirydate" #define CDDB "createdate" @@ -401,6 +405,7 @@ enum cmd_values { CMD_PSHIFT, CMD_SHSTA, CMD_USERINFO, + CMD_BTCSET, CMD_END }; @@ -589,8 +594,7 @@ enum cmd_values { STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ - _row->modifydate.tv_sec = 0; \ - _row->modifydate.tv_usec = 0; \ + DATE_ZERO(&(_row->modifydate)); \ _row->modifyby[0] = '\0'; \ _row->modifycode[0] = '\0'; \ _row->modifyinet[0] = '\0'; \ @@ -612,8 +616,7 @@ enum cmd_values { SET_CREATEBY(_list, _row->createby, _by); \ SET_CREATECODE(_list, _row->createcode, _code); \ SET_CREATEINET(_list, _row->createinet, _inet); \ - _row->modifydate.tv_sec = 0; \ - _row->modifydate.tv_usec = 0; \ + DATE_ZERO(&(_row->modifydate)); \ SET_MODIFYBY(_list, _row->modifyby, EMPTY); \ SET_MODIFYCODE(_list, _row->modifycode, EMPTY); \ SET_MODIFYINET(_list, _row->modifyinet, EMPTY); \ @@ -1493,6 +1496,10 @@ typedef struct blocks { double diffmean; double cdferl; double luck; + + // To save looking them up when needed + tv_t prevcreatedate; // non-DB field + tv_t blockcreatedate; // non-DB field } BLOCKS; #define ALLOC_BLOCKS 100 @@ -1545,6 +1552,11 @@ extern K_STORE *blocks_store; extern tv_t blocks_stats_time; extern bool blocks_stats_rebuild; +// Default number of blocks to display on web +#define BLOCKS_DEFAULT 42 +// OptionControl can override it +#define BLOCKS_SETTING_NAME "BlocksPageSize" + // MININGPAYOUTS typedef struct miningpayouts { int64_t payoutid; @@ -1570,6 +1582,7 @@ typedef struct payouts { int64_t payoutid; int32_t height; char blockhash[TXT_BIG+1]; + tv_t blockcreatedate; // non-DB field int64_t minerreward; int64_t workinfoidstart; int64_t workinfoidend; @@ -1591,6 +1604,7 @@ typedef struct payouts { extern K_TREE *payouts_root; extern K_TREE *payouts_id_root; +extern K_TREE *payouts_wid_root; extern K_LIST *payouts_free; extern K_STORE *payouts_store; extern cklock_t process_pplns_lock; @@ -1893,6 +1907,9 @@ typedef struct workmarkers { int64_t workinfoidstart; char *description; char status[TXT_FLAG+1]; + int rewards; // non-DB field + double pps_value; // non-DB field + double rewarded; // non-DB field HISTORYDATECONTROLFIELDS; } WORKMARKERS; @@ -2297,6 +2314,10 @@ extern const char *blocks_confirmed(char *confirmed); extern void zero_on_new_block(); extern void set_block_share_counters(); extern bool check_update_blocks_stats(tv_t *stats); +#define set_blockcreatedate(_h) _set_blockcreatedate(_h, WHERE_FFL_HERE) +extern bool _set_blockcreatedate(int32_t oldest_height, WHERE_FFL_ARGS); +#define set_prevcreatedate(_h) _set_prevcreatedate(_h, WHERE_FFL_HERE) +extern bool _set_prevcreatedate(int32_t oldest_height, WHERE_FFL_ARGS); 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); @@ -2305,9 +2326,11 @@ 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 cmp_t cmp_payouts_wid(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 K_ITEM *find_payouts_wid(int64_t workinfoidend, K_TREE_CTX *ctx); extern double payout_stats(PAYOUTS *payouts, char *statname); extern bool process_pplns(int32_t height, char *blockhash, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); @@ -2335,11 +2358,13 @@ extern bool make_markersummaries(bool msg, char *by, char *code, char *inet, extern void dsp_workmarkers(K_ITEM *item, FILE *stream); extern cmp_t cmp_workmarkers(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_workmarkers_workinfoid(K_ITEM *a, K_ITEM *b); -extern K_ITEM *find_workmarkers(int64_t workinfoid, bool anystatus, char status); +extern K_ITEM *find_workmarkers(int64_t workinfoid, bool anystatus, char status, K_TREE_CTX *ctx); extern K_ITEM *find_workmarkerid(int64_t markerid, bool anystatus, char status); extern bool workmarkers_generate(PGconn *conn, char *err, size_t siz, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, bool none_error); +extern bool reward_shifts(PAYOUTS *payouts, bool lock, int delta); +extern bool shift_rewards(K_ITEM *wm_item); extern cmp_t cmp_marks(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_marks(int64_t workinfoid); extern const char *marks_marktype(char *marktype); @@ -2358,8 +2383,8 @@ extern K_ITEM *_find_create_userinfo(int64_t userid, bool lock, WHERE_FFL_ARGS); #define userinfo_update(_s, _ss, _ms) _userinfo_update(_s, _ss, _ms, true, true) extern void _userinfo_update(SHARES *shares, SHARESUMMARY *sharesummary, MARKERSUMMARY *markersummary, bool ss_sub, bool lock); -#define userinfo_block(_blocks, _isnew) _userinfo_block(_blocks, _isnew, true) -extern void _userinfo_block(BLOCKS *blocks, enum info_type isnew, bool lock); +#define userinfo_block(_blocks, _isnew, _delta) _userinfo_block(_blocks, _isnew, _delta, true) +extern void _userinfo_block(BLOCKS *blocks, enum info_type isnew, int delta, bool lock); // *** // *** PostgreSQL functions ckdb_dbio.c @@ -2608,6 +2633,7 @@ extern bool check_2fa(USERS *users, int32_t value); extern bool tst_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern K_ITEM *remove_2fa(K_ITEM *old_u_item, int32_t value, char *by, - char *code, char *inet, tv_t *cd, K_TREE *trf_root); + char *code, char *inet, tv_t *cd, K_TREE *trf_root, + bool check); #endif diff --git a/src/ckdb_btc.c b/src/ckdb_btc.c index 44105190..93759bc2 100644 --- a/src/ckdb_btc.c +++ b/src/ckdb_btc.c @@ -32,9 +32,11 @@ static char *btc_data(char *json, size_t *len) APPEND_REALLOC_INIT(buf, off, *len); APPEND_REALLOC(buf, off, *len, "POST / HTTP/1.1\n"); + ck_wlock(&btc_lock); snprintf(tmp, sizeof(tmp), "Authorization: Basic %s\n", btc_auth); APPEND_REALLOC(buf, off, *len, tmp); snprintf(tmp, sizeof(tmp), "Host: %s/\n", btc_server); + ck_wunlock(&btc_lock); APPEND_REALLOC(buf, off, *len, tmp); APPEND_REALLOC(buf, off, *len, "Content-Type: application/json\n"); snprintf(tmp, sizeof(tmp), "Content-Length: %d\n\n", (int)strlen(json)); diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 774a018c..f4911289 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -213,7 +213,7 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, int32_t entropy, value; USERS *users; char *action, *buf = NULL, *st = NULL; - char *sfa_status = EMPTY, *sfa_error = EMPTY; + char *sfa_status = EMPTY, *sfa_error = EMPTY, *sfa_msg = EMPTY; bool ok = false, key = false; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); @@ -301,9 +301,24 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, else { key = false; sfa_status = "ok"; + sfa_msg = "2FA Enabled"; } // Report sfa_error to web ok = true; + } else if (strcmp(action, "untest") == 0) { + // Can't untest if it's not ready to test + if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA)) + != (USER_TOTPAUTH | USER_TEST2FA)) + goto dame; + // since it's currently test, the value isn't required + u_new = remove_2fa(u_item, 0, by, code, inet, now, + trf_root, false); + if (u_new) { + ok = true; + sfa_status = EMPTY; + key = false; + sfa_msg = "2FA Cancelled"; + } } else if (strcmp(action, "new") == 0) { // Can't new if 2FA isn't already present -> setup if ((users->databits & USER_TOTPAUTH) == 0) @@ -329,23 +344,27 @@ static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, // Can't remove if 2FA isn't already present if (!(users->databits & (USER_TOTPAUTH | USER_TEST2FA))) goto dame; + // remove requires value value = (int32_t)atoi(transfer_data(i_value)); if (!check_2fa(users, value)) { sfa_error = "Invalid code"; // Report sfa_error to web ok = true; } else { + /* already tested 2fa so don't retest, also, + * a retest will fail using the same value */ u_new = remove_2fa(u_item, value, by, code, - inet, now, trf_root); + inet, now, trf_root, false); if (u_new) { ok = true; sfa_status = EMPTY; key = false; + sfa_msg = "2FA Removed"; } } } if (key) { - char *keystr, *issuer = "Kano"; + char *keystr, *issuer = "KanoCKDB"; char cd_buf[DATE_BUFSIZ]; unsigned char *bin; OPTIONCONTROL *oc; @@ -411,8 +430,9 @@ dame: return strdup("failed."); } - snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s", - sfa_status, FLDSEP, sfa_error); + snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s%c2fa_msg=%s", + sfa_status, FLDSEP, sfa_error, FLDSEP, + sfa_msg); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.%s-%s.%s", id, transfer_data(i_username), action, buf); return buf; @@ -1116,7 +1136,7 @@ static char *cmd_workerstats(__maybe_unused PGconn *conn, char *cmd, char *id, } static char *cmd_blocklist(__maybe_unused PGconn *conn, char *cmd, char *id, - __maybe_unused tv_t *now, __maybe_unused char *by, + tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) @@ -1128,13 +1148,16 @@ static char *cmd_blocklist(__maybe_unused PGconn *conn, char *cmd, char *id, char tmp[1024]; char *buf, *desc, desc_buf[64]; size_t len, off; - int32_t height = -1; - tv_t first_cd = {0,0}, stats_tv = {0,0}, stats_tv2 = {0,0}; + tv_t stats_tv = {0,0}, stats_tv2 = {0,0}; int rows, srows, tot, seq; + int64_t maxrows; bool has_stats; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); + // 0 means just the system setting + maxrows = user_sys_setting(0, BLOCKS_SETTING_NAME, BLOCKS_DEFAULT, now); + APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); @@ -1158,15 +1181,8 @@ redo: } seq = tot; b_item = last_in_ktree(blocks_root, ctx); - while (b_item && rows < 42) { + while (b_item && rows < (int)maxrows) { DATA_BLOCKS(blocks, b_item); - /* For each block remember the initial createdate - * Reverse sort order the oldest expirydate is first - * which should be the 'n' record */ - if (height != blocks->height) { - height = blocks->height; - copy_tv(&first_cd, &(blocks->createdate)); - } if (CURRENT(&(blocks->expirydate))) { if (blocks->confirmed[0] == BLOCKS_ORPHAN || blocks->confirmed[0] == BLOCKS_REJECT) { @@ -1203,16 +1219,24 @@ redo: snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + // When block was found snprintf(tmp, sizeof(tmp), "first"CDTRF":%d=%ld%c", rows, - first_cd.tv_sec, FLDSEP); + blocks->blockcreatedate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + // Last time block was updated snprintf(tmp, sizeof(tmp), CDTRF":%d=%ld%c", rows, blocks->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + // When previous valid block was found + snprintf(tmp, sizeof(tmp), + "prev"CDTRF":%d=%ld%c", rows, + blocks->prevcreatedate.tv_sec, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "confirmed:%d=%s%cstatus:%d=%s%c", rows, blocks->confirmed, FLDSEP, rows, @@ -1299,12 +1323,18 @@ redo: snprintf(tmp, sizeof(tmp), "s_seq:%d=%d%c" "s_desc:%d=%s%c" + "s_height:%d=%d%c" + "s_"CDTRF":%d=%ld%c" + "s_prev"CDTRF":%d=%ld%c" "s_diffratio:%d=%.8f%c" "s_diffmean:%d=%.8f%c" "s_cdferl:%d=%.8f%c" "s_luck:%d=%.8f%c", srows, seq, FLDSEP, srows, desc, FLDSEP, + srows, (int)(blocks->height), FLDSEP, + srows, blocks->blockcreatedate.tv_sec, FLDSEP, + srows, blocks->prevcreatedate.tv_sec, FLDSEP, srows, blocks->diffratio, FLDSEP, srows, blocks->diffmean, FLDSEP, srows, blocks->cdferl, FLDSEP, @@ -1336,7 +1366,8 @@ redo: snprintf(tmp, sizeof(tmp), "s_rows=%d%cs_flds=%s%c", srows, FLDSEP, - "s_seq,s_desc,s_diffratio,s_diffmean,s_cdferl,s_luck", + "s_seq,s_desc,s_height,s_"CDTRF",s_prev"CDTRF",s_diffratio," + "s_diffmean,s_cdferl,s_luck", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); @@ -1344,8 +1375,8 @@ redo: "rows=%d%cflds=%s%c", rows, FLDSEP, "seq,height,blockhash,nonce,reward,workername,first"CDTRF"," - CDTRF",confirmed,status,info,statsconf,diffacc,diffinv," - "shareacc,shareinv,elapsed,netdiff,diffratio,cdf,luck", + CDTRF",prev"CDTRF",confirmed,status,info,statsconf,diffacc," + "diffinv,shareacc,shareinv,elapsed,netdiff,diffratio,cdf,luck", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); @@ -1697,8 +1728,7 @@ static char *cmd_percent(char *cmd, char *id, tv_t *now, USERS *users) // Add up all user's worker stats to be divided into payout percentages lookworkers.userid = users->userid; lookworkers.workername[0] = '\0'; - lookworkers.expirydate.tv_sec = 0; - lookworkers.expirydate.tv_usec = 0; + DATE_ZERO(&(lookworkers.expirydate)); w_look.data = (void *)(&lookworkers); w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx); DATA_WORKERS_NULL(workers, w_item); @@ -1959,8 +1989,7 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, lookworkers.userid = users->userid; lookworkers.workername[0] = '\0'; - lookworkers.expirydate.tv_sec = 0; - lookworkers.expirydate.tv_usec = 0; + DATE_ZERO(&(lookworkers.expirydate)); w_look.data = (void *)(&lookworkers); w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx); DATA_WORKERS_NULL(workers, w_item); @@ -1974,7 +2003,7 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, copy_tv(&last_share, &(workerstatus->last_share)); K_RUNLOCK(workerstatus_free); } else - last_share.tv_sec = last_share.tv_usec = 0L; + DATE_ZERO(&last_share); if (tvdiff(now, &last_share) < oldworkers) { str_to_buf(workers->workername, reply, sizeof(reply)); @@ -2012,7 +2041,7 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, w_elapsed = -1; if (!ws_item) { - w_lastshare.tv_sec = 0; + w_lastshare.tv_sec = 0L; w_lastdiff = w_diffacc = w_diffinv = w_diffsta = w_diffdup = w_diffhi = @@ -2020,7 +2049,7 @@ static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, w_shareinv = w_sharesta = w_sharedup = w_sharehi = w_sharerej = w_active_diffacc = 0; - w_active_start.tv_sec = 0; + w_active_start.tv_sec = 0L; } else { DATA_WORKERSTATUS(workerstatus, ws_item); // It's bad to read possibly changing data @@ -4009,8 +4038,8 @@ static char *cmd_pplns(__maybe_unused PGconn *conn, char *cmd, char *id, LOGDEBUG("%s(): height %"PRId32, __func__, height); - block_tv.tv_sec = block_tv.tv_usec = 0L; - cd.tv_sec = cd.tv_usec = 0L; + DATE_ZERO(&block_tv); + DATE_ZERO(&cd); lookblocks.height = height + 1; lookblocks.blockhash[0] = '\0'; INIT_BLOCKS(&b_look); @@ -4437,7 +4466,6 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, PAYMENTS *payments; PAYOUTS *payouts; BLOCKS lookblocks, *blocks; - tv_t block_tv = { 0L, 0L }; WORKINFO *bworkinfo, *workinfo; char ndiffbin[TXT_SML+1]; double ndiff; @@ -4461,36 +4489,26 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, LOGDEBUG("%s(): height %"PRId32, __func__, height); - lookblocks.height = height + 1; + lookblocks.height = height; lookblocks.blockhash[0] = '\0'; INIT_BLOCKS(&b_look); b_look.data = (void *)(&lookblocks); K_RLOCK(blocks_free); - b_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + b_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + K_RUNLOCK(blocks_free); if (!b_item) { K_RUNLOCK(blocks_free); - snprintf(reply, siz, "ERR.no block height %"PRId32, height); + 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); 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)); + if (blocks->blockcreatedate.tv_sec == 0) { + snprintf(reply, siz, "ERR.block %"PRId32" has 0 blockcreatedate", + height); return strdup(reply); } if (!CURRENT(&(blocks->expirydate))) { @@ -4667,10 +4685,11 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, 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)); + tv_to_buf(&(blocks->blockcreatedate), 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); + snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", + blocks->blockcreatedate.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); @@ -4780,6 +4799,8 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } + // Original wasn't generated, so reward it + reward_shifts(payouts2, true, 1); DATA_PAYOUTS(payouts2, p2_item); DATA_PAYOUTS(old_payouts2, old_p2_item); snprintf(msg, sizeof(msg), @@ -4849,6 +4870,8 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } + // Original was generated, so undo the reward + reward_shifts(payouts2, true, -1); DATA_PAYOUTS(payouts2, p2_item); DATA_PAYOUTS(old_payouts2, old_p2_item); snprintf(msg, sizeof(msg), @@ -4866,6 +4889,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, return strdup(reply); TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); + // payouts_full_expire updates the shift rewards p_item = payouts_full_expire(conn, payoutid, now, true); if (!p_item) { snprintf(reply, siz, "failed payout %"PRId64, payoutid); @@ -4902,6 +4926,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, return strdup(reply); TXT_TO_CTV("addrdate", transfer_data(i_addrdate), addrdate); + // process_pplns updates the shift rewards if (addrdate.tv_sec == 0) ok = process_pplns(height, blockhash, NULL); else @@ -4982,6 +5007,11 @@ static char *cmd_mpayouts(__maybe_unused PGconn *conn, char *cmd, char *id, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), + "block"CDTRF":%d=%ld%c", rows, + payouts->blockcreatedate.tv_sec, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + bigint_to_buf(payouts->elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", @@ -5026,7 +5056,7 @@ static char *cmd_mpayouts(__maybe_unused PGconn *conn, char *cmd, char *id, snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, - "payoutid,height,elapsed,amount,diffacc,minerreward,diffused,status", + "payoutid,height,block"CDTRF",elapsed,amount,diffacc,minerreward,diffused,status", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); @@ -5037,15 +5067,16 @@ static char *cmd_mpayouts(__maybe_unused PGconn *conn, char *cmd, char *id, return buf; } -/* Find the offset, in list, of the workername - * -1 means NULL list, empty list or not found */ -static int worker_offset(char **list, char *workername) +typedef struct worker_match { + char *worker; + bool match; + size_t len; + bool used; +} WM; + +static char *worker_offset(char *workername) { char *c1, *c2; - int i; - - if (!list || !(*list)) - return -1; /* Find the start of the workername including the SEP */ c1 = strchr(workername, WORKSEP1); @@ -5057,11 +5088,8 @@ static int worker_offset(char **list, char *workername) // No workername after the username if (!c1) c1 = WORKERS_EMPTY; - for (i = 0; list[i]; i++) { - if (strcmp(c1, list[i]) == 0) - return i; - } - return -1; + + return c1; } /* Some arbitrarily large limit, increase it if needed @@ -5069,28 +5097,19 @@ static int worker_offset(char **list, char *workername) #define SELECT_LIMIT 63 /* select is a string of workernames separated by WORKERS_SEL_SEP - * Return an array of strings of select broken up - * The array is terminated by NULL + * Setup the wm array of workers with select broken up + * The wm array is terminated by workers = NULL * and will have 0 elements if select is NULL/empty - * The count of the first occurrence of WORKERS_ALL is returned in *all_count, + * The count of the first occurrence of WORKERS_ALL is returned, * or -1 if WORKERS_ALL isn't found */ -static char **select_list(char *select, int *all_count) +static int select_list(WM *wm, char *select) { - size_t len, offset, siz; - char **list = NULL; - int count; + int count, all_count = -1; + size_t len, offset; char *end; - *all_count = -1; - - siz = sizeof(char *) * (SELECT_LIMIT + 1); - list = malloc(siz); - if (!list) - quithere(1, "malloc (%d) OOM", (int)siz); - list[0] = NULL; - if (select == NULL || *select == '\0') - return list; + return all_count; len = strlen(select); count = 0; @@ -5099,24 +5118,24 @@ static char **select_list(char *select, int *all_count) if (select[offset] == WORKERS_SEL_SEP) offset++; else { - list[count] = select + offset; - list[count+1] = NULL; - end = strchr(list[count], WORKERS_SEL_SEP); + wm[count].worker = select + offset; + wm[count+1].worker = NULL; + end = strchr(wm[count].worker, WORKERS_SEL_SEP); if (end != NULL) { offset = 1 + end - select; *end = '\0'; } - if (*all_count == -1 && - strcasecmp(list[count], WORKERS_ALL) == 0) { - *all_count = count; + if (all_count == -1 && + strcasecmp(wm[count].worker, WORKERS_ALL) == 0) { + all_count = count; } if (end == NULL || ++count > SELECT_LIMIT) break; } } - return list; + return all_count; } static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, @@ -5129,7 +5148,7 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, K_TREE_CTX wm_ctx[1], ms_ctx[1]; WORKMARKERS *wm; WORKINFO *wi; - MARKERSUMMARY markersummary, *ms, ms_add; + MARKERSUMMARY markersummary, *ms, ms_add[SELECT_LIMIT+1]; PAYOUTS *payouts; USERS *users; MARKS *marks = NULL; @@ -5137,14 +5156,14 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, char tmp[1024]; size_t siz = sizeof(reply); char *select = NULL; - char **selects = NULL; - bool used[SELECT_LIMIT]; - char *buf; + WM workm[SELECT_LIMIT+1]; + char *buf, *work; size_t len, off; tv_t marker_end = { 0L, 0L }; int rows, want, i, where_all; int64_t maxrows; double wm_count, d; + int64_t last_payout_start = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); @@ -5171,23 +5190,34 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, wm_count *= 1.42; if (maxrows < wm_count) maxrows = wm_count; + last_payout_start = payouts->workinfoidstart; } i_select = optional_name(trf_root, "select", 1, NULL, reply, siz); if (i_select) select = strdup(transfer_data(i_select)); - selects = select_list(select, &where_all); + bzero(workm, sizeof(workm)); + where_all = select_list(&(workm[0]), select); // Nothing selected = all - if (*selects == NULL) { + if (workm[0].worker == NULL) { where_all = 0; - selects[0] = WORKERS_ALL; - selects[1] = NULL; + workm[0].worker = WORKERS_ALL; + } else { + for (i = 0; workm[i].worker; i++) { + // N.B. len is only used if match is true + len = workm[i].len = strlen(workm[i].worker); + // If at least 3 characters and last is '*' + if (len > 2 && workm[i].worker[len-1] == '*') { + workm[i].worker[len-1] = '\0'; + workm[i].match = true; + workm[i].len--; + } + } } - bzero(used, sizeof(used)); if (where_all >= 0) - used[where_all] = true; + workm[where_all].used = true; APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); @@ -5216,7 +5246,12 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, wm->workinfoidend); } - bzero(&ms_add, sizeof(ms_add)); + // Zero everything for this shift + bzero(ms_add, sizeof(ms_add)); + for (i = 0; workm[i].worker; i++) { + if (i != where_all) + workm[i].used = false; + } markersummary.markerid = wm->markerid; markersummary.userid = users->userid; @@ -5227,49 +5262,56 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, DATA_MARKERSUMMARY_NULL(ms, ms_item); while (ms_item && ms->markerid == wm->markerid && ms->userid == users->userid) { - ms_add.diffacc += ms->diffacc; - ms_add.diffsta += ms->diffsta; - ms_add.diffdup += ms->diffdup; - ms_add.diffhi += ms->diffhi; - ms_add.diffrej += ms->diffrej; - ms_add.shareacc += ms->shareacc; - ms_add.sharesta += ms->sharesta; - ms_add.sharedup += ms->sharedup; - ms_add.sharehi += ms->sharehi; - ms_add.sharerej += ms->sharerej; - - want = worker_offset(selects, ms->workername); - if (want >= 0) { - used[want] = true; - double_to_buf(ms->diffacc, reply, sizeof(reply)); + work = worker_offset(ms->workername); + for (want = 0; workm[want].worker; want++) { + if ((want == where_all) || + (workm[want].match && strncmp(work, workm[want].worker, workm[want].len) == 0) || + (!(workm[want].match) && strcmp(workm[want].worker, work) == 0)) { + workm[want].used = true; + ms_add[want].diffacc += ms->diffacc; + ms_add[want].diffsta += ms->diffsta; + ms_add[want].diffdup += ms->diffdup; + ms_add[want].diffhi += ms->diffhi; + ms_add[want].diffrej += ms->diffrej; + ms_add[want].shareacc += ms->shareacc; + ms_add[want].sharesta += ms->sharesta; + ms_add[want].sharedup += ms->sharedup; + ms_add[want].sharehi += ms->sharehi; + ms_add[want].sharerej += ms->sharerej; + } + } + ms_item = next_in_ktree(ms_ctx); + DATA_MARKERSUMMARY_NULL(ms, ms_item); + } + K_RUNLOCK(markersummary_free); + + for (i = 0; i <= SELECT_LIMIT; i++) { + if (workm[i].used) { + double_to_buf(ms_add[i].diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", - want, rows, reply, FLDSEP); + i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - d = ms->diffsta + ms->diffdup + - ms->diffhi + ms->diffrej; + d = ms_add[i].diffsta + ms_add[i].diffdup + + ms_add[i].diffhi + ms_add[i].diffrej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", - want, rows, reply, FLDSEP); + i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - double_to_buf(ms->shareacc, reply, sizeof(reply)); + double_to_buf(ms_add[i].shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", - want, rows, reply, FLDSEP); + i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - d = ms->sharesta + ms->sharedup + - ms->sharehi + ms->sharerej; + d = ms_add[i].sharesta + ms_add[i].sharedup + + ms_add[i].sharehi + ms_add[i].sharerej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", - want, rows, reply, FLDSEP); + i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } - - ms_item = next_in_ktree(ms_ctx); - DATA_MARKERSUMMARY_NULL(ms, ms_item); } - K_RUNLOCK(markersummary_free); if (marker_end.tv_sec == 0L) { wi_item = next_workinfo(wm->workinfoidend, NULL); @@ -5286,7 +5328,6 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, wm->workinfoidend); snprintf(reply, siz, "data error 1"); free(buf); - free(selects); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); @@ -5307,7 +5348,6 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, wm->workinfoidstart); snprintf(reply, siz, "data error 2"); free(buf); - free(selects); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); @@ -5338,35 +5378,18 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - if (where_all >= 0) { - double_to_buf(ms_add.diffacc, reply, sizeof(reply)); - snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", - where_all, rows, - reply, FLDSEP); - APPEND_REALLOC(buf, off, len, tmp); - - d = ms_add.diffsta + ms_add.diffdup + - ms_add.diffhi + ms_add.diffrej; - double_to_buf(d, reply, sizeof(reply)); - snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", - where_all, rows, - reply, FLDSEP); - APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "rewards:%d=%d%c", + rows, wm->rewards, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); - double_to_buf(ms_add.shareacc, reply, sizeof(reply)); - snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", - where_all, rows, - reply, FLDSEP); - APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), "lastpayoutstart:%d=%s%c", + rows, + (wm->workinfoidstart == + last_payout_start) ? + "Y" : EMPTY, + FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); - d = ms_add.sharesta + ms_add.sharedup + - ms_add.sharehi + ms_add.sharerej; - double_to_buf(d, reply, sizeof(reply)); - snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", - where_all, rows, - reply, FLDSEP); - APPEND_REALLOC(buf, off, len, tmp); - } rows++; // Setup for next shift @@ -5379,11 +5402,13 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, } K_RUNLOCK(workmarkers_free); - for (i = 0; selects[i]; i++) { - if (used[i]) { + for (i = 0; workm[i].worker; i++) { + if (workm[i].used) { snprintf(tmp, sizeof(tmp), - "%d_worker=%s%c", - i, selects[i], FLDSEP); + "%d_worker=%s%s%c", + i, workm[i].worker, + workm[i].match ? "*" : EMPTY, + FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%d_flds=%s%c", i, @@ -5404,13 +5429,14 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, * other workers start >= 0 and finish <= rows-1 */ snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, - "markerid,shift,start,end", FLDSEP); + "markerid,shift,start,end,rewards," + "lastpayoutstart", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s", "Shifts"); APPEND_REALLOC(buf, off, len, tmp); - for (i = 0; selects[i]; i++) { - if (used[i]) { + for (i = 0; workm[i].worker; i++) { + if (workm[i].used) { snprintf(tmp, sizeof(tmp), ",Worker_%d", i); APPEND_REALLOC(buf, off, len, tmp); } @@ -5418,15 +5444,14 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, snprintf(tmp, sizeof(tmp), "%carp=", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - for (i = 0; selects[i]; i++) { - if (used[i]) { + for (i = 0; workm[i].worker; i++) { + if (workm[i].used) { snprintf(tmp, sizeof(tmp), ",%d_", i); APPEND_REALLOC(buf, off, len, tmp); } } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); - free(selects); return(buf); } @@ -5537,7 +5562,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(payouts, 1, 3); USEINFO(auths, 1, 1); USEINFO(poolstats, 1, 1); USEINFO(userstats, 2, 1); @@ -6300,6 +6325,60 @@ static char *cmd_userinfo(__maybe_unused PGconn *conn, char *cmd, char *id, return buf; } +/* Set/show the BTC server settings + * You must supply the btcserver to change anything + * The format for userpass is username:password + * If you don't supply the btcserver it will simply report the current server + * If supply btcserver but not the userpass it will use the current userpass + * The reply will ONLY contain the URL, not the user/pass */ +static char *cmd_btcset(__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) +{ + K_ITEM *i_btcserver, *i_userpass; + char *btcserver = NULL, *userpass = NULL, *tmp; + char reply[1024] = ""; + size_t siz = sizeof(reply); + char buf[256]; + + i_btcserver = optional_name(trf_root, "btcserver", 1, NULL, reply, siz); + if (i_btcserver) { + btcserver = strdup(transfer_data(i_btcserver)); + i_userpass = optional_name(trf_root, "userpass", 0, NULL, reply, siz); + if (i_userpass) + userpass = transfer_data(i_userpass); + + ck_wlock(&btc_lock); + btc_server = btcserver; + btcserver = NULL; + if (userpass) { + if (btc_auth) { + tmp = btc_auth; + while (*tmp) + *(tmp++) = '\0'; + } + FREENULL(btc_auth); + btc_auth = http_base64(userpass); + } + ck_wunlock(&btc_lock); + + if (userpass) { + tmp = userpass; + while (*tmp) + *(tmp++) = '\0'; + } + } + + FREENULL(btcserver); + + ck_wlock(&btc_lock); + snprintf(buf, sizeof(buf), "ok.btcserver=%s", btc_server); + ck_wunlock(&btc_lock); + LOGDEBUG("%s.%s.%s", id, cmd, buf); + return strdup(buf); +} + // TODO: limit access by having seperate sockets for each #define ACCESS_POOL "p" #define ACCESS_SYSTEM "s" @@ -6414,5 +6493,6 @@ struct CMDS ckdb_cmds[] = { { CMD_PSHIFT, "pshift", false, false, cmd_pshift, SEQ_NONE, ACCESS_SYSTEM ACCESS_WEB }, { CMD_SHSTA, "shsta", true, false, cmd_shsta, SEQ_NONE, ACCESS_SYSTEM }, { CMD_USERINFO, "userinfo", false, false, cmd_userinfo, SEQ_NONE, ACCESS_WEB }, + { CMD_BTCSET, "btcset", false, false, cmd_btcset, SEQ_NONE, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, SEQ_NONE, NULL } }; diff --git a/src/ckdb_crypt.c b/src/ckdb_crypt.c index d285e9ac..7ad0109f 100644 --- a/src/ckdb_crypt.c +++ b/src/ckdb_crypt.c @@ -237,14 +237,17 @@ bool tst_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code, } K_ITEM *remove_2fa(K_ITEM *old_u_item, int32_t value, char *by, char *code, - char *inet, tv_t *cd, K_TREE *trf_root) + char *inet, tv_t *cd, K_TREE *trf_root, bool check) { K_ITEM *u_item = NULL; USERS *old_users, *users; - bool ok, did = false; + bool ok = true, did = false; DATA_USERS(old_users, old_u_item); - ok = check_2fa(old_users, value); + /* N.B. check_2fa will fail if it is called a second time + * with the same value */ + if (check) + ok = check_2fa(old_users, value); if (ok) { K_WLOCK(users_free); u_item = k_unlink_head(users_free); diff --git a/src/ckdb_data.c b/src/ckdb_data.c index f2c491ee..63a872c0 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -369,8 +369,7 @@ void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t s long sec, nsec; int c; // Caller test for tv_sec=0 for failure - ((tv_t *)data)->tv_sec = 0L; - ((tv_t *)data)->tv_usec = 0L; + DATE_ZERO((tv_t *)data); c = sscanf(fld, "%ld,%ld", &sec, &nsec); if (c > 0) { ((tv_t *)data)->tv_sec = (time_t)sec; @@ -882,12 +881,17 @@ K_ITEM *_find_create_workerstatus(int64_t userid, char *workername, K_WUNLOCK(workerstatus_free); if (ws_err) { - LOGERR("%s(): CREATED Missing workerstatus %"PRId64"/%s" - WHERE_FFL WHERE_FFL, - __func__, userid, workername, - file2, func2, line2, WHERE_FFL_PASS); + LOGNOTICE("%s(): CREATED Missing workerstatus" + " %"PRId64"/%s" + WHERE_FFL WHERE_FFL, + __func__, userid, workername, + file2, func2, line2, WHERE_FFL_PASS); if (w_err) { - LOGERR("%s(): %s Missing worker %"PRId64"/%s", + int sta = LOG_ERR; + if (w_item) + sta = LOG_NOTICE; + LOGMSG(sta, + "%s(): %s Missing worker %"PRId64"/%s", __func__, w_item ? "CREATED" : "FAILED TO CREATE", userid, workername); @@ -1469,8 +1473,7 @@ K_ITEM *first_workers(int64_t userid, K_TREE_CTX *ctx) workers.userid = userid; workers.workername[0] = '\0'; - workers.expirydate.tv_sec = 0L; - workers.expirydate.tv_usec = 0L; + DATE_ZERO(&(workers.expirydate)); INIT_WORKERS(&look); look.data = (void *)(&workers); @@ -1623,8 +1626,7 @@ K_ITEM *find_paymentaddresses_create(int64_t userid, K_TREE_CTX *ctx) K_ITEM look, *item; paymentaddresses.userid = userid; - paymentaddresses.createdate.tv_sec = 0; - paymentaddresses.createdate.tv_usec = 0; + DATE_ZERO(&(paymentaddresses.createdate)); paymentaddresses.payaddress[0] = '\0'; INIT_PAYMENTADDRESSES(&look); @@ -1844,8 +1846,7 @@ K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height) * activationdate will all be the default value and not * decide the outcome */ STRNCPY(optioncontrol.optionname, optionname); - optioncontrol.activationdate.tv_sec = 0L; - optioncontrol.activationdate.tv_usec = 0L; + DATE_ZERO(&(optioncontrol.activationdate)); optioncontrol.activationheight = OPTIONCONTROL_HEIGHT - 1; optioncontrol.expirydate.tv_sec = default_expiry.tv_sec; optioncontrol.expirydate.tv_usec = default_expiry.tv_usec; @@ -2040,8 +2041,8 @@ bool workinfo_age(int64_t workinfoid, char *poolinstance, char *by, char *code, LOGDEBUG("%s(): age", __func__); - ss_first->tv_sec = ss_first->tv_usec = - ss_last->tv_sec = ss_last->tv_usec = 0; + DATE_ZERO(ss_first); + DATE_ZERO(ss_last); *ss_count = *s_count = *s_diff = 0; wi_item = find_workinfo(workinfoid, NULL); @@ -2065,7 +2066,7 @@ bool workinfo_age(int64_t workinfoid, char *poolinstance, char *by, char *code, } K_RLOCK(workmarkers_free); - wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED); + wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED, NULL); K_RUNLOCK(workmarkers_free); // Should never happen? if (wm_item && !reloading) { @@ -2137,8 +2138,7 @@ bool workinfo_age(int64_t workinfoid, char *poolinstance, char *by, char *code, lookshares.workinfoid = workinfoid; lookshares.userid = sharesummary->userid; strcpy(lookshares.workername, sharesummary->workername); - lookshares.createdate.tv_sec = 0; - lookshares.createdate.tv_usec = 0; + DATE_ZERO(&(lookshares.createdate)); s_look.data = (void *)(&lookshares); K_WLOCK(shares_free); @@ -2385,8 +2385,8 @@ void auto_age_older(int64_t workinfoid, char *poolinstance, char *by, cmp_sharesummary_workinfoid, ctx); DATA_SHARESUMMARY_NULL(sharesummary, ss_item); - ss_first_min.tv_sec = ss_first_min.tv_usec = - ss_last_max.tv_sec = ss_last_max.tv_usec = 0; + DATE_ZERO(&ss_first_min); + DATE_ZERO(&ss_last_max); ss_count_tot = s_count_tot = s_diff_tot = 0; found = false; @@ -2628,8 +2628,7 @@ K_ITEM *find_prev_blocks(int32_t height) * not NEW, blocks, which might not find the right one */ lookblocks.height = height; lookblocks.blockhash[0] = '\0'; - lookblocks.expirydate.tv_sec = 0L; - lookblocks.expirydate.tv_usec = 0L; + DATE_ZERO(&(lookblocks.expirydate)); INIT_BLOCKS(&look); look.data = (void *)(&lookblocks); @@ -2919,8 +2918,8 @@ bool check_update_blocks_stats(tv_t *stats) K_RUNLOCK(workinfo_free); if (!w_item) { setnow(&now); - if (!blocks->workinfoid != last_missing_workinfoid || - tvdiff(&now, &last_message) >= 5.0) { + if (blocks->workinfoid != last_missing_workinfoid || + tvdiff(&now, &last_message) >= 15.0) { LOGEMERG("%s(): missing block workinfoid %" PRId32"/%"PRId64"/%s", __func__, blocks->height, @@ -2988,6 +2987,184 @@ bool check_update_blocks_stats(tv_t *stats) return true; } +// Must be under K_WLOCK(blocks_free) when called except during DB load +bool _set_blockcreatedate(int32_t oldest_height, WHERE_FFL_ARGS) +{ + K_TREE_CTX ctx[1]; + BLOCKS *blocks; + K_ITEM *b_item; + int32_t height; + char blockhash[TXT_BIG+1]; + char cd_buf[DATE_BUFSIZ]; + tv_t createdate; + bool ok = true; + + // No blocks? + if (blocks_store->count == 0) + return true; + + height = 0; + blockhash[0] = '\0'; + DATE_ZERO(&createdate); + b_item = last_in_ktree(blocks_root, ctx); + DATA_BLOCKS_NULL(blocks, b_item); + while (b_item && blocks->height >= oldest_height) { + // NEW will be first going back + if (blocks->confirmed[0] == BLOCKS_NEW) { + height = blocks->height; + STRNCPY(blockhash, blocks->blockhash); + copy_tv(&createdate, &(blocks->createdate)); + } + if (blocks->height != height || + strcmp(blocks->blockhash, blockhash) != 0) { + // Missing NEW + tv_to_buf(&(blocks->expirydate), cd_buf, sizeof(cd_buf)); + LOGEMERG("%s() block %"PRId32"/%s/%s/%s has no '" + BLOCKS_NEW_STR "' prev was %"PRId32"/%s." + WHERE_FFL, + __func__, + blocks->height, blocks->blockhash, + blocks->confirmed, cd_buf, + height, blockhash, WHERE_FFL_PASS); + ok = false; + + height = blocks->height; + STRNCPY(blockhash, blocks->blockhash); + // set a useable (incorrect) value + copy_tv(&createdate, &(blocks->createdate)); + } + // Always update it + copy_tv(&(blocks->blockcreatedate), &createdate); + + b_item = prev_in_ktree(ctx); + DATA_BLOCKS_NULL(blocks, b_item); + } + return ok; +} + +// Must be under K_WLOCK(blocks_free) when called except during DB load +bool _set_prevcreatedate(int32_t oldest_height, WHERE_FFL_ARGS) +{ + K_ITEM look, *b_item = NULL, *wi_item; + BLOCKS lookblocks, *blocks = NULL; + K_TREE_CTX b_ctx[1], wi_ctx[1]; + WORKINFO *workinfo; + char curr_blockhash[TXT_BIG+1]; + char cd_buf[DATE_BUFSIZ]; + int32_t curr_height; + tv_t prev_createdate; + tv_t curr_createdate; + bool ok = true, currok = false; + + // No blocks? + if (blocks_store->count == 0) + return true; + + // Find first 'ok' block before oldest_height + lookblocks.height = oldest_height; + lookblocks.blockhash[0] = '\0'; + DATE_ZERO(&(lookblocks.expirydate)); + + INIT_BLOCKS(&look); + look.data = (void *)(&lookblocks); + b_item = find_before_in_ktree(blocks_root, &look, cmp_blocks, b_ctx); + while (b_item) { + DATA_BLOCKS(blocks, b_item); + if (CURRENT(&(blocks->expirydate)) && + blocks->confirmed[0] != BLOCKS_ORPHAN && + blocks->confirmed[0] != BLOCKS_REJECT) + break; + b_item = prev_in_ktree(b_ctx); + } + + // Setup prev_createdate + if (b_item) { + /* prev_createdate is the ok b_item (before oldest_height) + * _set_blockcreatedate() should always be called + * before calling _set_prevcreatedate() */ + copy_tv(&prev_createdate, &(blocks->blockcreatedate)); + + /* Move b_item forward to the next block + * since we don't have the prev value for b_item and + * also don't need to update the b_item block */ + curr_height = blocks->height; + STRNCPY(curr_blockhash, blocks->blockhash); + while (b_item && blocks->height == curr_height && + strcmp(blocks->blockhash, curr_blockhash) == 0) { + b_item = next_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks, b_item); + } + } else { + /* There's none before oldest_height, so instead use: + * 'Pool Start' = first workinfo createdate */ + K_RLOCK(workinfo_free); + wi_item = first_in_ktree(workinfo_root, wi_ctx); + K_RUNLOCK(workinfo_free); + if (wi_item) { + DATA_WORKINFO(workinfo, wi_item); + copy_tv(&prev_createdate, &(workinfo->createdate)); + } else { + /* Shouldn't be possible since this function is first + * called after workinfo is loaded and the workinfo + * for each block must exist - thus data corruption */ + DATE_ZERO(&prev_createdate); + LOGEMERG("%s() DB/tree corruption - blocks exist but " + "no workinfo exist!" + WHERE_FFL, + __func__, WHERE_FFL_PASS); + ok = false; + } + b_item = first_in_ktree(blocks_root, b_ctx); + } + + // curr_* is unset and will be set first time in the while loop + curr_height = 0; + curr_blockhash[0] = '\0'; + DATE_ZERO(&curr_createdate); + currok = false; + while (b_item) { + DATA_BLOCKS(blocks, b_item); + // While the same block, keep setting it + if (blocks->height == curr_height && + strcmp(blocks->blockhash, curr_blockhash) == 0) { + copy_tv(&(blocks->prevcreatedate), &prev_createdate); + } else { + // Next block - if currok then 'prev' becomes 'curr' + if (currok) + copy_tv(&prev_createdate, &curr_createdate); + + // New curr - CURRENT will be first + if (!CURRENT(&(blocks->expirydate))) { + tv_to_buf(&(blocks->expirydate), cd_buf, + sizeof(cd_buf)); + LOGEMERG("%s() block %"PRId32"/%s/%s/%s first " + "record is not CURRENT" WHERE_FFL, + __func__, + blocks->height, blocks->blockhash, + blocks->confirmed, cd_buf, + WHERE_FFL_PASS); + ok = false; + } + + curr_height = blocks->height; + STRNCPY(curr_blockhash, blocks->blockhash); + copy_tv(&curr_createdate, &(blocks->blockcreatedate)); + + if (CURRENT(&(blocks->expirydate)) && + blocks->confirmed[0] != BLOCKS_ORPHAN && + blocks->confirmed[0] != BLOCKS_REJECT) + currok = true; + else + currok = false; + + // Set it + copy_tv(&(blocks->prevcreatedate), &prev_createdate); + } + b_item = next_in_ktree(b_ctx); + } + return ok; +} + /* 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) @@ -3031,8 +3208,7 @@ K_ITEM *first_miningpayouts(int64_t payoutid, K_TREE_CTX *ctx) miningpayouts.payoutid = payoutid; miningpayouts.userid = 0; - miningpayouts.expirydate.tv_sec = 0; - miningpayouts.expirydate.tv_usec = 0; + DATE_ZERO(&(miningpayouts.expirydate)); INIT_MININGPAYOUTS(&look); look.data = (void *)(&miningpayouts); @@ -3109,6 +3285,22 @@ cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b) return c; } +/* order by workinfoidend asc,expirydate asc + * This must use workinfoidend, not workinfoidstart, since a change + * in the payout PPLNS N could have 2 payouts with the same start, + * but currently there is only one payout per block + * i.e. workinfoidend can only have one payout */ +cmp_t cmp_payouts_wid(K_ITEM *a, K_ITEM *b) +{ + PAYOUTS *pa, *pb; + DATA_PAYOUTS(pa, a); + DATA_PAYOUTS(pb, b); + cmp_t c = CMP_BIGINT(pa->workinfoidend, pb->workinfoidend); + if (c == 0) + c = CMP_TV(pa->expirydate, pb->expirydate); + return c; +} + K_ITEM *find_payouts(int32_t height, char *blockhash) { PAYOUTS payouts; @@ -3157,6 +3349,24 @@ K_ITEM *find_payoutid(int64_t payoutid) return find_in_ktree(payouts_id_root, &look, cmp_payouts_id, ctx); } +// First payouts workinfoidend equal or before workinfoidend +K_ITEM *find_payouts_wid(int64_t workinfoidend, K_TREE_CTX *ctx) +{ + PAYOUTS payouts; + K_TREE_CTX ctx0[1]; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + payouts.workinfoidend = workinfoidend+1; + DATE_ZERO(&(payouts.expirydate)); + + INIT_PAYOUTS(&look); + look.data = (void *)(&payouts); + return find_before_in_ktree(payouts_wid_root, &look, cmp_payouts_wid, ctx); +} + /* Values from payout stats, returns -1 if statname isn't found * If code needs a value then it probably really should be a new payouts field * rather than stored in the stats passed to the pplns2 web page @@ -3252,9 +3462,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) PAYMENTS *payments; WORKINFO *workinfo; PAYOUTS *payouts, *payouts2; - BLOCKS *blocks, *blocks2; + BLOCKS *blocks; USERS *users; - K_ITEM *p_item, *old_p_item, *b_item, *b2_item, *w_item, *wb_item; + K_ITEM *p_item, *old_p_item, *b_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; @@ -3291,7 +3501,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) 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" + LOGERR("%s(): payout for block %"PRId32"/%s already exists " "%"PRId64"/%"PRId64"/%"PRId64"/%s", __func__, height, blockhash, payouts->payoutid, payouts->workinfoidstart, payouts->workinfoidend, @@ -3302,37 +3512,16 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) // Check the block status K_RLOCK(blocks_free); b_item = find_blocks(height, blockhash, b_ctx); + K_RUNLOCK(blocks_free); 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); - 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, &(blocks2->createdate)); - if (!addr_cd) - addr_cd = &(blocks2->createdate); - 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; - } + copy_tv(&end_tv, &(blocks->blockcreatedate)); + if (!addr_cd) + addr_cd = &(blocks->blockcreatedate); LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, __func__, blocks->height, blocks->workinfoid, @@ -3363,11 +3552,11 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) // Get the PPLNS N values K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(PPLNSDIFFTIMES, &(blocks->createdate), + oc_item = find_optioncontrol(PPLNSDIFFTIMES, &(blocks->blockcreatedate), height); K_RUNLOCK(optioncontrol_free); if (!oc_item) { - tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + tv_to_buf(&(blocks->blockcreatedate), cd_buf, sizeof(cd_buf)); LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", __func__, PPLNSDIFFTIMES, cd_buf, blocks->height); goto oku; @@ -3376,11 +3565,11 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) diff_times = atof(optioncontrol->optionvalue); K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(PPLNSDIFFADD, &(blocks->createdate), + oc_item = find_optioncontrol(PPLNSDIFFADD, &(blocks->blockcreatedate), height); K_RUNLOCK(optioncontrol_free); if (!oc_item) { - tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + tv_to_buf(&(blocks->blockcreatedate), cd_buf, sizeof(cd_buf)); LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", __func__, PPLNSDIFFADD, cd_buf, blocks->height); goto oku; @@ -3431,7 +3620,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) 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 */ + * which will be >= blocks->blockcreatedate */ while (total_diff < diff_want && ss_item) { switch (sharesummary->complete[0]) { case SUMMARY_CONFIRM: @@ -3584,7 +3773,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) * 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 */ + * via cmd_payouts() */ LOGEMERG("%s(): payout had < 1 (%"PRId64") workmarkers for " "block %"PRId32"/%"PRId64"/%s/%s/%"PRId64 " beginwi=%"PRId64" ss=%"PRId64" diff=%.1f", @@ -3634,6 +3823,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) bzero(payouts, sizeof(*payouts)); payouts->height = height; STRNCPY(payouts->blockhash, blockhash); + copy_tv(&(payouts->blockcreatedate), &(blocks->blockcreatedate)); d64 = blocks->reward * 9 / 1000; g64 = blocks->reward - d64; payouts->minerreward = g64; @@ -3805,20 +3995,8 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) 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) { + /* Address user or normal user without a paymentaddress */ + if (users->userbits & USER_ADDRESS) { K_WLOCK(payments_free); pay_item = k_unlink_head(payments_free); K_WUNLOCK(payments_free); @@ -3901,6 +4079,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) ss_count, wm_count, ms_count, usercount, diff_times, diff_add, cd_buf); + /* At this point the payout is complete, but it just hasn't been + * flagged complete yet in the DB */ + K_WLOCK(payouts_free); p2_item = k_unlink_head(payouts_free); K_WUNLOCK(payouts_free); @@ -3909,6 +4090,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) payouts2->payoutid = payouts->payoutid; payouts2->height = payouts->height; STRNCPY(payouts2->blockhash, payouts->blockhash); + copy_tv(&(payouts2->blockcreatedate), &(payouts->blockcreatedate)); payouts2->minerreward = payouts->minerreward; payouts2->workinfoidstart = payouts->workinfoidstart; payouts2->workinfoidend = payouts->workinfoidend; @@ -3926,13 +4108,20 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) ok = payouts_add(conn, true, p2_item, &old_p2_item, (char *)by_default, (char *)__func__, (char *)inet_default, &now, NULL, false); + if (!ok) { + /* All that's required is to mark the payout GENERATED + * since it already exists in the DB and in RAM, thus a manual + * cmd_payouts 'generated' is all that's needed to fix it */ LOGEMERG("%s(): payout %"PRId64" for block %"PRId32"/%s " "NOT set generated - it needs to be set manually", __func__, payouts->payoutid, blocks->height, blocks->blockhash); } + // Flag each shift as rewarded + reward_shifts(payouts2, true, 1); + CKPQDisco(&conn, conned); goto oku; @@ -4141,7 +4330,7 @@ K_ITEM *_find_markersummary(int64_t markerid, int64_t workinfoid, K_TREE_CTX ctx[1]; if (markerid == 0) { - wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED); + wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED, NULL); if (wm_item) { DATA_WORKMARKERS(wm, wm_item); markerid = wm->markerid; @@ -4272,12 +4461,15 @@ cmp_t cmp_workmarkers_workinfoid(K_ITEM *a, K_ITEM *b) return c; } -K_ITEM *find_workmarkers(int64_t workinfoid, bool anystatus, char status) +K_ITEM *find_workmarkers(int64_t workinfoid, bool anystatus, char status, K_TREE_CTX *ctx) { WORKMARKERS workmarkers, *wm; - K_TREE_CTX ctx[1]; + K_TREE_CTX ctx0[1]; K_ITEM look, *wm_item; + if (ctx == NULL) + ctx = ctx0; + workmarkers.expirydate.tv_sec = default_expiry.tv_sec; workmarkers.expirydate.tv_usec = default_expiry.tv_usec; workmarkers.workinfoidend = workinfoid-1; @@ -4363,8 +4555,7 @@ static bool gen_workmarkers(PGconn *conn, MARKS *stt, bool after, MARKS *fin, look.data = (void *)(&workinfo); K_RLOCK(workinfo_free); if (before) { - workinfo.expirydate.tv_sec = 0; - workinfo.expirydate.tv_usec = 0; + DATE_ZERO(&(workinfo.expirydate)); wi_fin_item = find_before_in_ktree(workinfo_root, &look, cmp_workinfo, ctx); while (wi_fin_item) { @@ -4396,7 +4587,8 @@ static bool gen_workmarkers(PGconn *conn, MARKS *stt, bool after, MARKS *fin, * sort order and matching errors */ if (wi_fin->workinfoid >= wi_stt->workinfoid) { K_RLOCK(workmarkers_free); - old_wm_item = find_workmarkers(wi_fin->workinfoid, true, '\0'); + old_wm_item = find_workmarkers(wi_fin->workinfoid, true, '\0', + NULL); K_RUNLOCK(workmarkers_free); DATA_WORKMARKERS_NULL(old_wm, old_wm_item); if (old_wm_item && (WMREADY(old_wm->status) || @@ -4575,6 +4767,68 @@ bool workmarkers_generate(PGconn *conn, char *err, size_t siz, char *by, return true; } +// delta = 1 or -1 i.e. reward or undo reward +bool reward_shifts(PAYOUTS *payouts, bool lock, int delta) +{ + // TODO: PPS calculations + K_TREE_CTX ctx[1]; + K_ITEM *wm_item; + WORKMARKERS *wm; + bool did_one = false; + + if (lock) + K_WLOCK(workmarkers_free); + + wm_item = find_workmarkers(payouts->workinfoidstart, false, + MARKER_PROCESSED, ctx); + while (wm_item) { + DATA_WORKMARKERS(wm, wm_item); + if (wm->workinfoidstart > payouts->workinfoidend) + break; + /* The status doesn't matter since we want the rewards passed + * onto the PROCESSED status if it isn't already processed */ + if (CURRENT(&(wm->expirydate))) { + wm->rewards += delta; + did_one = true; + } + wm_item = next_in_ktree(ctx); + } + + if (lock) + K_WUNLOCK(workmarkers_free); + + return did_one; +} + +// (re)calculate rewards for a shift +bool shift_rewards(K_ITEM *wm_item) +{ + PAYOUTS *payouts = NULL; + K_TREE_CTX ctx[1]; + WORKMARKERS *wm; + K_ITEM *p_item; + int rewards = 0; + + DATA_WORKMARKERS(wm, wm_item); + + // Deadlock risk since calling code should have workmarkers locked + K_RLOCK(payouts_free); + p_item = find_payouts_wid(wm->workinfoidend, ctx); + DATA_PAYOUTS_NULL(payouts, p_item); + // a workmarker should not cross a payout boundary + while (p_item && payouts->workinfoidstart <= wm->workinfoidstart && + wm->workinfoidend <= payouts->workinfoidend) { + if (CURRENT(&(payouts->expirydate))) + rewards++; + p_item = prev_in_ktree(ctx); + DATA_PAYOUTS_NULL(payouts, p_item); + } + K_RUNLOCK(payouts_free); + + wm->rewards = rewards; + return (rewards > 0); +} + // order by expirydate asc,workinfoid asc // TODO: add poolinstance cmp_t cmp_marks(K_ITEM *a, K_ITEM *b) @@ -4897,7 +5151,7 @@ void _userinfo_update(SHARES *shares, SHARESUMMARY *sharesummary, } // N.B. good blocks = blocks - (orphans + rejects) -void _userinfo_block(BLOCKS *blocks, enum info_type isnew, bool lock) +void _userinfo_block(BLOCKS *blocks, enum info_type isnew, int delta, bool lock) { USERINFO *row; K_ITEM *item; @@ -4907,12 +5161,12 @@ void _userinfo_block(BLOCKS *blocks, enum info_type isnew, bool lock) if (lock) K_WLOCK(userinfo_free); if (isnew == INFO_NEW) { - row->blocks++; + row->blocks += delta; copy_tv(&(row->last_block), &(blocks->createdate)); } else if (isnew == INFO_ORPHAN) - row->orphans++; + row->orphans += delta; else if (isnew == INFO_REJECT) - row->rejects++; + row->rejects += delta; if (lock) K_WUNLOCK(userinfo_free); diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index f2927d9e..7c6e4a6b 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -1079,11 +1079,11 @@ K_ITEM *useratts_add(PGconn *conn, char *username, char *attname, else TXT_TO_BIGINT("attnum2", attnum2, row->attnum2); if (attdate == NULL || attdate[0] == '\0') - row->attdate.tv_sec = row->attdate.tv_usec = 0L; + DATE_ZERO(&(row->attdate)); else TXT_TO_TV("attdate", attdate, row->attdate); if (attdate2 == NULL || attdate2[0] == '\0') - row->attdate2.tv_sec = row->attdate2.tv_usec = 0L; + DATE_ZERO(&(row->attdate2)); else TXT_TO_TV("attdate2", attdate2, row->attdate2); @@ -2922,7 +2922,8 @@ bool workinfo_fill(PGconn *conn) if (!ok) { free_workinfo_data(item); k_add_head(workinfo_free, item); - } + } else + ok = set_prevcreatedate(0); //K_WUNLOCK(workinfo_free); PQclear(res); @@ -2959,7 +2960,7 @@ static bool shares_process(PGconn *conn, SHARES *shares, K_TREE *trf_root) if (reloading && !confirm_sharesummary) { // We only need to know if the workmarker is processed wm_item = find_workmarkers(shares->workinfoid, false, - MARKER_PROCESSED); + MARKER_PROCESSED, NULL); if (wm_item) { LOGDEBUG("%s(): workmarker exists for wid %"PRId64 " %"PRId64"/%s/%ld,%ld", @@ -3260,7 +3261,7 @@ static bool shareerrors_process(PGconn *conn, SHAREERRORS *shareerrors, if (reloading && !confirm_sharesummary) { // We only need to know if the workmarker is processed wm_item = find_workmarkers(shareerrors->workinfoid, false, - MARKER_PROCESSED); + MARKER_PROCESSED, NULL); if (wm_item) { LOGDEBUG("%s(): workmarker exists for wid %"PRId64 " %"PRId64"/%s/%ld,%ld", @@ -4024,7 +4025,8 @@ bool _sharesummary_update(SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, } K_RLOCK(workmarkers_free); - wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED); + wm_item = find_workmarkers(workinfoid, false, MARKER_PROCESSED, + NULL); K_RUNLOCK(workmarkers_free); if (wm_item) { DATA_WORKMARKERS(wm, wm_item); @@ -4298,6 +4300,7 @@ unparam: blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); k_add_head(blocks_store, b_item); blocks_stats_rebuild = true; + // 'confirmed' is unchanged so no need to recalc *createdate } K_WUNLOCK(blocks_free); @@ -4438,7 +4441,7 @@ bool blocks_add(PGconn *conn, char *height, char *blockhash, } // We didn't use a Begin ok = true; - userinfo_block(row, INFO_NEW); + userinfo_block(row, INFO_NEW, 1); goto unparam; break; case BLOCKS_ORPHAN: @@ -4597,10 +4600,49 @@ bool blocks_add(PGconn *conn, char *height, char *blockhash, } update_old = true; - if (confirmed[0] == BLOCKS_ORPHAN) - userinfo_block(row, INFO_ORPHAN); - else if (confirmed[0] == BLOCKS_REJECT) - userinfo_block(row, INFO_REJECT); + /* handle confirmed state changes for userinfo + * this case statement handles all possible combinations + * even if they can't happen (yet) */ + switch (oldblocks->confirmed[0]) { + case BLOCKS_ORPHAN: + switch (confirmed[0]) { + case BLOCKS_ORPHAN: + break; + case BLOCKS_REJECT: + userinfo_block(row, INFO_ORPHAN, -1); + userinfo_block(row, INFO_REJECT, 1); + break; + default: + userinfo_block(row, INFO_ORPHAN, -1); + break; + } + break; + case BLOCKS_REJECT: + switch (confirmed[0]) { + case BLOCKS_REJECT: + break; + case BLOCKS_ORPHAN: + userinfo_block(row, INFO_REJECT, -1); + userinfo_block(row, INFO_ORPHAN, 1); + break; + default: + userinfo_block(row, INFO_REJECT, -1); + break; + } + break; + default: + switch (confirmed[0]) { + case BLOCKS_ORPHAN: + userinfo_block(row, INFO_ORPHAN, 1); + break; + case BLOCKS_REJECT: + userinfo_block(row, INFO_REJECT, 1); + break; + default: + break; + } + break; + } break; default: LOGERR("%s(): %s.failed.invalid confirm='%s'", @@ -4638,6 +4680,9 @@ flail: blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); k_add_head(blocks_store, b_item); blocks_stats_rebuild = true; + // recalc the *createdate fields for possibly affected blocks + set_blockcreatedate(row->height); + set_prevcreatedate(row->height); } K_WUNLOCK(blocks_free); @@ -4711,6 +4756,7 @@ bool blocks_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; + K_TREE_CTX ctx[1]; K_ITEM *item; int n, i; BLOCKS *row; @@ -4862,16 +4908,30 @@ bool blocks_fill(PGconn *conn) pool.height = row->height; } - if (CURRENT(&(row->expirydate))) { - _userinfo_block(row, INFO_NEW, false); - if (row->confirmed[0] == BLOCKS_ORPHAN) - _userinfo_block(row, INFO_ORPHAN, false); - else if (row->confirmed[0] == BLOCKS_REJECT) - _userinfo_block(row, INFO_REJECT, false); - } + // first add all the NEW blocks + if (row->confirmed[0] == BLOCKS_NEW) + _userinfo_block(row, INFO_NEW, 1, false); } + if (!ok) k_add_head(blocks_free, item); + else + ok = set_blockcreatedate(0); + + // Now update all the CURRENT orphan/reject stats + if (ok) { + item = first_in_ktree(blocks_root, ctx); + while (item) { + DATA_BLOCKS(row, item); + if (CURRENT(&(row->expirydate))) { + if (row->confirmed[0] == BLOCKS_ORPHAN) + _userinfo_block(row, INFO_ORPHAN, 1, false); + else if (row->confirmed[0] == BLOCKS_REJECT) + _userinfo_block(row, INFO_REJECT, 1, false); + } + item = next_in_ktree(ctx); + } + } K_WUNLOCK(blocks_free); PQclear(res); @@ -5118,12 +5178,15 @@ void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, tv_t *cd) 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); + payouts_wid_root = remove_from_ktree(payouts_wid_root, old_p_item, cmp_payouts_wid); 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_wid_root = add_to_ktree(payouts_wid_root, old_p_item, cmp_payouts_wid); } 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); + payouts_wid_root = add_to_ktree(payouts_wid_root, p_item, cmp_payouts_wid); k_add_head(payouts_store, p_item); } K_WUNLOCK(payouts_free); @@ -5430,9 +5493,11 @@ K_ITEM *payouts_full_expire(PGconn *conn, int64_t payoutid, tv_t *now, bool lock DATA_PAYOUTS(payouts, po_item); payouts_root = remove_from_ktree(payouts_root, po_item, cmp_payouts); payouts_id_root = remove_from_ktree(payouts_id_root, po_item, cmp_payouts_id); + payouts_wid_root = remove_from_ktree(payouts_wid_root, po_item, cmp_payouts_wid); copy_tv(&(payouts->expirydate), now); payouts_root = add_to_ktree(payouts_root, po_item, cmp_payouts); payouts_id_root = add_to_ktree(payouts_id_root, po_item, cmp_payouts_id); + payouts_wid_root = add_to_ktree(payouts_wid_root, po_item, cmp_payouts_wid); mp_item = first_miningpayouts(payoutid, mp_ctx); DATA_MININGPAYOUTS_NULL(mp, mp_item); @@ -5465,6 +5530,12 @@ K_ITEM *payouts_full_expire(PGconn *conn, int64_t payoutid, tv_t *now, bool lock DATA_PAYMENTS_NULL(payments, pm_item); } + if (PAYGENERATED(payouts->status)) { + // Original was generated, so undo the reward + reward_shifts(payouts, true, -1); + + } + ok = true; matane: if (begun) @@ -5494,8 +5565,10 @@ bool payouts_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; - K_ITEM *item; + K_ITEM *item, *b_item; + K_TREE_CTX ctx[1]; PAYOUTS *row; + BLOCKS *blocks; int n, i; char *field; char *sel; @@ -5609,10 +5682,30 @@ bool payouts_fill(PGconn *conn) if (!ok) break; + // This also of course, verifies the payouts -> blocks reference + b_item = find_blocks(row->height, row->blockhash, ctx); + if (!b_item) { + LOGERR("%s(): payoutid %"PRId64" references unknown " + "block %"PRId32"/%s", + __func__, row->payoutid, row->height, + row->blockhash); + ok = false; + break; + } else { + // blockcreatedate will already be set + DATA_BLOCKS(blocks, b_item); + copy_tv(&(row->blockcreatedate), + &(blocks->blockcreatedate)); + } + payouts_root = add_to_ktree(payouts_root, item, cmp_payouts); payouts_id_root = add_to_ktree(payouts_id_root, item, cmp_payouts_id); + payouts_wid_root = add_to_ktree(payouts_wid_root, item, cmp_payouts_wid); k_add_head(payouts_store, item); + if (CURRENT(&(row->expirydate)) && PAYGENERATED(row->status)) + reward_shifts(row, false, 1); + tick(); } if (!ok) { @@ -6517,7 +6610,7 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, if (markerid == 0) { K_RLOCK(workmarkers_free); - old_wm_item = find_workmarkers(workinfoidend, true, '\0'); + old_wm_item = find_workmarkers(workinfoidend, true, '\0', NULL); K_RUNLOCK(workmarkers_free); } else { K_RLOCK(workmarkers_free); @@ -6691,6 +6784,8 @@ unparam: cmp_workmarkers_workinfoid); } if (wm_item) { + shift_rewards(wm_item); + workmarkers_root = add_to_ktree(workmarkers_root, wm_item, cmp_workmarkers); diff --git a/src/generator.c b/src/generator.c index be8c5db5..b5fd9e48 100644 --- a/src/generator.c +++ b/src/generator.c @@ -1715,7 +1715,7 @@ static void *proxy_send(void *arg) return NULL; } -static void passthrough_send(ckpool_t __maybe_unused *ckp, pass_msg_t *pm) +static void passthrough_send(ckpool_t *ckp, pass_msg_t *pm) { int len, sent; @@ -1723,8 +1723,9 @@ static void passthrough_send(ckpool_t __maybe_unused *ckp, pass_msg_t *pm) len = strlen(pm->msg); sent = write_socket(pm->cs->fd, pm->msg, len); if (sent != len) { - /* FIXME: Do something about this? */ - LOGWARNING("Failed to passthrough %d bytes of message %s", len, pm->msg); + LOGWARNING("Failed to passthrough %d bytes of message %s, attempting reconnect", + len, pm->msg); + send_proc(ckp->generator, "reconnect"); } free(pm->msg); free(pm); diff --git a/src/ktree.c b/src/ktree.c index c94d12fb..588683b0 100644 --- a/src/ktree.c +++ b/src/ktree.c @@ -89,15 +89,11 @@ static void show_ktree(K_TREE *root, char *path, int pos, char *(*dsp_funct)(K_I path[pos] = '\0'; - switch(root->red) - { - case RED_RED: + if (root->red == RED_RED) col = 'R'; - break; - case RED_BLACK: + else + // if (root->red == RED_BLACK) col = 'B'; - break; - } printf(" %c %s=%s\n", col, path, dsp_funct(root->data));