mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge pull request #1317 from delta1/ct-fees-discount
feat: discounted fees for confidential transactions
This commit is contained in:
commit
a4d7ac7bbe
17 changed files with 653 additions and 24 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
50
src/policy/discount.h
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
||||
|
|
|
|||
158
test/functional/feature_discount_ct.py
Executable file
158
test/functional/feature_discount_ct.py
Executable 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()
|
||||
292
test/functional/feature_discount_ct_ordering.py
Executable file
292
test/functional/feature_discount_ct_ordering.py
Executable 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()
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue