diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-23 11:41:28 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-23 11:41:28 +0000 |
| commit | 9d6fcb2058d90e3ecfb011e2168e4eee04e3a90a (patch) | |
| tree | 334e7c0297c858c07f1f42037ac96752a4aede0e /src | |
| parent | d273a04ff89233f0f01431bd6e3c18d5261818b0 (diff) | |
payout: ask the wallet why a send failed instead of guessing from the balance
A payment that the pool could not fund was being recorded as a fault
against the payment, and 1440 faults erased the debt with the coin still
in the operator's wallet.
The classifier read GetBalance() after the failure and asked whether it
covered `amount + nTransactionFee + CENT`. Every term of that was wrong.
nTransactionFee is 0 on this pool -- it is only ever assigned from the
GUI preferences dialog, which a headless node does not have -- so the
entire reserve was one CENT. The fee it is meant to reserve for is
GetMinFee(fDiscount=true), which is a step function of the serialised
size: 0 below 10,000 bytes and (1 + bytes/1000) * CENT at or above it.
There is no required-fee value between 1 and 11 CENT, so a one-CENT
reserve is never the right number -- below the step nothing needs
reserving, at the step it is short by a factor of eleven.
Worse, the trigger correlates with the failure. A pool wallet close to
broke selects many small inputs BECAUSE it is close to broke, and about
56 signed P2PKH inputs is 10 KB, which a pool that has been paying out
accumulates as change. So: balance of amount + 0.05, the pre-send gate
passes, CreateTransaction builds a transaction over 10 KB, the fee
becomes 0.11, SelectCoins(amount + 0.11) fails, SendMoney returns false
-- and (amount + 0.05) < (amount + 0 + 0.01) is false, so "funded", so a
fault. The condition is stable, so it recurred every 60 s until the
budget ran out and ForfeitPayoutRecipient erased the miner's money.
Three changes.
The wallet now says what it did. CreateTransaction takes an optional
WalletSendResult out-parameter and sets WALLET_SEND_INSUFFICIENT_FUNDS
on the one return that means a funding shortfall -- SelectCoins failing
to cover nValue plus whatever fee that pass is working with, which on
the second and later passes is GetMinFee() of the transaction the first
pass actually built. SendMoney gains a four-argument overload returning
that plus the fee that was required. The three-argument SendMoney stays,
as a separate declaration rather than a default argument, because
gui.cpp declares it itself instead of including main.h; walletcmd.cpp
and the GUI are unchanged. These are wallet functions, not consensus.
ClassifyPayoutSendFailure() then classifies the answer rather than
guessing: locked or short of coin defers, anything else is a fault.
There is no reserve left to get wrong, and the size-dependent fee is
accounted for exactly, because the wallet computed it.
And the give-up branch is gone. 1440 failures is not a decidable state,
it is a count of how long we have been asking, and this pool reached it
by misclassifying a permanent funding shortfall. A payment that keeps
failing now stays in flight, keeps its ledger entry, keeps being
reported as owed in pool_status.json, and keeps retrying once a minute.
A debt that is never written off cannot be written off wrongly. The
remaining call to ForfeitPayoutRecipient is the one decidable case --
the ledger names a string that is not a valid address, so no transaction
can pay it -- which previously only dropped the in-flight record and
left the ledger and the round proof claiming the debt forever.
Tested against a real wallet and the real CreateTransaction, not against
injected values, because the honest question is whether a wallet holding
many small coins reports the state this code assumes. 60 coins of 1 BTF,
balance 60.00, amount 59.95: CreateTransaction returns false with
WALLET_SEND_INSUFFICIENT_FUNDS and a required fee of 0.11 -- and since
GetMinFee returns 0 below 10,000 bytes, a required fee of 11 CENT is
itself the measurement that the transaction crossed the step. The same
test asserts that the old reserve judged that state "funded", and that
the same wallet pays a 3-input transaction with a required fee of 0.00,
which is why no fixed reserve is right for both.
Property: a send that failed because the wallet could not fund it is
deferred, whatever the transaction's size, and no number of failures
erases a debt.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 47 | ||||
| -rw-r--r-- | src/main.h | 45 | ||||
| -rw-r--r-- | src/rpc.cpp | 441 |
3 files changed, 454 insertions, 79 deletions
diff --git a/src/main.cpp b/src/main.cpp index 252911b..7b3e555 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -4907,9 +4907,17 @@ bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet) | |||
| 4907 | 4907 | ||
| 4908 | 4908 | ||
| 4909 | 4909 | ||
| 4910 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) | 4910 | // presultRet, when given, says WHY this returned false: the caller cannot work |
| 4911 | // it out afterwards. "Coin selection came up short" and "the wallet could not | ||
| 4912 | // build the transaction" are indistinguishable from the outside -- both are | ||
| 4913 | // just `false` -- and the difference decides whether a pool defers a payment | ||
| 4914 | // or counts it against the payment's retry budget. See WalletSendResult. | ||
| 4915 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet, | ||
| 4916 | WalletSendResult* presultRet) | ||
| 4911 | { | 4917 | { |
| 4912 | nFeeRequiredRet = 0; | 4918 | nFeeRequiredRet = 0; |
| 4919 | if (presultRet) | ||
| 4920 | *presultRet = WALLET_SEND_FAILED; | ||
| 4913 | CRITICAL_BLOCK(cs_main) | 4921 | CRITICAL_BLOCK(cs_main) |
| 4914 | { | 4922 | { |
| 4915 | // txdb must be opened before the mapWallet lock | 4923 | // txdb must be opened before the mapWallet lock |
| @@ -4940,10 +4948,21 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 4940 | // fixed this in 107d9e288. | 4948 | // fixed this in 107d9e288. |
| 4941 | int64 nTotalValue = nValue + nFee; | 4949 | int64 nTotalValue = nValue + nFee; |
| 4942 | 4950 | ||
| 4943 | // Choose coins to use | 4951 | // Choose coins to use. |
| 4952 | // | ||
| 4953 | // Failing here is a funding shortfall and nothing else: there | ||
| 4954 | // is not enough spendable coin for nValue plus the fee this | ||
| 4955 | // pass is working with. On the second and later passes that | ||
| 4956 | // fee is GetMinFee() of the transaction the first pass built, | ||
| 4957 | // so this is also how a wallet that can afford the amount but | ||
| 4958 | // not the 11 * CENT charged at 10,000 bytes reports itself. | ||
| 4944 | set<CWalletTx*> setCoins; | 4959 | set<CWalletTx*> setCoins; |
| 4945 | if (!SelectCoins(nTotalValue, setCoins)) | 4960 | if (!SelectCoins(nTotalValue, setCoins)) |
| 4961 | { | ||
| 4962 | if (presultRet) | ||
| 4963 | *presultRet = WALLET_SEND_INSUFFICIENT_FUNDS; | ||
| 4946 | return false; | 4964 | return false; |
| 4965 | } | ||
| 4947 | int64 nValueIn = 0; | 4966 | int64 nValueIn = 0; |
| 4948 | foreach(CWalletTx* pcoin, setCoins) | 4967 | foreach(CWalletTx* pcoin, setCoins) |
| 4949 | nValueIn += pcoin->GetCredit(); | 4968 | nValueIn += pcoin->GetCredit(); |
| @@ -5013,6 +5032,8 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 5013 | } | 5032 | } |
| 5014 | } | 5033 | } |
| 5015 | } | 5034 | } |
| 5035 | if (presultRet) | ||
| 5036 | *presultRet = WALLET_SEND_OK; | ||
| 5016 | return true; | 5037 | return true; |
| 5017 | } | 5038 | } |
| 5018 | 5039 | ||
| @@ -5075,13 +5096,24 @@ bool CommitTransactionSpent(const CWalletTx& wtxNew) | |||
| 5075 | 5096 | ||
| 5076 | 5097 | ||
| 5077 | 5098 | ||
| 5078 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | 5099 | // outcomeRet reports what happened rather than only whether it worked. A |
| 5100 | // caller that has to decide what a failure MEANS -- the pool payout thread -- | ||
| 5101 | // cannot reconstruct it from a balance reading afterwards, because the fee | ||
| 5102 | // that was actually required depends on the size of a transaction that was | ||
| 5103 | // never built. See WalletSendResult. | ||
| 5104 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, | ||
| 5105 | SendMoneyOutcome& outcomeRet) | ||
| 5079 | { | 5106 | { |
| 5107 | outcomeRet.result = WALLET_SEND_FAILED; | ||
| 5108 | outcomeRet.nFeeRequired = 0; | ||
| 5080 | CRITICAL_BLOCK(cs_main) | 5109 | CRITICAL_BLOCK(cs_main) |
| 5081 | { | 5110 | { |
| 5082 | int64 nFeeRequired; | 5111 | int64 nFeeRequired; |
| 5083 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) | 5112 | WalletSendResult createResult = WALLET_SEND_FAILED; |
| 5113 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired, &createResult)) | ||
| 5084 | { | 5114 | { |
| 5115 | outcomeRet.result = createResult; | ||
| 5116 | outcomeRet.nFeeRequired = nFeeRequired; | ||
| 5085 | string strError; | 5117 | string strError; |
| 5086 | if (nValue + nFeeRequired > GetBalance()) | 5118 | if (nValue + nFeeRequired > GetBalance()) |
| 5087 | // Not an oversized transaction -- this branch is simply "the | 5119 | // Not an oversized transaction -- this branch is simply "the |
| @@ -5119,6 +5151,13 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | |||
| 5119 | } | 5151 | } |
| 5120 | wtxNew.RelayWalletTransaction(); | 5152 | wtxNew.RelayWalletTransaction(); |
| 5121 | } | 5153 | } |
| 5154 | outcomeRet.result = WALLET_SEND_OK; | ||
| 5122 | MainFrameRepaint(); | 5155 | MainFrameRepaint(); |
| 5123 | return true; | 5156 | return true; |
| 5124 | } | 5157 | } |
| 5158 | |||
| 5159 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | ||
| 5160 | { | ||
| 5161 | SendMoneyOutcome ignored; | ||
| 5162 | return SendMoney(scriptPubKey, nValue, wtxNew, ignored); | ||
| 5163 | } | ||
| @@ -342,9 +342,52 @@ struct WalletRecoveryAudit | |||
| 342 | } | 342 | } |
| 343 | }; | 343 | }; |
| 344 | WalletRecoveryAudit GetWalletRecoveryAudit(); | 344 | WalletRecoveryAudit GetWalletRecoveryAudit(); |
| 345 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& txNew, int64& nFeeRequiredRet); | 345 | |
| 346 | // Why a send did not go through. | ||
| 347 | // | ||
| 348 | // The pool's payout thread has to decide, on every failed SendMoney, whether | ||
| 349 | // the failure was the wallet's condition (wait for it to change) or a fault | ||
| 350 | // (something is wrong with this payment). It used to guess by re-reading | ||
| 351 | // GetBalance() afterwards and allowing a fixed CENT of headroom for the fee. | ||
| 352 | // That cannot work: the fee here is a step function of the SERIALISED SIZE of | ||
| 353 | // a transaction that no longer exists, and the step is from 0 to 11 * CENT at | ||
| 354 | // 10,000 bytes -- so a one-CENT allowance is either unnecessary or an order of | ||
| 355 | // magnitude short, never right. A pool wallet close to broke selects many | ||
| 356 | // small inputs BECAUSE it is close to broke, which is what pushes the | ||
| 357 | // transaction over 10 KB, so the guess is wrong exactly when the money is | ||
| 358 | // tight. | ||
| 359 | // | ||
| 360 | // The wallet already knows the answer; it just did not say. SelectCoins() | ||
| 361 | // failing is "there is not enough spendable coin to cover the amount plus | ||
| 362 | // whatever fee this size of transaction requires", and it is the only reason | ||
| 363 | // CreateTransaction() can fail that is about funding rather than about the | ||
| 364 | // wallet being broken. | ||
| 365 | enum WalletSendResult | ||
| 366 | { | ||
| 367 | WALLET_SEND_OK = 0, | ||
| 368 | WALLET_SEND_INSUFFICIENT_FUNDS, // coin selection could not cover amount+fee | ||
| 369 | WALLET_SEND_FAILED // anything else: could not build, sign or accept | ||
| 370 | }; | ||
| 371 | |||
| 372 | // What the wallet did with a send request. nFeeRequired is the fee the | ||
| 373 | // transaction turned out to need, which for a failed send is what coin | ||
| 374 | // selection was asked for on top of the amount and could not find -- 0 if it | ||
| 375 | // never got that far. | ||
| 376 | struct SendMoneyOutcome | ||
| 377 | { | ||
| 378 | WalletSendResult result = WALLET_SEND_OK; | ||
| 379 | int64 nFeeRequired = 0; | ||
| 380 | }; | ||
| 381 | |||
| 382 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& txNew, int64& nFeeRequiredRet, | ||
| 383 | WalletSendResult* presultRet=NULL); | ||
| 346 | bool CommitTransactionSpent(const CWalletTx& wtxNew); | 384 | bool CommitTransactionSpent(const CWalletTx& wtxNew); |
| 385 | // The three-argument form is kept, and kept as a distinct declaration rather | ||
| 386 | // than a defaulted argument, because gui.cpp declares SendMoney itself instead | ||
| 387 | // of including this header. | ||
| 347 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); | 388 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); |
| 389 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, | ||
| 390 | SendMoneyOutcome& outcomeRet); | ||
| 348 | 391 | ||
| 349 | 392 | ||
| 350 | 393 | ||
diff --git a/src/rpc.cpp b/src/rpc.cpp index 403cb14..06de7f2 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -1700,8 +1700,11 @@ static void ForgetPayment(const PayingRecord& r) | |||
| 1700 | 1700 | ||
| 1701 | // When the last record for a round is gone the round is no longer in | 1701 | // When the last record for a round is gone the round is no longer in |
| 1702 | // flight, so a later flush may explode it again if the ledger still | 1702 | // flight, so a later flush may explode it again if the ledger still |
| 1703 | // shows recipients owed (a give-up, for instance, leaves the entry | 1703 | // shows recipients owed. That is the intended behaviour and it is now |
| 1704 | // standing). | 1704 | // the only way a stalled payment ever leaves this list: a payment that |
| 1705 | // keeps failing is retried forever rather than given up on, so its | ||
| 1706 | // record stays here, and the flush that would re-explode it is stopped | ||
| 1707 | // by PaymentInFlightLocked. | ||
| 1705 | bool stillInFlight = false; | 1708 | bool stillInFlight = false; |
| 1706 | for (const auto& e : gPaying) | 1709 | for (const auto& e : gPaying) |
| 1707 | if (SamePayoutRound(e.blockHeight, e.blockHash, | 1710 | if (SamePayoutRound(e.blockHeight, e.blockHash, |
| @@ -1776,9 +1779,16 @@ static void RecordPayoutTxid(const PayingRecord& r, const std::string& txid) | |||
| 1776 | LogPrint("payout", "[payout] block %d fully settled\n", r.blockHeight); | 1779 | LogPrint("payout", "[payout] block %d fully settled\n", r.blockHeight); |
| 1777 | } | 1780 | } |
| 1778 | 1781 | ||
| 1779 | // Write a recipient off. Only reached after a full budget of failures that | 1782 | // Write a recipient off. |
| 1780 | // were NOT the wallet being locked or short of coin -- see the classification | 1783 | // |
| 1781 | // in PayoutThreadFn -- against a block still on the main chain. | 1784 | // Only ever called for a state that is DECIDABLE -- currently one: the ledger |
| 1785 | // names an address that is not a valid address, so no transaction paying it | ||
| 1786 | // can be constructed by this or any other build. It is not called, and must | ||
| 1787 | // not be, because a payment has failed some number of times. "The wallet said | ||
| 1788 | // no N times" is not a decidable state, it is a description of how long we | ||
| 1789 | // have been asking; a debt written off on the strength of it is a debt the | ||
| 1790 | // operator keeps, and the arithmetic that decided WHY the wallet said no was | ||
| 1791 | // wrong for a year (see PayoutThreadFn). | ||
| 1782 | // | 1792 | // |
| 1783 | // The amount is removed from the entry rather than marked paid, so the money | 1793 | // The amount is removed from the entry rather than marked paid, so the money |
| 1784 | // is not reported as sent. Once nothing is left owed the entry retires. The | 1794 | // is not reported as sent. Once nothing is left owed the entry retires. The |
| @@ -1810,6 +1820,60 @@ static void ForfeitPayoutRecipient(const PayingRecord& r, const std::string& rea | |||
| 1810 | r.address, reason); | 1820 | r.address, reason); |
| 1811 | } | 1821 | } |
| 1812 | 1822 | ||
| 1823 | // What a failed send means for the payment's retry budget. | ||
| 1824 | // | ||
| 1825 | // A deferral is "the wallet's condition is temporarily wrong": wait, and do | ||
| 1826 | // not hold it against this payment. A fault is "the wallet is funded and | ||
| 1827 | // unlocked and still would not do it", which is worth logging loudly and | ||
| 1828 | // retrying with backoff. | ||
| 1829 | // | ||
| 1830 | // This used to be decided by re-reading GetBalance() after the failure and | ||
| 1831 | // comparing it against `amount + nTransactionFee + CENT`. Every term of that | ||
| 1832 | // was wrong: | ||
| 1833 | // | ||
| 1834 | // * nTransactionFee is 0 in production. It is only ever assigned from the | ||
| 1835 | // GUI preferences dialog (gui.cpp), which a headless pool node does not | ||
| 1836 | // have, so the whole reserve was the CENT. | ||
| 1837 | // | ||
| 1838 | // * The fee the reserve is for is GetMinFee(fDiscount=true), a STEP | ||
| 1839 | // function of the serialised size: 0 below 10,000 bytes, and | ||
| 1840 | // (1 + bytes/1000) * CENT at or above it -- so 11 * CENT at the step. | ||
| 1841 | // There is no required-fee value between 1 and 11 CENT. A one-CENT | ||
| 1842 | // reserve is therefore never the right number: below the step nothing | ||
| 1843 | // needs reserving, and at the step it is short by a factor of eleven. | ||
| 1844 | // | ||
| 1845 | // * A pool wallet close to broke selects many small inputs BECAUSE it is | ||
| 1846 | // close to broke, and ~56 signed P2PKH inputs is 10 KB. The trigger and | ||
| 1847 | // the failure are positively correlated: the guess is wrong precisely | ||
| 1848 | // when the pool is nearly out of money. | ||
| 1849 | // | ||
| 1850 | // Concretely, with a balance of amount + 0.05: the pre-send gate passes, | ||
| 1851 | // CreateTransaction builds a >10 KB transaction, the fee becomes 0.11, | ||
| 1852 | // SelectCoins(amount + 0.11) fails, SendMoney returns false -- and the old | ||
| 1853 | // arithmetic said (amount + 0.05) < (amount + 0.01) is false, so "funded", | ||
| 1854 | // so a fault. The condition is stable, so it recurred every 60 s, and 1440 of | ||
| 1855 | // them erased the debt with the coin still in the operator's wallet. | ||
| 1856 | // | ||
| 1857 | // So stop guessing. The wallet is asked to say what it did, and this only | ||
| 1858 | // classifies the answer. WALLET_SEND_INSUFFICIENT_FUNDS is coin selection | ||
| 1859 | // failing to cover amount plus whatever fee that size of transaction | ||
| 1860 | // required, whatever that turned out to be -- no reserve to get wrong. | ||
| 1861 | enum PayoutFailureClass { | ||
| 1862 | PAYOUT_FAILURE_DEFER = 0, // conditions are wrong; wait for them to change | ||
| 1863 | PAYOUT_FAILURE_FAULT // funded and unlocked, and it still would not go | ||
| 1864 | }; | ||
| 1865 | |||
| 1866 | static PayoutFailureClass ClassifyPayoutSendFailure(WalletSendResult result, | ||
| 1867 | bool fWalletLocked) | ||
| 1868 | { | ||
| 1869 | // A locked wallet cannot sign. Nothing about this payment is wrong. | ||
| 1870 | if (fWalletLocked) | ||
| 1871 | return PAYOUT_FAILURE_DEFER; | ||
| 1872 | if (result == WALLET_SEND_INSUFFICIENT_FUNDS) | ||
| 1873 | return PAYOUT_FAILURE_DEFER; | ||
| 1874 | return PAYOUT_FAILURE_FAULT; | ||
| 1875 | } | ||
| 1876 | |||
| 1813 | // Payout execution thread: works through gPaying, calls SendMoney. | 1877 | // Payout execution thread: works through gPaying, calls SendMoney. |
| 1814 | // Runs independently of the event loop so slow wallet operations | 1878 | // Runs independently of the event loop so slow wallet operations |
| 1815 | // (SendMoney takes cs_main) never stall miner I/O. | 1879 | // (SendMoney takes cs_main) never stall miner I/O. |
| @@ -1867,9 +1931,24 @@ static void PayoutThreadFn(void*) | |||
| 1867 | 1931 | ||
| 1868 | uint160 h160; | 1932 | uint160 h160; |
| 1869 | if (!AddressToHash160(r.address, h160)) { | 1933 | if (!AddressToHash160(r.address, h160)) { |
| 1870 | // Should not happen -- bad addresses are filtered in QueuePayouts | 1934 | // Should not happen -- bad addresses are filtered in |
| 1871 | LogPrint("payout", "[payout] SKIPPED bad address '%s'\n", | 1935 | // QueuePayouts. If one is here anyway it is decidable and |
| 1872 | r.address.c_str()); | 1936 | // permanent: there is no transaction that pays a string which |
| 1937 | // is not an address, so retrying is not waiting for anything. | ||
| 1938 | // | ||
| 1939 | // This used to only ForgetPayment(), which drops the in-flight | ||
| 1940 | // record and leaves the ledger entry claiming the debt and | ||
| 1941 | // pool_rounds.json advertising the recipient as unpaid | ||
| 1942 | // forever, with the next flush re-exploding it. Write it off | ||
| 1943 | // properly and say why. | ||
| 1944 | const std::string reason = | ||
| 1945 | strprintf("'%s' is not a valid address, so no transaction" | ||
| 1946 | " can pay it", r.address.c_str()); | ||
| 1947 | LogPrint("payout", "[payout] SKIPPED bad address '%s' --" | ||
| 1948 | " %s owed for block %d cannot be paid\n", | ||
| 1949 | r.address.c_str(), FormatMoney(r.amount).c_str(), | ||
| 1950 | r.blockHeight); | ||
| 1951 | ForfeitPayoutRecipient(r, reason); | ||
| 1873 | ForgetPayment(r); | 1952 | ForgetPayment(r); |
| 1874 | continue; | 1953 | continue; |
| 1875 | } | 1954 | } |
| @@ -1926,13 +2005,17 @@ static void PayoutThreadFn(void*) | |||
| 1926 | // the operator's wallet and nothing on disk saying it was owed. | 2005 | // the operator's wallet and nothing on disk saying it was owed. |
| 1927 | // | 2006 | // |
| 1928 | // nTransactionFee is the part of the fee that can be known before | 2007 | // nTransactionFee is the part of the fee that can be known before |
| 1929 | // the transaction exists. The rest is GetMinFee(), a function of | 2008 | // the transaction exists, and in production it is 0 -- it is only |
| 1930 | // the serialised size and therefore of how many inputs SelectCoins | 2009 | // ever assigned from the GUI preferences dialog, which a pool node |
| 1931 | // ends up picking, which cannot be predicted from here -- so this | 2010 | // does not have -- so including it here changes nothing on this |
| 1932 | // gate deliberately does NOT try to reserve it. Refusing to attempt | 2011 | // pool and is kept only because a GUI operator can set it. The |
| 1933 | // a payment the wallet could actually have made would be its own | 2012 | // rest is GetMinFee(), a function of the serialised size and |
| 1934 | // way of not paying a miner. That residual is handled after the | 2013 | // therefore of how many inputs SelectCoins ends up picking, which |
| 1935 | // fact, by classifying the failure below. | 2014 | // cannot be predicted from here -- so this gate deliberately does |
| 2015 | // NOT try to reserve it. Refusing to attempt a payment the wallet | ||
| 2016 | // could actually have made would be its own way of not paying a | ||
| 2017 | // miner. That residual is handled after the fact, by asking the | ||
| 2018 | // wallet why it refused -- see ClassifyPayoutSendFailure. | ||
| 1936 | int64 nSpendable = GetBalance(); | 2019 | int64 nSpendable = GetBalance(); |
| 1937 | if (nSpendable < r.amount + nTransactionFee) { | 2020 | if (nSpendable < r.amount + nTransactionFee) { |
| 1938 | r.deferrals++; | 2021 | r.deferrals++; |
| @@ -1977,7 +2060,8 @@ static void PayoutThreadFn(void*) | |||
| 1977 | wtx.mapValue["pool_payout_address"] = r.address; | 2060 | wtx.mapValue["pool_payout_address"] = r.address; |
| 1978 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount); | 2061 | wtx.mapValue["pool_payout_amount"] = strprintf("%lld", r.amount); |
| 1979 | 2062 | ||
| 1980 | if (SendMoney(sc, r.amount, wtx)) { | 2063 | SendMoneyOutcome outcome; |
| 2064 | if (SendMoney(sc, r.amount, wtx, outcome)) { | ||
| 1981 | // The wallet holds the transaction before this returns, so a | 2065 | // The wallet holds the transaction before this returns, so a |
| 1982 | // crash between here and the ledger write is recoverable: the | 2066 | // crash between here and the ledger write is recoverable: the |
| 1983 | // probe above finds it by id+address+amount and does not | 2067 | // probe above finds it by id+address+amount and does not |
| @@ -1989,74 +2073,67 @@ static void PayoutThreadFn(void*) | |||
| 1989 | FormatMoney(r.amount).c_str(), r.address.c_str(), | 2073 | FormatMoney(r.amount).c_str(), r.address.c_str(), |
| 1990 | wtx.GetHash().ToString().substr(0, 12).c_str()); | 2074 | wtx.GetHash().ToString().substr(0, 12).c_str()); |
| 1991 | } else { | 2075 | } else { |
| 1992 | // Why did it fail? The answer decides whether this costs a | 2076 | // Why did it fail? The answer used to decide whether the |
| 1993 | // retry, and the retry budget is what eventually erases a | 2077 | // payment eventually got erased, and it was decided by |
| 1994 | // miner's money -- so guessing here is how a pool keeps coin | 2078 | // guessing from a balance snapshot -- see |
| 1995 | // it owes. | 2079 | // ClassifyPayoutSendFailure for the arithmetic and what it |
| 2080 | // cost. The wallet now says what it did, and nothing here | ||
| 2081 | // erases anything. | ||
| 1996 | // | 2082 | // |
| 1997 | // Re-read the conditions rather than trusting the ones checked | 2083 | // The lock state is re-read rather than trusted from before |
| 1998 | // before the attempt: SendMoney takes cs_main and can block for | 2084 | // the attempt: SendMoney takes cs_main and can block for a |
| 1999 | // a long time, so the balance and the lock state may both have | 2085 | // long time, so it may have moved underneath us. The funding |
| 2000 | // moved underneath it. | 2086 | // answer does not need re-reading -- it is a statement about |
| 2001 | // | 2087 | // the attempt that was actually made. |
| 2002 | // Short of coin covers the size-dependent part of the fee that | 2088 | bool fLocked = IsWalletLocked(); |
| 2003 | // the gate above deliberately does not reserve: if the balance | 2089 | int64 nAfter = GetBalance(); |
| 2004 | // no longer covers amount + configured fee + one fee tier, the | 2090 | |
| 2005 | // failure is a funding shortfall, not a fault. | 2091 | if (ClassifyPayoutSendFailure(outcome.result, fLocked) |
| 2006 | int64 nAfter = GetBalance(); | 2092 | == PAYOUT_FAILURE_DEFER) { |
| 2007 | bool fLocked = IsWalletLocked(); | 2093 | // Conditions are temporarily wrong. Wait. |
| 2008 | bool fUnfunded = nAfter < r.amount + nTransactionFee + CENT; | ||
| 2009 | |||
| 2010 | if (fLocked || fUnfunded) { | ||
| 2011 | // Conditions are temporarily wrong. Wait; do not spend the | ||
| 2012 | // budget that leads to a permanent write-off. | ||
| 2013 | r.deferrals++; | 2094 | r.deferrals++; |
| 2014 | r.retryAfter = GetTime() + 60; | 2095 | r.retryAfter = GetTime() + 60; |
| 2015 | if (r.deferrals == 1 || r.deferrals % 60 == 0) | 2096 | if (r.deferrals == 1 || r.deferrals % 60 == 0) |
| 2016 | LogPrint("payout", "[payout] deferring %s to %s -- send" | 2097 | LogPrint("payout", "[payout] deferring %s to %s -- send" |
| 2017 | " failed with %s (%s spendable, %s needed with" | 2098 | " failed with %s (%s spendable; the wallet" |
| 2018 | " fee headroom); waiting %d min so far\n", | 2099 | " needed a fee of %s for a transaction this" |
| 2100 | " size); waiting %d min so far\n", | ||
| 2019 | FormatMoney(r.amount).c_str(), r.address.c_str(), | 2101 | FormatMoney(r.amount).c_str(), r.address.c_str(), |
| 2020 | fLocked ? "the wallet locked" : "too little coin", | 2102 | fLocked ? "the wallet locked" : "too little coin", |
| 2021 | FormatMoney(nAfter).c_str(), | 2103 | FormatMoney(nAfter).c_str(), |
| 2022 | FormatMoney(r.amount + nTransactionFee + CENT).c_str(), | 2104 | FormatMoney(outcome.nFeeRequired).c_str(), |
| 2023 | r.deferrals); | 2105 | r.deferrals); |
| 2024 | ReschedulePayment(r); | 2106 | ReschedulePayment(r); |
| 2025 | } else if (r.retries < 1440) { | 2107 | } else { |
| 2026 | // A genuine fault: funded, unlocked, block on the main | 2108 | // Funded, unlocked, block on the main chain, and the |
| 2027 | // chain, and the wallet still would not build or accept | 2109 | // wallet still would not build, sign or accept the |
| 2028 | // the transaction. Retry with backoff. | 2110 | // transaction. That is worth shouting about; it is not |
| 2111 | // grounds to keep the money. | ||
| 2112 | // | ||
| 2113 | // There is deliberately no give-up branch. It used to | ||
| 2114 | // forfeit the debt after 1440 of these, which is not a | ||
| 2115 | // decidable state -- it is a count of how long we have | ||
| 2116 | // been asking, and it was reached by misclassifying a | ||
| 2117 | // funding shortfall as a fault. A debt that is never | ||
| 2118 | // written off cannot be written off wrongly. The payment | ||
| 2119 | // stays in flight, keeps its ledger entry, keeps being | ||
| 2120 | // reported in pool_status.json as owed, and keeps | ||
| 2121 | // retrying once a minute until the operator or the wallet | ||
| 2122 | // makes it possible. | ||
| 2029 | r.retries++; | 2123 | r.retries++; |
| 2030 | r.retryAfter = GetTime() + 60; | 2124 | r.retryAfter = GetTime() + 60; |
| 2031 | ReschedulePayment(r); | 2125 | ReschedulePayment(r); |
| 2032 | LogPrint("payout", "[payout] FAILED to pay %s to %s" | 2126 | // Every minute at first, then hourly: a fault that is not |
| 2033 | " (%s spendable, wallet unlocked) -- retry %d/1440" | 2127 | // going away must not turn the log into one line a minute |
| 2034 | " in 60s\n", | 2128 | // forever, and must not go quiet either. |
| 2035 | FormatMoney(r.amount).c_str(), r.address.c_str(), | 2129 | if (r.retries <= 5 || r.retries % 60 == 0) |
| 2036 | FormatMoney(nAfter).c_str(), r.retries); | 2130 | LogPrint("payout", "[payout] FAILED to pay %s to %s" |
| 2037 | } else { | 2131 | " (%s spendable, wallet unlocked, block %d on" |
| 2038 | // 1440 failures that were each individually impossible to | 2132 | " the main chain) -- attempt %d, retrying in" |
| 2039 | // explain by maturity, funding, the wallet being locked, or | 2133 | " 60s; this debt stands until it is paid\n", |
| 2040 | // a reorg. That is the only state in which this payment can | 2134 | FormatMoney(r.amount).c_str(), r.address.c_str(), |
| 2041 | // be called impossible rather than merely blocked. | 2135 | FormatMoney(nAfter).c_str(), r.blockHeight, |
| 2042 | const std::string reason = strprintf( | 2136 | r.retries); |
| 2043 | "wallet refused this payment %d times while funded" | ||
| 2044 | " (%s spendable), unlocked, and block %d on the main" | ||
| 2045 | " chain", r.retries, FormatMoney(nAfter).c_str(), | ||
| 2046 | r.blockHeight); | ||
| 2047 | LogPrint("payout", "[payout] GIVING UP on %s to %s: %s" | ||
| 2048 | " -- payment forfeited\n", | ||
| 2049 | FormatMoney(r.amount).c_str(), r.address.c_str(), | ||
| 2050 | reason.c_str()); | ||
| 2051 | // Write the debt off in the ledger as well as dropping the | ||
| 2052 | // in-flight record. Under the survives-until-fully-paid | ||
| 2053 | // model the entry would otherwise never complete, and every | ||
| 2054 | // later flush would explode it again -- a forfeit that | ||
| 2055 | // silently becomes an infinite retry. The round proof is | ||
| 2056 | // told too, so the public record does not keep claiming the | ||
| 2057 | // address is owed. | ||
| 2058 | ForfeitPayoutRecipient(r, reason); | ||
| 2059 | ForgetPayment(r); | ||
| 2060 | } | 2137 | } |
| 2061 | } | 2138 | } |
| 2062 | } | 2139 | } |
| @@ -2850,13 +2927,19 @@ static void PayoutTestRemoveDir(const std::string& dir) | |||
| 2850 | } | 2927 | } |
| 2851 | RemoveDirectoryA(dir.c_str()); | 2928 | RemoveDirectoryA(dir.c_str()); |
| 2852 | #else | 2929 | #else |
| 2930 | // One level of recursion, because CreateTransaction() opens a Berkeley DB | ||
| 2931 | // environment and BDB puts its logs in a "database" subdirectory. Without | ||
| 2932 | // this the scratch directory cannot be removed and the test leaves one | ||
| 2933 | // behind on every run, which is what d5b3503 was for. | ||
| 2853 | DIR* d = opendir(dir.c_str()); | 2934 | DIR* d = opendir(dir.c_str()); |
| 2854 | if (d) { | 2935 | if (d) { |
| 2855 | struct dirent* ent; | 2936 | struct dirent* ent; |
| 2856 | while ((ent = readdir(d)) != NULL) { | 2937 | while ((ent = readdir(d)) != NULL) { |
| 2857 | std::string name = ent->d_name; | 2938 | std::string name = ent->d_name; |
| 2858 | if (name == "." || name == "..") continue; | 2939 | if (name == "." || name == "..") continue; |
| 2859 | unlink((dir + "/" + name).c_str()); | 2940 | const std::string path = dir + "/" + name; |
| 2941 | if (unlink(path.c_str()) != 0) | ||
| 2942 | PayoutTestRemoveDir(path); | ||
| 2860 | } | 2943 | } |
| 2861 | closedir(d); | 2944 | closedir(d); |
| 2862 | } | 2945 | } |
| @@ -2942,6 +3025,48 @@ static std::string PayoutTestAddWalletTx(const std::string& comment, | |||
| 2942 | return hash.GetHex(); | 3025 | return hash.GetHex(); |
| 2943 | } | 3026 | } |
| 2944 | 3027 | ||
| 3028 | // A real key, in the keystore, so a test wallet's outputs are IsMine() and can | ||
| 3029 | // actually be signed -- both are needed for CreateTransaction to build the | ||
| 3030 | // large transaction the fee schedule is being tested against. Inserted | ||
| 3031 | // straight into the key maps rather than through AddKey(), which would also | ||
| 3032 | // write it to a wallet.dat. | ||
| 3033 | static std::string PayoutTestAddSpendableKey() | ||
| 3034 | { | ||
| 3035 | CKey key; | ||
| 3036 | key.MakeNewKey(); | ||
| 3037 | std::vector<unsigned char> vchPubKey = key.GetPubKey(); | ||
| 3038 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 3039 | { | ||
| 3040 | mapKeys[vchPubKey] = key.GetPrivKey(); | ||
| 3041 | mapPubKeys[Hash160(vchPubKey)] = vchPubKey; | ||
| 3042 | } | ||
| 3043 | return Hash160ToAddress(Hash160(vchPubKey)); | ||
| 3044 | } | ||
| 3045 | |||
| 3046 | // Give the wallet `n` spendable outputs of `each`, one transaction apiece -- | ||
| 3047 | // which is the granularity SelectCoins works at, and therefore the number of | ||
| 3048 | // inputs the transaction spending them will carry. | ||
| 3049 | // | ||
| 3050 | // Deliberately not coinbases: a null prevout would make CWalletTx::GetCredit | ||
| 3051 | // return 0 until COINBASE_SPENDABLE_DEPTH, and there is no chain here. | ||
| 3052 | static void PayoutTestFundWallet(int n, int64 each, const std::string& address) | ||
| 3053 | { | ||
| 3054 | const CScript sc = PayoutTestScriptFor(address); | ||
| 3055 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3056 | { | ||
| 3057 | for (int i = 0; i < n; i++) { | ||
| 3058 | CWalletTx wtx; | ||
| 3059 | wtx.vin.resize(1); | ||
| 3060 | wtx.vin[0].prevout.hash = (uint64)(i + 1); | ||
| 3061 | wtx.vin[0].prevout.n = 0; | ||
| 3062 | wtx.vout.resize(1); | ||
| 3063 | wtx.vout[0].nValue = each; | ||
| 3064 | wtx.vout[0].scriptPubKey = sc; | ||
| 3065 | mapWallet[wtx.GetHash()] = wtx; | ||
| 3066 | } | ||
| 3067 | } | ||
| 3068 | } | ||
| 3069 | |||
| 2945 | // Defined below with the event loop; the status-file publication test drives | 3070 | // Defined below with the event loop; the status-file publication test drives |
| 2946 | // it directly. | 3071 | // it directly. |
| 2947 | static void RefreshStats(int blocksFoundThisSession, uint64 roundShareTotal); | 3072 | static void RefreshStats(int blocksFoundThisSession, uint64 roundShareTotal); |
| @@ -2967,11 +3092,32 @@ int RunPayoutLedgerSelfTest() | |||
| 2967 | #else | 3092 | #else |
| 2968 | unsigned long pid = (unsigned long)getpid(); | 3093 | unsigned long pid = (unsigned long)getpid(); |
| 2969 | #endif | 3094 | #endif |
| 2970 | std::string dir = strprintf("payout_selftest_%d_%lu", (int)GetTime(), pid); | 3095 | // Absolute, and the process moves into it. |
| 3096 | // | ||
| 3097 | // Both are needed by the CreateTransaction section: Berkeley DB resolves | ||
| 3098 | // its log directory relative to the environment home, so a relative | ||
| 3099 | // datadir makes dbenv.open() fail with DB_RUNRECOVERY; and BDB opens | ||
| 3100 | // db.log relative to the working directory, which is src/ during `make | ||
| 3101 | // tests`. Moving in puts it inside the scratch directory, where it dies | ||
| 3102 | // with everything else. | ||
| 3103 | std::string cwd = "."; | ||
| 3104 | { | ||
| 3105 | #ifdef _WIN32 | ||
| 3106 | char buf[MAX_PATH]; | ||
| 3107 | if (GetCurrentDirectoryA(MAX_PATH, buf) > 0) cwd = buf; | ||
| 3108 | #else | ||
| 3109 | char buf[4096]; | ||
| 3110 | if (getcwd(buf, sizeof(buf)) != NULL) cwd = buf; | ||
| 3111 | #endif | ||
| 3112 | } | ||
| 3113 | std::string dir = cwd + "/" + | ||
| 3114 | strprintf("payout_selftest_%d_%lu", (int)GetTime(), pid); | ||
| 2971 | #ifdef _WIN32 | 3115 | #ifdef _WIN32 |
| 2972 | CreateDirectoryA(dir.c_str(), NULL); | 3116 | CreateDirectoryA(dir.c_str(), NULL); |
| 3117 | SetCurrentDirectoryA(dir.c_str()); | ||
| 2973 | #else | 3118 | #else |
| 2974 | mkdir(dir.c_str(), 0700); | 3119 | mkdir(dir.c_str(), 0700); |
| 3120 | if (chdir(dir.c_str()) != 0) { /* stay put; the paths are absolute anyway */ } | ||
| 2975 | #endif | 3121 | #endif |
| 2976 | strSetDataDir = dir; | 3122 | strSetDataDir = dir; |
| 2977 | fprintf(stdout, " scratch datadir: %s\n", dir.c_str()); | 3123 | fprintf(stdout, " scratch datadir: %s\n", dir.c_str()); |
| @@ -3494,7 +3640,141 @@ int RunPayoutLedgerSelfTest() | |||
| 3494 | strPoolRoundsFile = savedRoundsFile; | 3640 | strPoolRoundsFile = savedRoundsFile; |
| 3495 | } | 3641 | } |
| 3496 | 3642 | ||
| 3497 | // -- 11. No payout mutex is held across the status-file write ------------ | 3643 | // -- 11. A send that failed for want of coin is not a fault -------------- |
| 3644 | // | ||
| 3645 | // The defect this replaces: the classifier decided "was this a funding | ||
| 3646 | // problem?" by re-reading GetBalance() and allowing amount + | ||
| 3647 | // nTransactionFee + CENT. nTransactionFee is 0 on a headless node, and | ||
| 3648 | // GetMinFee() is a step function -- 0 below 10,000 bytes, 11 * CENT at it | ||
| 3649 | // -- so the allowance was either unnecessary or eleven times too small, | ||
| 3650 | // and the ledger's own retry budget then wrote the debt off after 1440 | ||
| 3651 | // repeats of the same stable condition. | ||
| 3652 | // | ||
| 3653 | // This is tested against a real wallet and the real CreateTransaction, not | ||
| 3654 | // against injected values: the honest question is not "does my enum | ||
| 3655 | // switch work" but "does a wallet holding many small coins actually report | ||
| 3656 | // the state I think it does". 60 outputs of 1 BTF give ~180 signed P2PKH | ||
| 3657 | // bytes each, which is where the fee schedule steps. Nothing is committed | ||
| 3658 | // or broadcast -- CreateTransaction only builds. | ||
| 3659 | { | ||
| 3660 | PayoutTestResetState(); | ||
| 3661 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3662 | mapWallet.clear(); | ||
| 3663 | |||
| 3664 | // CreateTransaction opens CTxDB("r"), which throws if blkindex.dat is | ||
| 3665 | // not there. Create it in the scratch datadir first. | ||
| 3666 | bool dbOk = true; | ||
| 3667 | try { | ||
| 3668 | CTxDB txdb("cr"); | ||
| 3669 | } catch (const std::exception&) { | ||
| 3670 | dbOk = false; | ||
| 3671 | } | ||
| 3672 | check(dbOk, "a scratch block index exists for CreateTransaction to open"); | ||
| 3673 | |||
| 3674 | const std::string walletAddr = PayoutTestAddSpendableKey(); | ||
| 3675 | const std::string payee = PayoutTestAddress(0x77); | ||
| 3676 | const CScript payeeScript = PayoutTestScriptFor(payee); | ||
| 3677 | |||
| 3678 | // -- the >= 10 KB case: 60 small coins, balance = amount + 0.05 ------ | ||
| 3679 | PayoutTestFundWallet(60, 1 * COIN, walletAddr); | ||
| 3680 | const int64 balance = GetBalance(); | ||
| 3681 | check(balance == 60 * COIN, | ||
| 3682 | "the test wallet holds 60 separately spendable coins"); | ||
| 3683 | |||
| 3684 | const int64 amount = balance - 5 * CENT; | ||
| 3685 | // The pre-send gate in PayoutThreadFn passes: the balance covers the | ||
| 3686 | // amount and the only fee that can be known in advance. | ||
| 3687 | check(balance >= amount + nTransactionFee, | ||
| 3688 | "the pre-send gate would let this payment through"); | ||
| 3689 | |||
| 3690 | CWalletTx wtx; | ||
| 3691 | SendMoneyOutcome outcome; | ||
| 3692 | int64 feeRequired = 0; | ||
| 3693 | WalletSendResult result = WALLET_SEND_OK; | ||
| 3694 | bool built = true; | ||
| 3695 | if (dbOk) { | ||
| 3696 | try { | ||
| 3697 | built = CreateTransaction(payeeScript, amount, wtx, feeRequired, | ||
| 3698 | &result); | ||
| 3699 | } catch (const std::exception&) { | ||
| 3700 | built = true; // report as a failure below | ||
| 3701 | } | ||
| 3702 | } | ||
| 3703 | check(dbOk && !built, | ||
| 3704 | "a wallet of many small coins cannot fund amount + the fee its" | ||
| 3705 | " own size demands"); | ||
| 3706 | check(dbOk && result == WALLET_SEND_INSUFFICIENT_FUNDS, | ||
| 3707 | "the wallet reports that as a funding shortfall, not as a" | ||
| 3708 | " failure to build"); | ||
| 3709 | // GetMinFee(fDiscount) returns 0 below 10,000 bytes and | ||
| 3710 | // (1 + bytes/1000) * CENT at or above, so a required fee of 11 CENT or | ||
| 3711 | // more IS the statement that the transaction crossed 10 KB. Measured | ||
| 3712 | // through the real serialiser rather than asserted. | ||
| 3713 | check(dbOk && feeRequired >= 11 * CENT, | ||
| 3714 | "the transaction it built crossed 10,000 bytes, so the fee" | ||
| 3715 | " stepped from 0 to at least 11 CENT"); | ||
| 3716 | |||
| 3717 | // The defect, stated as arithmetic. The old classifier asked whether | ||
| 3718 | // the balance covered amount + nTransactionFee + CENT; it did, so it | ||
| 3719 | // called a permanent funding shortfall a fault and started counting | ||
| 3720 | // towards a write-off. | ||
| 3721 | check(!(balance < amount + nTransactionFee + CENT), | ||
| 3722 | "the reserve this replaces would have judged this send a fault"); | ||
| 3723 | check(balance < amount + feeRequired, | ||
| 3724 | "while the fee actually required was more than the balance had"); | ||
| 3725 | |||
| 3726 | outcome.result = result; | ||
| 3727 | outcome.nFeeRequired = feeRequired; | ||
| 3728 | check(dbOk && ClassifyPayoutSendFailure(outcome.result, false) | ||
| 3729 | == PAYOUT_FAILURE_DEFER, | ||
| 3730 | "a >= 10 KB send that ran out of coin is deferred, not counted" | ||
| 3731 | " against the payment"); | ||
| 3732 | |||
| 3733 | // -- the < 10 KB case: no reserve is needed at all ------------------- | ||
| 3734 | // | ||
| 3735 | // The other half of the arithmetic. Below the step the required fee is | ||
| 3736 | // 0, so a reserve of any size is a reason to refuse a payment the | ||
| 3737 | // wallet could have made -- which is its own way of not paying a | ||
| 3738 | // miner. | ||
| 3739 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3740 | mapWallet.clear(); | ||
| 3741 | PayoutTestFundWallet(3, 1 * COIN, walletAddr); | ||
| 3742 | const int64 smallAmount = 3 * COIN - 5 * CENT; | ||
| 3743 | CWalletTx smallTx; | ||
| 3744 | int64 smallFee = -1; | ||
| 3745 | WalletSendResult smallResult = WALLET_SEND_FAILED; | ||
| 3746 | bool smallBuilt = false; | ||
| 3747 | if (dbOk) { | ||
| 3748 | try { | ||
| 3749 | smallBuilt = CreateTransaction(payeeScript, smallAmount, smallTx, | ||
| 3750 | smallFee, &smallResult); | ||
| 3751 | } catch (const std::exception&) { | ||
| 3752 | smallBuilt = false; | ||
| 3753 | } | ||
| 3754 | } | ||
| 3755 | check(dbOk && smallBuilt && smallResult == WALLET_SEND_OK, | ||
| 3756 | "the same balance pays a small transaction with no headroom at" | ||
| 3757 | " all"); | ||
| 3758 | check(dbOk && smallFee == 0, | ||
| 3759 | "because below 10,000 bytes the required fee is zero"); | ||
| 3760 | |||
| 3761 | // -- and the classification of every other answer -------------------- | ||
| 3762 | check(ClassifyPayoutSendFailure(WALLET_SEND_INSUFFICIENT_FUNDS, false) | ||
| 3763 | == PAYOUT_FAILURE_DEFER && | ||
| 3764 | ClassifyPayoutSendFailure(WALLET_SEND_INSUFFICIENT_FUNDS, true) | ||
| 3765 | == PAYOUT_FAILURE_DEFER && | ||
| 3766 | ClassifyPayoutSendFailure(WALLET_SEND_FAILED, true) | ||
| 3767 | == PAYOUT_FAILURE_DEFER, | ||
| 3768 | "short of coin or unable to sign is always a deferral"); | ||
| 3769 | check(ClassifyPayoutSendFailure(WALLET_SEND_FAILED, false) | ||
| 3770 | == PAYOUT_FAILURE_FAULT, | ||
| 3771 | "funded and unlocked and still refused is a fault"); | ||
| 3772 | |||
| 3773 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 3774 | mapWallet.clear(); | ||
| 3775 | } | ||
| 3776 | |||
| 3777 | // -- 12. No payout mutex is held across the status-file write ------------ | ||
| 3498 | // | 3778 | // |
| 3499 | // RefreshStats runs on the event loop -- the thread that owns every | 3779 | // RefreshStats runs on the event loop -- the thread that owns every |
| 3500 | // miner's socket -- and used to hold gStatsMutex and gPayoutLedgerMutex at | 3780 | // miner's socket -- and used to hold gStatsMutex and gPayoutLedgerMutex at |
| @@ -3628,6 +3908,19 @@ int RunPayoutLedgerSelfTest() | |||
| 3628 | PayoutTestResetState(); | 3908 | PayoutTestResetState(); |
| 3629 | CRITICAL_BLOCK(cs_mapWallet) | 3909 | CRITICAL_BLOCK(cs_mapWallet) |
| 3630 | mapWallet.clear(); | 3910 | mapWallet.clear(); |
| 3911 | // Out of the scratch directory before removing it, and out of the key maps | ||
| 3912 | // too: the keys this test generated are process-local, but leaving them in | ||
| 3913 | // the keystore would let a later test think a script was its own. | ||
| 3914 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 3915 | { | ||
| 3916 | mapKeys.clear(); | ||
| 3917 | mapPubKeys.clear(); | ||
| 3918 | } | ||
| 3919 | #ifdef _WIN32 | ||
| 3920 | SetCurrentDirectoryA(cwd.c_str()); | ||
| 3921 | #else | ||
| 3922 | if (chdir(cwd.c_str()) != 0) { /* nothing useful to do about it */ } | ||
| 3923 | #endif | ||
| 3631 | PayoutTestRemoveDir(dir); | 3924 | PayoutTestRemoveDir(dir); |
| 3632 | strSetDataDir = savedDataDir; | 3925 | strSetDataDir = savedDataDir; |
| 3633 | nBestHeight = savedBestHeight; | 3926 | nBestHeight = savedBestHeight; |