Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add an implementation of DIP 0027 Credit Asset Locks #5026

Merged
merged 37 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
967443a
feat: add implementation of DIP 0027 Credit Asset Locks
knst Sep 29, 2022
ae631b5
feat: add unittests for SkipSet structure
knst Nov 29, 2022
a16fe16
cleanup mninfo
knst May 9, 2023
c763d97
fixups for coin
knst May 11, 2023
2db5c05
refactor: clean up duplicated code - using get_recovered_sigs from fr…
knst May 11, 2023
3ffdcdd
fixup after assetlocks
knst Apr 25, 2023
d07abef
using check_mempool_size() instead direct call of RPC
knst May 11, 2023
4fa8f1c
fixup codestyle
knst May 11, 2023
fc8e7d2
fixup feature_nulldummy
knst May 11, 2023
a1a0ce1
feat: instead comments in functional test use logs for more clear pro…
knst May 16, 2023
51d095a
improved performance of functional test feature_asset_locks significa…
knst May 16, 2023
157a282
resolve conflicts after refactoring util/validation.h follow-up bitco…
knst Jun 1, 2023
e5469cf
resolve conflicts
knst Jun 8, 2023
6836e9d
fix: removed nType from AssetLock tx accordingly DIP
knst Jun 16, 2023
cd22d3a
fix: nVersion in Asset Lock/Unlock transaction's payload is 8bits not 16
knst Jun 16, 2023
3416553
fixup missing explicit
knst Jun 17, 2023
1b2944d
fixes accordingly to codereview
knst Jun 20, 2023
9a7645c
codestyle: { at new line
knst Jun 20, 2023
46c6c6d
fix for SkipList unit tests accordingly code review
knst Jun 20, 2023
1e02bcf
more fixes accodringly to review
knst Jun 20, 2023
4ebfcc4
fix inflation bug for credit outputs in assetlock tx
knst Jun 20, 2023
4ff8e75
improved hash calculation
knst Jun 20, 2023
4f60fb2
hidden implementation of asset locks from tx_verify
knst Jun 20, 2023
4e6e963
cleanup `quorum_count`
knst Jun 20, 2023
f2c2df2
fix: revert hash calculation of AssetUnlock transaction
UdjinM6 Jun 20, 2023
55dff94
refactor: add more const for local variables that is not supposed to …
knst Jun 21, 2023
3908739
fix: apply suggestions from code review
knst Jun 30, 2023
128ef19
refactor: move trivial getters of CAsset{Lock,Unlock}Payload to header
knst Jul 9, 2023
23fc477
refactor: moved CSkipSet from CreditPool.h to separate file
knst Jul 13, 2023
95b8d19
fix: code-style adjusting for methods naming in credit pool and relat…
knst Jul 13, 2023
5d29b06
fix: fixing typos and improving comments. Fixing error message for v2…
knst Jul 14, 2023
2ea4251
fix: replaced an assert to an exception in CSkipSet inside Add
knst Jul 14, 2023
54ae92d
fix: add missing #define guards for skip_set
knst Jul 14, 2023
0e793c6
fix: removed duplicated `return false` from src/evo/specialtxman.cpp
knst Jul 18, 2023
5534f64
feat: check credit pool amount against all nodes in functional test
knst Jul 19, 2023
dcd9262
feat: add an extra node with wallet for asset locks function test
knst Jul 19, 2023
2c6aa49
feat: new tests with asset-unlock tx absurdly high fee and zero fee
knst Jul 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ BITCOIN_CORE_H = \
cuckoocache.h \
ctpl_stl.h \
cxxtimer.hpp \
evo/assetlocktx.h \
evo/dmn_types.h \
evo/cbtx.h \
evo/creditpool.h \
evo/deterministicmns.h \
evo/dmnstate.h \
evo/evodb.h \
Expand Down Expand Up @@ -320,6 +322,7 @@ BITCOIN_CORE_H = \
util/underlying.h \
util/serfloat.h \
util/settings.h \
util/skip_set.h \
util/sock.h \
util/string.h \
util/time.h \
Expand Down Expand Up @@ -384,7 +387,9 @@ libbitcoin_server_a_SOURCES = \
consensus/tx_verify.cpp \
dbwrapper.cpp \
dsnotificationinterface.cpp \
evo/assetlocktx.cpp \
evo/cbtx.cpp \
evo/creditpool.cpp \
evo/deterministicmns.cpp \
evo/dmnstate.cpp \
evo/evodb.cpp \
Expand Down Expand Up @@ -731,6 +736,7 @@ libbitcoin_util_a_SOURCES = \
util/message.cpp \
util/moneystr.cpp \
util/settings.cpp \
util/skip_set.cpp \
util/spanparsing.cpp \
util/strencodings.cpp \
util/time.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ BITCOIN_TESTS =\
test/dip0020opcodes_tests.cpp \
test/descriptor_tests.cpp \
test/dynamic_activation_thresholds_tests.cpp \
test/evo_assetlocks_tests.cpp \
test/evo_deterministicmns_tests.cpp \
test/evo_instantsend_tests.cpp \
test/evo_simplifiedmns_tests.cpp \
Expand Down
4 changes: 4 additions & 0 deletions src/bloom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t
case(TRANSACTION_MNHF_SIGNAL):
// No additional checks for this transaction types
return false;
case(TRANSACTION_ASSET_LOCK):
case(TRANSACTION_ASSET_UNLOCK):
// TODO asset lock/unlock bloom?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it's not needed for us to handle bloom for either of these, but @UdjinM6 please provide your thoughts

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOCK: could probably use it for p2pkh inside op_return but that's a question for platform/mobile guys if they need to track LOCK txes this way.
UNLOCK: I can't think of any use case here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we need to figure out if we want to remove this TODO or implement something here

return false;
}

LogPrintf("Unknown special transaction type in Bloom filter check.\n");
Expand Down
4 changes: 4 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class CMainParams : public CChainParams {
consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_100_67;
consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_400_85;
consensus.llmqTypeAssetLocks = Consensus::LLMQType::LLMQ_400_85;

fDefaultConsistencyChecks = false;
fRequireStandard = true;
Expand Down Expand Up @@ -447,6 +448,7 @@ class CTestNetParams : public CChainParams {
consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_25_67;
consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_50_60;
consensus.llmqTypeAssetLocks = Consensus::LLMQType::LLMQ_50_60;

fDefaultConsistencyChecks = false;
fRequireStandard = false;
Expand Down Expand Up @@ -619,6 +621,7 @@ class CDevNetParams : public CChainParams {
consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_100_67;
consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_50_60;
consensus.llmqTypeAssetLocks = Consensus::LLMQType::LLMQ_50_60;

UpdateDevnetLLMQChainLocksFromArgs(args);
UpdateDevnetLLMQInstantSendFromArgs(args);
Expand Down Expand Up @@ -891,6 +894,7 @@ class CRegTestParams : public CChainParams {
consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_TEST_DIP0024;
consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_TEST_PLATFORM;
consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_TEST;
consensus.llmqTypeAssetLocks = Consensus::LLMQType::LLMQ_TEST;

UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST);
UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST_INSTANTSEND);
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ struct Params {
LLMQType llmqTypeDIP0024InstantSend{LLMQType::LLMQ_NONE};
LLMQType llmqTypePlatform{LLMQType::LLMQ_NONE};
LLMQType llmqTypeMnhf{LLMQType::LLMQ_NONE};
LLMQType llmqTypeAssetLocks{LLMQType::LLMQ_NONE};
};
} // namespace Consensus

Expand Down
13 changes: 9 additions & 4 deletions src/consensus/tx_check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@

bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
{
bool allowEmptyTxInOut = false;
bool allowEmptyTxIn = false;
bool allowEmptyTxOut = false;
if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) {
allowEmptyTxInOut = true;
allowEmptyTxIn = true;
allowEmptyTxOut = true;
}
if (tx.nType == TRANSACTION_ASSET_UNLOCK) {
allowEmptyTxIn = true;
}

// Basic checks that don't depend on any context
if (!allowEmptyTxInOut && tx.vin.empty())
if (!allowEmptyTxIn && tx.vin.empty())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty");
if (!allowEmptyTxInOut && tx.vout.empty())
if (!allowEmptyTxOut && tx.vout.empty())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-empty");
// Size limits
if (::GetSerializeSize(tx, PROTOCOL_VERSION) > MAX_LEGACY_BLOCK_SIZE)
Expand Down
6 changes: 6 additions & 0 deletions src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <consensus/validation.h>
#include <evo/assetlocktx.h>

// TODO remove the following dependencies
#include <chain.h>
Expand Down Expand Up @@ -160,6 +161,11 @@ unsigned int GetTransactionSigOpCount(const CTransaction& tx, const CCoinsViewCa

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
{

if (bool isAssetUnlockTx = (tx.nVersion == 3 && tx.nType == TRANSACTION_ASSET_UNLOCK); isAssetUnlockTx) {
return GetAssetUnlockFee(tx, txfee, state);
}

// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
Expand Down
15 changes: 15 additions & 0 deletions src/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <spentindex.h>

#include <evo/assetlocktx.h>
#include <evo/cbtx.h>
#include <evo/mnhftx.h>
#include <evo/providertx.h>
Expand Down Expand Up @@ -310,6 +311,20 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
mnhfTx.ToJson(obj);
entry.pushKV("mnhfTx", obj);
}
} else if (tx.nType == TRANSACTION_ASSET_LOCK) {
CAssetLockPayload assetLockTx;
if (!GetTxPayload(tx, assetLockTx)) {
UniValue obj;
assetLockTx.ToJson(obj);
entry.pushKV("assetLockTx", obj);
}
} else if (tx.nType == TRANSACTION_ASSET_UNLOCK) {
CAssetUnlockPayload assetUnlockTx;
if (!GetTxPayload(tx, assetUnlockTx)) {
UniValue obj;
assetUnlockTx.ToJson(obj);
entry.pushKV("assetUnlockTx", obj);
}
}

if (!hashBlock.IsNull())
Expand Down
205 changes: 205 additions & 0 deletions src/evo/assetlocktx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright (c) 2023 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <evo/assetlocktx.h>
#include <evo/specialtx.h>
#include <evo/creditpool.h>

#include <consensus/params.h>

#include <chainparams.h>
#include <logging.h>
#include <validation.h>

#include <llmq/commitment.h>
#include <llmq/signing.h>
#include <llmq/utils.h>
#include <llmq/quorums.h>

#include <algorithm>

/**
* Common code for Asset Lock and Asset Unlock
*/
bool CheckAssetLockUnlockTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCreditPool& creditPool, TxValidationState& state)
{
switch (tx.nType) {
case TRANSACTION_ASSET_LOCK:
return CheckAssetLockTx(tx, state);
case TRANSACTION_ASSET_UNLOCK:
return CheckAssetUnlockTx(tx, pindexPrev, creditPool, state);
default:
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-not-asset-locks-at-all");
}
}

/**
* Asset Lock Transaction
*/
bool CheckAssetLockTx(const CTransaction& tx, TxValidationState& state)
{
if (tx.nType != TRANSACTION_ASSET_LOCK) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-type");
}

CAmount returnAmount{0};
for (const CTxOut& txout : tx.vout) {
const CScript& script = txout.scriptPubKey;
if (script.empty() || script[0] != OP_RETURN) continue;

if (script.size() != 2 || script[1] != 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-non-empty-return");

if (txout.nValue == 0 || !MoneyRange(txout.nValue)) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-opreturn-outofrange");

// Should be only one OP_RETURN
if (returnAmount > 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-multiple-return");
returnAmount = txout.nValue;
}

if (returnAmount == 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-no-return");

CAssetLockPayload assetLockTx;
if (!GetTxPayload(tx, assetLockTx)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-payload");
}

if (assetLockTx.getVersion() == 0 || assetLockTx.getVersion() > CAssetLockPayload::CURRENT_VERSION) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-version");
}

if (assetLockTx.getCreditOutputs().empty()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-emptycreditoutputs");
}

CAmount creditOutputsAmount = 0;
for (const CTxOut& out : assetLockTx.getCreditOutputs()) {
if (out.nValue == 0 || !MoneyRange(out.nValue) || !MoneyRange(creditOutputsAmount + out.nValue)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-credit-outofrange");
}

creditOutputsAmount += out.nValue;
if (!out.scriptPubKey.IsPayToPublicKeyHash()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-pubKeyHash");
}
}
if (creditOutputsAmount != returnAmount) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-creditamount");
}

return true;
}

