Skip to content

Commit 946d62f

Browse files
committed
feat: discounted fees for confidential transactions
This is a mempool policy only change to enable cheaper fees for Confidential Transactions. At a feerate of 1 sat/vb for a 2 input, 3 output transaction: - explicit tx fee 326 sats - confidential tx fee 437 sats
1 parent 2d298f7 commit 946d62f

39 files changed

+243
-6
lines changed

src/chainparams.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,8 @@ class CCustomParams : public CRegTestParams {
887887
const CScript default_script(CScript() << OP_TRUE);
888888
consensus.fedpegScript = StrHexToScriptWithDefault(args.GetArg("-fedpegscript", ""), default_script);
889889
consensus.start_p2wsh_script = args.GetIntArg("-con_start_p2wsh_script", consensus.start_p2wsh_script);
890+
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", false);
891+
create_discount_ct = args.GetBoolArg("-creatediscountct", false);
890892

891893
// Calculate pegged Bitcoin asset
892894
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
@@ -1023,7 +1025,7 @@ class CLiquidTestNetParams : public CCustomParams {
10231025
*/
10241026
class CLiquidV1Params : public CChainParams {
10251027
public:
1026-
CLiquidV1Params()
1028+
explicit CLiquidV1Params(const ArgsManager& args)
10271029
{
10281030

10291031
strNetworkID = "liquidv1";
@@ -1118,6 +1120,8 @@ class CLiquidV1Params : public CChainParams {
11181120
enforce_pak = true;
11191121

11201122
multi_data_permitted = true;
1123+
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", true);
1124+
create_discount_ct = args.GetBoolArg("-creatediscountct", false);
11211125

11221126
parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
11231127
const bool parent_genesis_is_null = parentGenesisBlockHash == uint256();
@@ -1261,7 +1265,7 @@ class CLiquidV1Params : public CChainParams {
12611265
*/
12621266
class CLiquidV1TestParams : public CLiquidV1Params {
12631267
public:
1264-
explicit CLiquidV1TestParams(const ArgsManager& args)
1268+
explicit CLiquidV1TestParams(const ArgsManager& args) : CLiquidV1Params(args)
12651269
{
12661270
// 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.
12671271

@@ -1466,6 +1470,8 @@ class CLiquidV1TestParams : public CLiquidV1Params {
14661470
enforce_pak = args.GetBoolArg("-enforce_pak", enforce_pak);
14671471

14681472
multi_data_permitted = args.GetBoolArg("-multi_data_permitted", multi_data_permitted);
1473+
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct);
1474+
create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct);
14691475

14701476
if (args.IsArgSet("-parentgenesisblockhash")) {
14711477
parentGenesisBlockHash = uint256S(args.GetArg("-parentgenesisblockhash", ""));
@@ -1557,7 +1563,7 @@ std::unique_ptr<const CChainParams> CreateChainParams(const ArgsManager& args, c
15571563
} else if (chain == CBaseChainParams::REGTEST) {
15581564
return std::unique_ptr<CChainParams>(new CRegTestParams(args));
15591565
} else if (chain == CBaseChainParams::LIQUID1) {
1560-
return std::unique_ptr<CChainParams>(new CLiquidV1Params());
1566+
return std::unique_ptr<CChainParams>(new CLiquidV1Params(args));
15611567
} else if (chain == CBaseChainParams::LIQUID1TEST) {
15621568
return std::unique_ptr<CChainParams>(new CLiquidV1TestParams(args));
15631569
} else if (chain == CBaseChainParams::LIQUIDTESTNET) {

src/chainparams.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ class CChainParams
135135
const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; }
136136
bool GetEnforcePak() const { return enforce_pak; }
137137
bool GetMultiDataPermitted() const { return multi_data_permitted; }
138+
bool GetAcceptDiscountCT() const { return accept_discount_ct; }
139+
bool GetCreateDiscountCT() const { return create_discount_ct; }
138140

139141
protected:
140142
CChainParams() {}
@@ -167,6 +169,8 @@ class CChainParams
167169
std::string parent_blech32_hrp;
168170
bool enforce_pak;
169171
bool multi_data_permitted;
172+
bool accept_discount_ct;
173+
bool create_discount_ct;
170174
};
171175

172176
/**

src/core_write.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
236236
entry.pushKV("version", static_cast<int64_t>(static_cast<uint32_t>(tx.nVersion)));
237237
entry.pushKV("size", (int)::GetSerializeSize(tx, PROTOCOL_VERSION));
238238
entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
239+
// ELEMENTS: add discountvsize
240+
if (Params().GetAcceptDiscountCT() && tx.IsConfidential()) {
241+
// this is inlined here since we don't have access to GetDiscountedVirtualTransactionSize from policy.h
242+
int size = ::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) + (::GetSerializeSize(tx.witness.vtxinwit, PROTOCOL_VERSION) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
243+
entry.pushKV("discountvsize", size);
244+
} else {
245+
entry.pushKV("discountvsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
246+
}
239247
entry.pushKV("weight", GetTransactionWeight(tx));
240248
entry.pushKV("locktime", (int64_t)tx.nLockTime);
241249

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ void SetupServerArgs(ArgsManager& argsman)
640640
argsman.AddArg("-initialreissuancetokens=<n>", "The amount of reissuance tokens created in the genesis block. (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
641641
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);
642642
argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
643+
argsman.AddArg("-acceptdiscountct", "Accept discounted fees for Confidential Transactions (default: true for liquidv1, false for other chains)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
644+
argsman.AddArg("-creatediscountct", "Create Confidential Transactions with discounted fees (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
643645

644646
#if defined(USE_SYSCALL_SANDBOX)
645647
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);

src/net_processing.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4964,7 +4964,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
49644964
auto txid = txinfo.tx->GetHash();
49654965
auto wtxid = txinfo.tx->GetWitnessHash();
49664966
// Peer told you to not send transactions at that feerate? Don't bother sending it.
4967-
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
4967+
// ELEMENTS: use the discounted vsize here so that discounted CTs are relayed.
4968+
// discountvsize only differs from vsize if accept_discount_ct is true.
4969+
if (txinfo.fee < filterrate.GetFee(txinfo.discountvsize)) {
49684970
continue;
49694971
}
49704972
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;

src/policy/policy.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@ static inline int64_t GetVirtualTransactionInputSize(const CTransaction& tx)
135135
return GetVirtualTransactionInputSize(tx, 0, 0, 0);
136136
}
137137

138+
// ELEMENTS: use a smaller virtual size for discounted Confidential Transactions
139+
static inline int64_t GetDiscountedVirtualTransactionSize(const CTransaction& tx)
140+
{
141+
return ::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) + (::GetSerializeSize(tx.witness.vtxinwit, PROTOCOL_VERSION) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
142+
}
143+
138144
#endif // BITCOIN_POLICY_POLICY_H

src/primitives/transaction.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,12 @@ std::string CTransaction::ToString() const
162162
str += " " + tx_out.ToString() + "\n";
163163
return str;
164164
}
165+
166+
bool CTransaction::IsConfidential() const
167+
{
168+
for (const CTxOut& out : vout) {
169+
if (out.IsFee()) continue;
170+
if (out.nValue.IsExplicit() || out.nAsset.IsExplicit()) return false;
171+
}
172+
return true;
173+
}

src/primitives/transaction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,8 @@ class CTransaction
515515
const uint32_t nLockTime;
516516
// For elements we need to keep track of some extra state for script witness outside of vin
517517
const CTxWitness witness;
518+
// ELEMENTS: true if all outputs (except fee) are blinded
519+
bool IsConfidential() const;
518520

519521
private:
520522
/** Memory only. */

src/txmempool.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,14 @@ void CTxMemPool::queryHashes(std::vector<uint256>& vtxid) const
965965
}
966966

967967
static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it) {
968-
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee()};
968+
// ELEMENTS: include the discounted vsize of the tx
969+
size_t discountvsize = it->GetTxSize();
970+
CTransaction tx = it->GetTx();
971+
// discountvsize only differs from vsize if we accept discounted CTs
972+
if (Params().GetAcceptDiscountCT() && tx.IsConfidential()) {
973+
discountvsize = GetDiscountedVirtualTransactionSize(tx);
974+
}
975+
return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), it->GetFee(), it->GetTxSize(), it->GetModifiedFee() - it->GetFee(), discountvsize};
969976
}
970977

971978
std::vector<TxMempoolInfo> CTxMemPool::infoAll() const

src/txmempool.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ struct TxMempoolInfo
345345

346346
/** The fee delta. */
347347
int64_t nFeeDelta;
348+
349+
/** ELEMENTS: Discounted CT size. */
350+
size_t discountvsize;
348351
};
349352

350353
/** Reason why a transaction was removed from the mempool,

src/validation.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
914914

915915
// No transactions are allowed below minRelayTxFee except from disconnected
916916
// blocks
917-
if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
917+
bool fee_check = CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state);
918+
// ELEMENTS: accept discounted fees for Confidential Transactions only, if enabled.
919+
if (Params().GetAcceptDiscountCT() && tx.IsConfidential()) {
920+
fee_check = CheckFeeRate(GetDiscountedVirtualTransactionSize(tx), ws.m_modified_fees, state);
921+
}
922+
if (!bypass_limits && !fee_check) return false;
918923

919924
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
920925
// Calculate in-mempool ancestors, up to a limit.

src/wallet/spend.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
174174
CTransaction ctx(txNew);
175175
int64_t vsize = GetVirtualTransactionSize(ctx);
176176
int64_t weight = GetTransactionWeight(ctx);
177+
// ELEMENTS: use discounted vsize for CTs if enabled
178+
if (Params().GetCreateDiscountCT() && tx.IsConfidential()) {
179+
vsize = GetDiscountedVirtualTransactionSize(ctx);
180+
}
181+
177182
return TxSize{vsize, weight};
178183
}
179184

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2016 The Bitcoin Core developers
3+
# Distributed under the MIT/X11 software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from decimal import Decimal
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal,
10+
)
11+
12+
class CTTest(BitcoinTestFramework):
13+
def set_test_params(self):
14+
self.num_nodes = 3
15+
self.setup_clean_chain = True
16+
args = [
17+
"-anyonecanspendaremine=1",
18+
"-con_blocksubsidy=0",
19+
"-con_connect_genesis_outputs=1",
20+
"-initialfreecoins=2100000000000000",
21+
"-txindex=1",
22+
]
23+
self.extra_args = [
24+
# node 0 does not accept nor create discounted CTs
25+
args + ["-acceptdiscountct=0", "-creatediscountct=0"],
26+
# node 1 accepts but does not create discounted CTs
27+
args + ["-acceptdiscountct=1", "-creatediscountct=0"],
28+
# node 2 both accepts and creates discounted CTs
29+
args + ["-acceptdiscountct=1", "-creatediscountct=1"],
30+
]
31+
32+
def skip_test_if_missing_module(self):
33+
self.skip_if_no_wallet()
34+
35+
def run_test(self):
36+
feerate = 1.0
37+
38+
node0 = self.nodes[0]
39+
node1 = self.nodes[1]
40+
node2 = self.nodes[2]
41+
42+
self.generate(node0, 101)
43+
balance = node0.getbalance()
44+
assert_equal(balance['bitcoin'], 21000000)
45+
46+
self.log.info("Create UTXOs")
47+
many = {}
48+
num = 25
49+
for i in range(num):
50+
addr = node0.getnewaddress()
51+
info = node0.getaddressinfo(addr)
52+
many[info['unconfidential']] = 1
53+
for i in range(10):
54+
addr = node1.getnewaddress()
55+
info = node1.getaddressinfo(addr)
56+
many[info['unconfidential']] = 1
57+
for i in range(10):
58+
addr = node2.getnewaddress()
59+
info = node2.getaddressinfo(addr)
60+
many[info['unconfidential']] = 1
61+
62+
txid = node0.sendmany("", many)
63+
self.generate(node0, 1)
64+
65+
self.log.info("Send explicit tx to node 0")
66+
addr = node0.getnewaddress()
67+
info = node0.getaddressinfo(addr)
68+
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
69+
tx = node0.gettransaction(txid, True, True)
70+
decoded = tx['decoded']
71+
vin = decoded['vin']
72+
vout = decoded['vout']
73+
assert_equal(len(vin), 2)
74+
assert_equal(len(vout), 3)
75+
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
76+
assert_equal(decoded['vsize'], 326)
77+
self.generate(node0, 1)
78+
79+
self.log.info("Send confidential tx to node 0")
80+
addr = node0.getnewaddress()
81+
info = node0.getaddressinfo(addr)
82+
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
83+
tx = node0.gettransaction(txid, True, True)
84+
decoded = tx['decoded']
85+
vin = decoded['vin']
86+
vout = decoded['vout']
87+
assert_equal(len(vin), 2)
88+
assert_equal(len(vout), 3)
89+
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00002575'))
90+
assert_equal(decoded['vsize'], 2575)
91+
self.generate(node0, 1)
92+
93+
self.log.info("Send explicit tx to node 1")
94+
addr = node1.getnewaddress()
95+
info = node1.getaddressinfo(addr)
96+
txid = node0.sendtoaddress(info['unconfidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
97+
tx = node0.gettransaction(txid, True, True)
98+
decoded = tx['decoded']
99+
vin = decoded['vin']
100+
vout = decoded['vout']
101+
assert_equal(len(vin), 2)
102+
assert_equal(len(vout), 3)
103+
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00000326'))
104+
assert_equal(decoded['vsize'], 326)
105+
self.generate(node0, 1)
106+
107+
self.log.info("Send confidential (undiscounted) tx to node 1")
108+
addr = node1.getnewaddress()
109+
info = node1.getaddressinfo(addr)
110+
txid = node0.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
111+
tx = node0.gettransaction(txid, True, True)
112+
decoded = tx['decoded']
113+
vin = decoded['vin']
114+
vout = decoded['vout']
115+
assert_equal(len(vin), 2)
116+
assert_equal(len(vout), 3)
117+
assert_equal(tx['fee']['bitcoin'], Decimal('-0.00002575'))
118+
assert_equal(decoded['vsize'], 2575)
119+
self.generate(node0, 1)
120+
121+
self.log.info("Send confidential (discounted) tx to node 1")
122+
bitcoin = 'b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23'
123+
addr = node1.getnewaddress()
124+
info = node1.getaddressinfo(addr)
125+
txid = node2.sendtoaddress(info['confidential'], 1.0, "", "", False, None, None, None, None, None, None, feerate)
126+
# node0 won't accept or relay the tx
127+
self.sync_mempools([node1, node2])
128+
assert_equal(node0.getrawmempool(), [])
129+
self.generate(node2, 1, sync_fun=self.sync_blocks)
130+
for node in [node2, node1]:
131+
tx = node.gettransaction(txid, True, True)
132+
decoded = tx['decoded']
133+
vin = decoded['vin']
134+
vout = decoded['vout']
135+
assert_equal(len(vin), 2)
136+
assert_equal(len(vout), 3)
137+
if 'bitcoin' in decoded['fee']:
138+
assert_equal(decoded['fee']['bitcoin'], Decimal('-0.00000437'))
139+
else:
140+
assert_equal(decoded['fee'][bitcoin], Decimal('0.00000437'))
141+
assert_equal(decoded['vsize'], 2575)
142+
assert_equal(decoded['discountvsize'], 437)
143+
144+
# node0 should report the same discountvsize and vsize
145+
tx = node0.getrawtransaction(txid, True)
146+
assert_equal(tx['vsize'], 2575)
147+
assert_equal(tx['discountvsize'], 2575)
148+
149+
150+
if __name__ == '__main__':
151+
CTTest().main()

test/functional/test_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@
185185
'wallet_avoidreuse.py --descriptors',
186186
'mempool_reorg.py',
187187
'mempool_persist.py',
188+
# ELEMENTS: discounted Confidential Transactions
189+
'feature_discount_ct.py',
188190
'wallet_multiwallet.py --legacy-wallet',
189191
'wallet_multiwallet.py --descriptors',
190192
'wallet_multiwallet.py --usecli',

test/util/data/blanktxv1.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": 1,
55
"size": 10,
66
"vsize": 10,
7+
"discountvsize": 10,
78
"weight": 40,
89
"locktime": 0,
910
"vin": [

test/util/data/blanktxv2.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": 2,
55
"size": 10,
66
"vsize": 10,
7+
"discountvsize": 10,
78
"weight": 40,
89
"locktime": 0,
910
"vin": [

test/util/data/tt-delin1-out.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": 1,
55
"size": 3040,
66
"vsize": 3040,
7+
"discountvsize": 3040,
78
"weight": 12160,
89
"locktime": 0,
910
"vin": [

test/util/data/tt-delout1-out.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": 1,
55
"size": 3155,
66
"vsize": 3155,
7+
"discountvsize": 3155,
78
"weight": 12620,
89
"locktime": 0,
910
"vin": [

test/util/data/tt-locktime317000-out.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": 1,
55
"size": 3189,
66
"vsize": 3189,
7+
"discountvsize": 3189,
78
"weight": 12756,
89
"locktime": 317000,
910
"vin": [

0 commit comments

Comments
 (0)