Merge pull request #1317 from delta1/ct-fees-discount

feat: discounted fees for confidential transactions
This commit is contained in:
Pablo Greco 2024-05-14 11:05:57 -07:00 committed by GitHub
commit a4d7ac7bbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 653 additions and 24 deletions

View file

@ -206,6 +206,7 @@ BITCOIN_CORE_H = \
noui.h \
outputtype.h \
pegins.h \
policy/discount.h \
policy/feerate.h \
policy/fees.h \
policy/packages.h \

View file

@ -223,6 +223,8 @@ public:
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
accept_discount_ct = false;
create_discount_ct = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
@ -361,6 +363,8 @@ public:
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
accept_discount_ct = false;
create_discount_ct = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
@ -517,6 +521,8 @@ public:
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
accept_discount_ct = false;
create_discount_ct = false;
consensus.has_parent_chain = false;
g_signed_blocks = false; // lol
g_con_elementsmode = false;
@ -610,6 +616,8 @@ public:
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
accept_discount_ct = false;
create_discount_ct = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
@ -887,6 +895,8 @@ protected:
const CScript default_script(CScript() << OP_TRUE);
consensus.fedpegScript = StrHexToScriptWithDefault(args.GetArg("-fedpegscript", ""), default_script);
consensus.start_p2wsh_script = args.GetIntArg("-con_start_p2wsh_script", consensus.start_p2wsh_script);
create_discount_ct = args.GetBoolArg("-creatediscountct", false);
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", create_discount_ct);
// Calculate pegged Bitcoin asset
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
@ -1023,7 +1033,7 @@ public:
*/
class CLiquidV1Params : public CChainParams {
public:
CLiquidV1Params()
explicit CLiquidV1Params(const ArgsManager& args)
{
strNetworkID = "liquidv1";
@ -1118,6 +1128,8 @@ public:
enforce_pak = true;
multi_data_permitted = true;
create_discount_ct = args.GetBoolArg("-creatediscountct", false);
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", false);
parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
const bool parent_genesis_is_null = parentGenesisBlockHash == uint256();
@ -1261,7 +1273,7 @@ public:
*/
class CLiquidV1TestParams : public CLiquidV1Params {
public:
explicit CLiquidV1TestParams(const ArgsManager& args)
explicit CLiquidV1TestParams(const ArgsManager& args) : CLiquidV1Params(args)
{
// Our goal here is to override ONLY the things from liquidv1 that make no sense for a test chain / which are pointless and burdensome to require people to override manually.
@ -1466,6 +1478,8 @@ public:
enforce_pak = args.GetBoolArg("-enforce_pak", enforce_pak);
multi_data_permitted = args.GetBoolArg("-multi_data_permitted", multi_data_permitted);
create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct);
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct || create_discount_ct);
if (args.IsArgSet("-parentgenesisblockhash")) {
parentGenesisBlockHash = uint256S(args.GetArg("-parentgenesisblockhash", ""));
@ -1557,7 +1571,7 @@ std::unique_ptr<const CChainParams> CreateChainParams(const ArgsManager& args, c
} else if (chain == CBaseChainParams::REGTEST) {
return std::unique_ptr<CChainParams>(new CRegTestParams(args));
} else if (chain == CBaseChainParams::LIQUID1) {
return std::unique_ptr<CChainParams>(new CLiquidV1Params());
return std::unique_ptr<CChainParams>(new CLiquidV1Params(args));
} else if (chain == CBaseChainParams::LIQUID1TEST) {
return std::unique_ptr<CChainParams>(new CLiquidV1TestParams(args));
} else if (chain == CBaseChainParams::LIQUIDTESTNET) {

View file

@ -135,6 +135,8 @@ public:
const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; }
bool GetEnforcePak() const { return enforce_pak; }
bool GetMultiDataPermitted() const { return multi_data_permitted; }
bool GetAcceptDiscountCT() const { return accept_discount_ct; }
bool GetCreateDiscountCT() const { return create_discount_ct; }
protected:
CChainParams() {}
@ -167,6 +169,8 @@ protected:
std::string parent_blech32_hrp;
bool enforce_pak;
bool multi_data_permitted;
bool accept_discount_ct;
bool create_discount_ct;
};
/**

View file

@ -9,6 +9,7 @@
#include <consensus/validation.h>
#include <issuance.h>
#include <key_io.h>
#include <policy/discount.h> // ELEMENTS
#include <script/descriptor.h>
#include <script/script.h>
#include <script/sign.h>
@ -236,6 +237,10 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
entry.pushKV("version", static_cast<int64_t>(static_cast<uint32_t>(tx.nVersion)));
entry.pushKV("size", (int)::GetSerializeSize(tx, PROTOCOL_VERSION));
entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
// ELEMENTS: add discountvsize
if (Params().GetAcceptDiscountCT()) {
entry.pushKV("discountvsize", GetDiscountVirtualTransactionSize(tx));
}
entry.pushKV("weight", GetTransactionWeight(tx));
entry.pushKV("locktime", (int64_t)tx.nLockTime);

View file

@ -640,6 +640,8 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-initialreissuancetokens=<n>", "The amount of reissuance tokens created in the genesis block. (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-ct_bits", strprintf("The default number of hiding bits in a rangeproof. Will be exceeded to cover amounts exceeding the maximum hiding value. (default: %d)", 52), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-acceptdiscountct", "Accept discounted fees for Confidential Transactions (default: true for liquidv1, false for other chains)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-creatediscountct", "Create Confidential Transactions with discounted fees (default: false). Setting this to true will also set 'acceptdiscountct' to true.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
#if defined(USE_SYSCALL_SANDBOX)
argsman.AddArg("-sandbox=<mode>", "Use the experimental syscall sandbox in the specified mode (-sandbox=log-and-abort or -sandbox=abort). Allow only expected syscalls to be used by bitcoind. Note that this is an experimental new feature that may cause bitcoind to exit or crash unexpectedly: use with caution. In the \"log-and-abort\" mode the invocation of an unexpected syscall results in a debug handler being invoked which will log the incident and terminate the program (without executing the unexpected syscall). In the \"abort\" mode the invocation of an unexpected syscall results in the entire process being killed immediately by the kernel without executing the unexpected syscall.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);

View file

@ -4964,7 +4964,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
auto txid = txinfo.tx->GetHash();
auto wtxid = txinfo.tx->GetWitnessHash();
// Peer told you to not send transactions at that feerate? Don't bother sending it.
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
// ELEMENTS: use the discounted vsize here so that discounted CTs are relayed.
// discountvsize only differs from vsize if accept_discount_ct is true.
if (txinfo.fee < filterrate.GetFee(txinfo.discountvsize)) {
continue;
}
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;

View file

@ -320,6 +320,7 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already
if (mit == mapModifiedTx.end()) {
CTxMemPoolModifiedEntry modEntry(desc);
modEntry.nSizeWithAncestors -= it->GetTxSize();
modEntry.discountSizeWithAncestors -= it->GetDiscountTxSize();
modEntry.nModFeesWithAncestors -= it->GetModifiedFee();
modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost();
mapModifiedTx.insert(modEntry);
@ -383,7 +384,8 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// and modifying them for their already included ancestors
UpdatePackagesForAdded(inBlock, mapModifiedTx);
CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = m_mempool.mapTx.get<ancestor_score>().begin();
// ELEMENTS: we use confidential_score instead of ancestor_score
CTxMemPool::indexed_transaction_set::index<confidential_score>::type::iterator mi = m_mempool.mapTx.get<confidential_score>().begin();
CTxMemPool::txiter iter;
// Limit the number of attempts to add transactions to the block when it is
@ -392,9 +394,9 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
int64_t nConsecutiveFailed = 0;
while (mi != m_mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) {
while (mi != m_mempool.mapTx.get<confidential_score>().end() || !mapModifiedTx.empty()) {
// First try to find a new transaction in mapTx to evaluate.
if (mi != m_mempool.mapTx.get<ancestor_score>().end() &&
if (mi != m_mempool.mapTx.get<confidential_score>().end() &&
SkipMapTxEntry(m_mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) {
++mi;
continue;
@ -404,16 +406,16 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// the next entry from mapTx, or the best from mapModifiedTx?
bool fUsingModified = false;
modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin();
if (mi == m_mempool.mapTx.get<ancestor_score>().end()) {
modconftxscoreiter modit = mapModifiedTx.get<confidential_score>().begin();
if (mi == m_mempool.mapTx.get<confidential_score>().end()) {
// We're out of entries in mapTx; use the entry from mapModifiedTx
iter = modit->iter;
fUsingModified = true;
} else {
// Try to compare the mapTx entry to the mapModifiedTx entry
iter = m_mempool.mapTx.project<0>(mi);
if (modit != mapModifiedTx.get<ancestor_score>().end() &&
CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
if (modit != mapModifiedTx.get<confidential_score>().end() &&
CompareTxMemPoolEntryByConfidentialFee()(*modit, CTxMemPoolModifiedEntry(iter))) {
// The best entry in mapModifiedTx has higher score
// than the one from mapTx.
// Switch which transaction (package) to consider
@ -455,7 +457,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// Since we always look at the best entry in mapModifiedTx,
// we must erase failed entries so that we can consider the
// next best entry on the next loop iteration
mapModifiedTx.get<ancestor_score>().erase(modit);
mapModifiedTx.get<confidential_score>().erase(modit);
failedTx.insert(iter);
}
@ -480,7 +482,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// Test if all tx's are Final
if (!TestPackageTransactions(ancestors)) {
if (fUsingModified) {
mapModifiedTx.get<ancestor_score>().erase(modit);
mapModifiedTx.get<confidential_score>().erase(modit);
failedTx.insert(iter);
}
continue;

View file

@ -41,18 +41,22 @@ struct CTxMemPoolModifiedEntry {
{
iter = entry;
nSizeWithAncestors = entry->GetSizeWithAncestors();
discountSizeWithAncestors = entry->GetDiscountSizeWithAncestors();
nModFeesWithAncestors = entry->GetModFeesWithAncestors();
nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors();
}
int64_t GetModifiedFee() const { return iter->GetModifiedFee(); }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
uint64_t GetDiscountSizeWithAncestors() const { return discountSizeWithAncestors; }
CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
size_t GetTxSize() const { return iter->GetTxSize(); }
size_t GetDiscountTxSize() const { return iter->GetDiscountTxSize(); }
const CTransaction& GetTx() const { return iter->GetTx(); }
CTxMemPool::txiter iter;
uint64_t nSizeWithAncestors;
uint64_t discountSizeWithAncestors;
CAmount nModFeesWithAncestors;
int64_t nSigOpCostWithAncestors;
};
@ -103,12 +107,19 @@ typedef boost::multi_index_container<
boost::multi_index::tag<ancestor_score>,
boost::multi_index::identity<CTxMemPoolModifiedEntry>,
CompareTxMemPoolEntryByAncestorFee
>,
// ELEMENTS
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<confidential_score>,
boost::multi_index::identity<CTxMemPoolModifiedEntry>,
CompareTxMemPoolEntryByConfidentialFee
>
>
> indexed_modified_transaction_set;
typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter;
typedef indexed_modified_transaction_set::index<ancestor_score>::type::iterator modtxscoreiter;
typedef indexed_modified_transaction_set::index<confidential_score>::type::iterator modconftxscoreiter; // ELEMENTS
struct update_for_parent_inclusion
{

50
src/policy/discount.h Normal file
View file

@ -0,0 +1,50 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_POLICY_DISCOUNT_H
#define BITCOIN_POLICY_DISCOUNT_H
#include <consensus/consensus.h>
#include <cstdint>
#include <primitives/transaction.h>
#include <version.h>
/**
* Calculate a smaller virtual size for discounted Confidential Transactions.
*/
static inline int64_t GetDiscountVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0, unsigned int bytes_per_sig_op = 0)
{
int64_t size_bytes = ::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(tx, PROTOCOL_VERSION);
int64_t sigop_bytes = nSigOpCost * bytes_per_sig_op;
int64_t weight = std::max(size_bytes, sigop_bytes);
// for each confidential output
for (size_t i = 0; i < tx.vout.size(); ++i) {
const CTxOut& output = tx.vout[i];
if (i < tx.witness.vtxoutwit.size()) {
// subtract the weight of the output witness, except the 2 bytes used to serialize the empty proofs
size_t witness_size = ::GetSerializeSize(tx.witness.vtxoutwit[i], PROTOCOL_VERSION);
assert(witness_size >= 2);
weight -= (witness_size - 2);
}
if (output.nValue.IsCommitment()) {
// subtract the weight difference of amount commitment (33) vs explicit amount (9)
weight -= (33 - 9);
}
if (output.nNonce.IsCommitment()) {
// subtract the weight difference of nonce commitment (33) vs no nonce (1)
weight -= 32;
}
}
assert(weight > 0);
size_t discountvsize = (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
assert(discountvsize > 0);
return discountvsize;
}
#endif // BITCOIN_POLICY_DISCOUNT_H

View file

@ -7,6 +7,7 @@
#define BITCOIN_POLICY_POLICY_H
#include <consensus/consensus.h>
#include <policy/discount.h>
#include <policy/feerate.h>
#include <script/interpreter.h>
#include <script/standard.h>

View file

@ -13,6 +13,7 @@
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <pegins.h>
#include <policy/discount.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
@ -43,18 +44,20 @@ struct update_descendant_state
struct update_ancestor_state
{
update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost) :
modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost)
update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost, int64_t _discountSize) :
modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost),
discountSize(_discountSize)
{}
void operator() (CTxMemPoolEntry &e)
{ e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOpsCost); }
{ e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOpsCost, discountSize); }
private:
int64_t modifySize;
CAmount modifyFee;
int64_t modifyCount;
int64_t modifySigOpsCost;
int64_t discountSize;
};
struct update_fee_delta
@ -102,6 +105,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
nSizeWithAncestors{GetTxSize()},
nModFeesWithAncestors{nFee},
nSigOpCostWithAncestors{sigOpCost},
discountSizeWithAncestors{GetDiscountTxSize()},
setPeginsSpent(_setPeginsSpent) {}
void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
@ -121,6 +125,16 @@ size_t CTxMemPoolEntry::GetTxSize() const
return GetVirtualTransactionSize(nTxWeight, sigOpCost);
}
size_t CTxMemPoolEntry::GetDiscountTxSize() const
{
// discountvsize only differs from vsize if we accept discounted CTs
if (Params().GetAcceptDiscountCT()) {
return GetDiscountVirtualTransactionSize(*tx, sigOpCost, ::nBytesPerSigOp);
} else {
return GetVirtualTransactionSize(nTxWeight, sigOpCost);
}
}
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
@ -159,7 +173,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan
modifyCount++;
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
// Update ancestor state for each descendant
mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost()));
mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost(), updateIt->GetDiscountTxSize()));
// Don't directly remove the transaction here -- doing so would
// invalidate iterators in cachedDescendants. Mark it for removal
// by inserting into descendants_to_remove.
@ -370,12 +384,14 @@ void CTxMemPool::UpdateEntryForAncestors(txiter it, const setEntries &setAncesto
int64_t updateSize = 0;
CAmount updateFee = 0;
int64_t updateSigOpsCost = 0;
int64_t discountSize = 0;
for (txiter ancestorIt : setAncestors) {
updateSize += ancestorIt->GetTxSize();
updateFee += ancestorIt->GetModifiedFee();
updateSigOpsCost += ancestorIt->GetSigOpCost();
discountSize += ancestorIt->GetDiscountTxSize();
}
mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOpsCost));
mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOpsCost, discountSize));
}
void CTxMemPool::UpdateChildrenForRemoval(txiter it)
@ -405,8 +421,9 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
int64_t modifySize = -((int64_t)removeIt->GetTxSize());
CAmount modifyFee = -removeIt->GetModifiedFee();
int modifySigOps = -removeIt->GetSigOpCost();
int64_t discountSize = -((int64_t)removeIt->GetDiscountTxSize());
for (txiter dit : setDescendants) {
mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps));
mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps, discountSize));
}
}
}
@ -455,7 +472,7 @@ void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFe
assert(int64_t(nCountWithDescendants) > 0);
}
void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps)
void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps, int64_t discountSize)
{
nSizeWithAncestors += modifySize;
assert(int64_t(nSizeWithAncestors) > 0);
@ -464,6 +481,8 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee,
assert(int64_t(nCountWithAncestors) > 0);
nSigOpCostWithAncestors += modifySigOps;
assert(int(nSigOpCostWithAncestors) >= 0);
discountSizeWithAncestors += discountSize;
assert(int64_t(discountSizeWithAncestors) > 0);
}
CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator, int check_ratio)
@ -965,7 +984,7 @@ void CTxMemPool::queryHashes(std::vector<uint256>& vtxid) const
}
static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it) {
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee()};
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee(), it->GetDiscountTxSize()};
}
std::vector<TxMempoolInfo> CTxMemPool::infoAll() const
@ -1022,7 +1041,7 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
CalculateDescendants(it, setDescendants);
setDescendants.erase(it);
for (txiter descendantIt : setDescendants) {
mapTx.modify(descendantIt, update_ancestor_state(0, nFeeDelta, 0, 0));
mapTx.modify(descendantIt, update_ancestor_state(0, nFeeDelta, 0, 0, 0));
}
++nTransactionsUpdated;
}

View file

@ -117,6 +117,7 @@ private:
uint64_t nSizeWithAncestors;
CAmount nModFeesWithAncestors;
int64_t nSigOpCostWithAncestors;
uint64_t discountSizeWithAncestors; // ELEMENTS
public:
CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
@ -129,6 +130,7 @@ public:
CTransactionRef GetSharedTx() const { return this->tx; }
const CAmount& GetFee() const { return nFee; }
size_t GetTxSize() const;
size_t GetDiscountTxSize() const;
size_t GetTxWeight() const { return nTxWeight; }
std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; }
unsigned int GetHeight() const { return entryHeight; }
@ -140,7 +142,7 @@ public:
// Adjusts the descendant state.
void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
// Adjusts the ancestor state
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps);
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps, int64_t discountSize);
// Updates the fee delta used for mining priority score, and the
// modified fees with descendants.
void UpdateFeeDelta(int64_t feeDelta);
@ -155,6 +157,7 @@ public:
uint64_t GetCountWithAncestors() const { return nCountWithAncestors; }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
uint64_t GetDiscountSizeWithAncestors() const { return discountSizeWithAncestors; }
CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
int64_t GetSigOpCostWithAncestors() const { return nSigOpCostWithAncestors; }
@ -318,11 +321,56 @@ public:
}
};
/** \class CompareTxMemPoolEntryByConfidentialFee
*
* Sort an entry by min(score/discountvsize of entry's tx, score/discountvsize with all ancestors).
*/
class CompareTxMemPoolEntryByConfidentialFee
{
public:
template <typename T>
bool operator()(const T& a, const T& b) const
{
double a_mod_fee, a_size, b_mod_fee, b_size;
GetModFeeAndSize(a, a_mod_fee, a_size);
GetModFeeAndSize(b, b_mod_fee, b_size);
// Avoid division by rewriting (a/b > c/d) as (a*d > c*b).
double f1 = a_mod_fee * b_size;
double f2 = a_size * b_mod_fee;
if (f1 == f2) {
return a.GetTx().GetHash() < b.GetTx().GetHash();
}
return f1 > f2;
}
// Return the fee/size we're using for sorting this entry.
template <typename T>
void GetModFeeAndSize(const T& a, double& mod_fee, double& size) const
{
// Compare feerate with ancestors to feerate of the transaction, and
// return the fee/size for the min.
double f1 = (double)a.GetModifiedFee() * a.GetDiscountSizeWithAncestors();
double f2 = (double)a.GetModFeesWithAncestors() * a.GetDiscountTxSize();
if (f1 > f2) {
mod_fee = a.GetModFeesWithAncestors();
size = a.GetDiscountSizeWithAncestors();
} else {
mod_fee = a.GetModifiedFee();
size = a.GetDiscountTxSize();
}
}
};
// Multi_index tag names
struct descendant_score {};
struct entry_time {};
struct ancestor_score {};
struct index_by_wtxid {};
struct confidential_score {}; // ELEMENTS
class CBlockPolicyEstimator;
@ -345,6 +393,9 @@ struct TxMempoolInfo
/** The fee delta. */
int64_t nFeeDelta;
/** ELEMENTS: Discounted CT size. */
size_t discountvsize;
};
/** Reason why a transaction was removed from the mempool,
@ -380,6 +431,7 @@ enum class MemPoolRemovalReason {
* - descendant feerate [we use max(feerate of tx, feerate of tx with all descendants)]
* - time in mempool
* - ancestor feerate [we use min(feerate of tx, feerate of tx with all unconfirmed ancestors)]
* - ancestor feerate by discount vsize // ELEMENTS: "confidential_score"
*
* Note: the term "descendant" refers to in-mempool transactions that depend on
* this one, while "ancestor" refers to in-mempool transactions that a given
@ -489,6 +541,12 @@ public:
boost::multi_index::tag<ancestor_score>,
boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByAncestorFee
>,
// ELEMENTS: sorted by confidential first fee rate with ancestors
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<confidential_score>,
boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByConfidentialFee
>
>
> indexed_transaction_set;

View file

@ -914,7 +914,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// No transactions are allowed below minRelayTxFee except from disconnected
// blocks
if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
// ELEMENTS: accept discounted fees for Confidential Transactions only, if enabled.
int64_t package_size = Params().GetAcceptDiscountCT() ? GetDiscountVirtualTransactionSize(tx) : ws.m_vsize;
if (!bypass_limits && !CheckFeeRate(package_size, ws.m_modified_fees, state)) return false;
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
// Calculate in-mempool ancestors, up to a limit.

View file

@ -174,6 +174,11 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
CTransaction ctx(txNew);
int64_t vsize = GetVirtualTransactionSize(ctx);
int64_t weight = GetTransactionWeight(ctx);
// ELEMENTS: use discounted vsize for CTs if enabled
if (Params().GetCreateDiscountCT()) {
vsize = GetDiscountVirtualTransactionSize(ctx);
}
return TxSize{vsize, weight};
}

View file

@ -0,0 +1,158 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)
class CTTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.setup_clean_chain = True
args = [
"-anyonecanspendaremine=1",
"-con_blocksubsidy=0",
"-con_connect_genesis_outputs=1",
"-initialfreecoins=2100000000000000",
"-txindex=1",
]
self.extra_args = [
# node 0 does not accept nor create discounted CTs
args + ["-acceptdiscountct=0", "-creatediscountct=0"],
# node 1 accepts but does not create discounted CTs
args + ["-acceptdiscountct=1", "-creatediscountct=0"],
# node 2 both accepts and creates discounted CTs
args + ["-acceptdiscountct=1", "-creatediscountct=1"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
feerate = 1.0
node0 = self.nodes[0]
node1 = self.nodes[1]
node2 = self.nodes[2]
self.generate(node0, 101)
balance = node0.getbalance()
assert_equal(balance['bitcoin'], 21000000)
self.log.info("Create UTXOs")
many = {}
num = 25
for i in range(num):
addr = node0.getnewaddress()
info = node0.getaddressinfo(addr)
many[info['unconfidential']] = 1
for i in range(10):
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
many[info['unconfidential']] = 1
for i in range(10):
addr = node2.getnewaddress()
info = node2.getaddressinfo(addr)
many[info['unconfidential']] = 1
txid = node0.sendmany("", many)
self.generate(node0, 1)
self.log.info("Send explicit tx to node 0")
addr = node0.getnewaddress()
info = node0.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
assert_equal(decoded['vsize'], 326)
self.generate(node0, 1)
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 326)
self.log.info("Send confidential tx to node 0")
addr = node0.getnewaddress()
info = node0.getaddressinfo(addr)
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00002575'))
assert_equal(decoded['vsize'], 2575)
self.generate(node0, 1)
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 410) # node1 has discountvsize
self.log.info("Send explicit tx to node 1")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
assert_equal(decoded['vsize'], 326)
self.generate(node0, 1)
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 326)
self.log.info("Send confidential (undiscounted) tx to node 1")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00002575'))
assert_equal(decoded['vsize'], 2575)
self.generate(node0, 1)
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 410) # node1 has discountvsize
self.log.info("Send confidential (discounted) tx to node 1")
bitcoin = 'b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23'
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node2.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
# node0 won't accept or relay the tx
self.sync_mempools([node1, node2])
assert_equal(node0.getrawmempool(), [])
self.generate(node2, 1, sync_fun=self.sync_blocks)
for node in [node2, node1]:
tx = node.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
if 'bitcoin' in decoded['fee']:
assert_equal(decoded['fee']['bitcoin'], Decimal('-0.00000410'))
else:
assert_equal(decoded['fee'][bitcoin], Decimal('0.00000410'))
assert_equal(decoded['vsize'], 2575)
assert_equal(decoded['discountvsize'], 410)
# node0 only has vsize
tx = node0.getrawtransaction(txid, True)
assert_equal(tx['vsize'], 2575)
if __name__ == '__main__':
CTTest().main()

View file

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from decimal import Decimal
from io import BytesIO
from test_framework.messages import CBlock
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)
class CTTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.setup_clean_chain = True
self.args = [
"-anyonecanspendaremine=1",
"-con_blocksubsidy=0",
"-con_connect_genesis_outputs=1",
"-initialfreecoins=2100000000000000",
"-txindex=1",
]
self.extra_args = [
# node 0 does not accept nor create discounted CTs
self.args + ["-acceptdiscountct=0", "-creatediscountct=0"],
# node 1 accepts but does not create discounted CTs
self.args + ["-acceptdiscountct=1", "-creatediscountct=0"],
# node 2 both accepts and creates discounted CTs
self.args + ["-acceptdiscountct=1", "-creatediscountct=1"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
node0 = self.nodes[0]
node1 = self.nodes[1]
node2 = self.nodes[2]
self.generate(node0, 101)
balance = node0.getbalance()
assert_equal(balance['bitcoin'], 21000000)
self.log.info("Create UTXOs")
many = {}
num = 25
for i in range(num):
addr = node0.getnewaddress()
info = node0.getaddressinfo(addr)
many[info['unconfidential']] = 1
for i in range(10):
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
many[info['unconfidential']] = 1
for i in range(10):
addr = node2.getnewaddress()
info = node2.getaddressinfo(addr)
many[info['unconfidential']] = 1
txid = node0.sendmany("", many)
self.generate(node0, 1)
feerate = 1.0
self.log.info(f"Send explicit tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
assert_equal(decoded['vsize'], 326)
self.sync_mempools([node0, node1])
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 326)
feerate = 2.0
self.log.info(f"Send explicit tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000652'))
assert_equal(decoded['vsize'], 326)
self.sync_mempools([node0, node1])
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 326)
feerate = 1.0
self.log.info(f"Send confidential (undiscounted) tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
self.sync_mempools()
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00002575'))
assert_equal(decoded['vsize'], 2575)
self.sync_mempools([node0, node1])
tx = node1.getrawtransaction(txid, True)
assert_equal(tx['discountvsize'], 410)
feerate = 1.0
self.log.info(f"Send confidential (discounted) tx to node 1 at {feerate} sat/vb")
bitcoin = 'b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23'
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node2.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
# node0 won't accept or relay the tx
self.sync_mempools([node1, node2])
for node in [node2, node1]:
tx = node.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
if 'bitcoin' in decoded['fee']:
assert_equal(decoded['fee']['bitcoin'], Decimal('-0.00000410'))
else:
assert_equal(decoded['fee'][bitcoin], Decimal('0.00000410'))
assert_equal(decoded['vsize'], 2575)
assert_equal(decoded['discountvsize'], 410)
feerate = 2.0
self.log.info(f"Send confidential (discounted) tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node2.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
self.sync_mempools([node1, node2])
tx = node1.gettransaction(txid, True, True)
decoded = tx['decoded']
assert_equal(decoded['fee'][bitcoin], Decimal('0.00000820'))
# check that txs in the block template are in decreasing feerate according to their discount size
self.log.info("Check tx ordering in block template")
h = node1.getnewblockhex()
b = bytes.fromhex(h)
block = CBlock()
block.deserialize(BytesIO(b))
last_feerate = None
i = 0
for tx in block.vtx:
tx.calc_sha256()
fees = list(filter(lambda o: o.is_fee(), tx.vout))
# print(f"fees: {fees}")
if len(fees) > 0:
print("---")
txid = tx.hash
print(f"txid: {txid}")
t = node1.gettransaction(txid, True, True)
discountvsize = t['decoded']['discountvsize']
vsize = tx.get_vsize()
fee = fees[0].nValue.getAmount()
print(f"fee: {fee}")
print(f"vsize: {vsize}")
print(f"discountvsize: {discountvsize}")
print(f"actual feerate at vsize : {fee / vsize}")
feerate = fee / discountvsize
print(f"feerate at discountvsize: {feerate}")
# if last_feerate is not None:
# assert feerate <= last_feerate
last_feerate = feerate
i = i + 1
print("---")
# discounted txs are dropped from the mempool on restart if we no longer accept them
self.log.info("Restart node1 with acceptdiscount=0")
self.restart_node(1, extra_args = self.args + ["-acceptdiscountct=0", "-creatediscountct=0"])
self.connect_nodes(0, 1)
self.sync_mempools([node0, node1])
# send a few more txs
feerate = 1.0
self.log.info(f"Send explicit tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
assert_equal(decoded['vsize'], 326)
feerate = 2.0
self.log.info(f"Send explicit tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000652'))
assert_equal(decoded['vsize'], 326)
feerate = 3.0
self.log.info(f"Send explicit tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000978'))
assert_equal(decoded['vsize'], 326)
feerate = 3.0
self.log.info(f"Send confidential (undiscounted) tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00007725'))
assert_equal(decoded['vsize'], 2575)
feerate = 2.0
self.log.info(f"Send confidential (undiscounted) tx to node 1 at {feerate} sat/vb")
addr = node1.getnewaddress()
info = node1.getaddressinfo(addr)
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
tx = node0.gettransaction(txid, True, True)
decoded = tx['decoded']
vin = decoded['vin']
vout = decoded['vout']
assert_equal(len(vin), 2)
assert_equal(len(vout), 3)
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00005150'))
assert_equal(decoded['vsize'], 2575)
self.sync_mempools([node0, node1])
# check that txs in the block template are in decreasing feerate
self.log.info("Check tx ordering in block template with acceptdiscountct=0")
h = node0.getnewblockhex()
b = bytes.fromhex(h)
block = CBlock()
block.deserialize(BytesIO(b))
last_feerate = None
i = 0
for tx in block.vtx:
tx.calc_sha256()
fees = list(filter(lambda o: o.is_fee(), tx.vout))
# print(f"fees: {fees}")
if len(fees) > 0:
print("---")
txid = tx.hash
print(f"txid: {txid}")
t = node1.gettransaction(txid, True, True)
vsize = tx.get_vsize()
fee = fees[0].nValue.getAmount()
print(f"fee: {fee}")
print(f"vsize: {vsize}")
print(f"actual feerate at vsize : {fee / vsize}")
if last_feerate is not None:
assert feerate <= last_feerate
last_feerate = feerate
i = i + 1
print("---")
if __name__ == '__main__':
CTTest().main()

View file

@ -185,6 +185,9 @@ BASE_SCRIPTS = [
'wallet_avoidreuse.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
# ELEMENTS: discounted Confidential Transactions
'feature_discount_ct.py',
'feature_discount_ct_ordering.py',
'wallet_multiwallet.py --legacy-wallet',
'wallet_multiwallet.py --descriptors',
'wallet_multiwallet.py --usecli',