std::string CAssetLockPayload::ToString() const
{
std::string outputs{"["};
for (const CTxOut& tx: creditOutputs) {
outputs.append(tx.ToString());
outputs.append(",");
}
outputs.back() = ']';
return strprintf("CAssetLockPayload(nVersion=%d,creditOutputs=%s)", nVersion, outputs.c_str());
}

/**
* Asset Unlock Transaction (withdrawals)
*/

const std::string ASSETUNLOCK_REQUESTID_PREFIX = "plwdtx";

bool CAssetUnlockPayload::VerifySig(const uint256& msgHash, const CBlockIndex* pindexTip, TxValidationState& state) const
{
// That quourm hash must be active at `requestHeight`,
// and at the quorumHash must be active in either the current or previous quorum cycle
// and the sig must validate against that specific quorumHash.

Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypeAssetLocks;

// We check at most 2 quorums
const auto quorums = llmq::quorumManager->ScanQuorums(llmqType, pindexTip, 2);
bool isActive = std::any_of(quorums.begin(), quorums.end(), [&](const auto &q) { return q->qc->quorumHash == quorumHash; });

if (!isActive) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-not-active-quorum");
}

if (pindexTip->nHeight < requestedHeight || pindexTip->nHeight >= getHeightToExpiry()) {
LogPrintf("Asset unlock tx %d with requested height %d could not be accepted on height: %d\n",
index, requestedHeight, pindexTip->nHeight);
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-too-late");
}

const auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
assert(quorum);

const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, index));

const uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
if (quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) {
return true;
}

return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-not-verified");
}

bool CheckAssetUnlockTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCreditPool& creditPool, TxValidationState& state)
{
if (tx.nType != TRANSACTION_ASSET_UNLOCK) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-type");
}

if (!tx.vin.empty()) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-have-input");
}

if (tx.vout.size() > CAssetUnlockPayload::MAXIMUM_WITHDRAWALS) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-too-many-outs");
}

CAssetUnlockPayload assetUnlockTx;
if (!GetTxPayload(tx, assetUnlockTx)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-payload");
}

if (assetUnlockTx.getVersion() == 0 || assetUnlockTx.getVersion() > CAssetUnlockPayload::CURRENT_VERSION) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-version");
}

if (creditPool.indexes.Contains(assetUnlockTx.getIndex())) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-duplicated-index");
}

const CBlockIndex* pindexQuorum = WITH_LOCK(cs_main, return g_chainman.m_blockman.LookupBlockIndex(assetUnlockTx.getQuorumHash()));
if (!pindexQuorum) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-quorum-hash");
}

// Copy transaction except `quorumSig` field to calculate hash
CMutableTransaction tx_copy(tx);
const CAssetUnlockPayload payload_copy{assetUnlockTx.getVersion(), assetUnlockTx.getIndex(), assetUnlockTx.getFee(), assetUnlockTx.getRequestedHeight(), assetUnlockTx.getQuorumHash(), CBLSSignature{}};
SetTxPayload(tx_copy, payload_copy);

uint256 msgHash = tx_copy.GetHash();

return assetUnlockTx.VerifySig(msgHash, pindexPrev, state);
}

bool GetAssetUnlockFee(const CTransaction& tx, CAmount& txfee, TxValidationState& state)
{
CAssetUnlockPayload assetUnlockTx;
if (!GetTxPayload(tx, assetUnlockTx)) {
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-payload");
}
const CAmount txfee_aux = assetUnlockTx.getFee();
if (txfee_aux == 0 || !MoneyRange(txfee_aux)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-assetunlock-fee-outofrange");
}
txfee = txfee_aux;
return true;
}

std::string CAssetUnlockPayload::ToString() const
{
return strprintf("CAssetUnlockPayload(nVersion=%d,index=%d,fee=%d.%08d,requestedHeight=%d,quorumHash=%d,quorumSig=%s",
nVersion, index, fee / COIN, fee % COIN, requestedHeight, quorumHash.GetHex(), quorumSig.ToString().c_str());
}
Loading