Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions src/evo/specialtxman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,3 +1235,32 @@ bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> p

return true;
}

bool IsStandardSpecialTx(const CTransaction& tx, std::string& reason)
{
if (!tx.IsSpecialTxVersion()) return true;

if (tx.nType != TRANSACTION_ASSET_LOCK) return true;

// Each input is referenced by Platform's funding state transition; beyond this
// many inputs that state transition exceeds Platform's ~20 kB size limit.
static constexpr size_t MAX_STANDARD_ASSET_LOCK_INPUTS{100};
if (tx.vin.size() > MAX_STANDARD_ASSET_LOCK_INPUTS) {
reason = "assetlocktx-too-many-inputs";
return false;
}

constexpr int max_tx_size_for_platform = 20480;
if (tx.GetTotalSize() > max_tx_size_for_platform) {
reason = "assetlocktx-too-big";
return false;
}

if (const auto opt_assetLockTx = GetTxPayload<CAssetLockPayload>(tx);
opt_assetLockTx.has_value() && opt_assetLockTx->getVersion() >= 2) {
reason = "assetlocktx-version-2";
return false;
}
Comment thread
knst marked this conversation as resolved.

return true;
}
16 changes: 16 additions & 0 deletions src/evo/specialtxman.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,20 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> p
bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, CDeterministicMNManager& dmnman,
const ChainstateManager& chainman, TxValidationState& state, bool check_sigs);


/**
* Asset lock transactions with more than 100 inputs (and so over ~20 kB) can not
* be processed by Platform, so Dash Core nodes should not relay them: they are
* marked non-standard, which keeps the network from propagating them over p2p.
*
* Asset lock v2 is enabled by the v24 fork, but Platform can not process it yet.
* It is kept non-standard so it can be enabled later without another hard fork.
*
* These are relay/mempool checks only: a rejected transaction stays valid inside
* a block.
*
* Returns false (with `reason` set) for a non-standard asset lock, and
* true for any transaction that is not an asset lock or not special-tx.
*/
bool IsStandardSpecialTx(const CTransaction& tx, std::string& reason);
#endif // BITCOIN_EVO_SPECIALTXMAN_H
3 changes: 3 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (fRequireStandard && !IsStandardTx(tx, reason))
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);

if (fRequireStandard && !IsStandardSpecialTx(tx, reason))
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);

// Do not work on transactions that are too small.
// A transaction with 1 empty scriptSig input and 1 P2SH output has size of 83 bytes.
// Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying
Expand Down
34 changes: 32 additions & 2 deletions test/functional/feature_asset_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def create_assetlock(self, coin, amount, pubkey):
node_wallet = self.nodes[0]


inputs = [CTxIn(COutPoint(int(coin["txid"], 16), coin["vout"]))]
coins = coin if isinstance(coin, list) else [coin]
inputs = [CTxIn(COutPoint(int(c["txid"], 16), c["vout"])) for c in coins]

credit_outputs = []
tmp_amount = amount
Expand All @@ -78,7 +79,7 @@ def create_assetlock(self, coin, amount, pubkey):

lockTx_payload = CAssetLockTx(1, credit_outputs)

remaining = int(COIN * coin['amount']) - tiny_amount - amount
remaining = sum(int(COIN * c['amount']) for c in coins) - tiny_amount - amount

tx_output_ret = CTxOut(amount, CScript([OP_RETURN, b""]))
tx_output = CTxOut(remaining, key_to_p2pk_script(pubkey))
Expand Down Expand Up @@ -270,6 +271,7 @@ def run_test(self):
self.test_mn_rr(node_wallet, node, pubkey)
self.test_withdrawals_fork(node_wallet, node, pubkey)
self.test_v24_fork(node_wallet, node, pubkey)
self.test_non_standard(node_wallet, node, pubkey)


def test_asset_locks(self, node_wallet, node, pubkey):
Expand Down Expand Up @@ -759,5 +761,33 @@ def test_v24_fork(self, node_wallet, node, pubkey):
assert txid_in_block not in node.getblock(tip_hash)['tx']


def test_non_standard(self, node_wallet, node, pubkey):
self.log.info("Split one coin into 101 outputs to build an asset lock with >100 inputs")
raw = node_wallet.createrawtransaction([], [{node_wallet.getnewaddress(): 1} for _ in range(101)])
funded = node_wallet.fundrawtransaction(raw, {'change_position': 101})['hex']
split_txid = node_wallet.sendrawtransaction(node_wallet.signrawtransactionwithwallet(funded)['hex'])
self.generate(node, 1)
many_coins = [{'txid': split_txid, 'vout': i, 'amount': 1} for i in range(101)]
tx_many_inputs = self.create_assetlock(many_coins, COIN, pubkey)
assert_equal(len(tx_many_inputs.vin), 101)

self.log.info("A standard node (-acceptnonstdtxn=1) rejects them; the permissive node accepts them")
self.restart_node(1, self.extra_args[1] + ["-acceptnonstdtxn=1"])
self.connect_nodes(1, 0)

tx_hex = tx_many_inputs.serialize().hex()
assert_equal(node.testmempoolaccept([tx_hex])[0]['allowed'], True)
rejected = node_wallet.testmempoolaccept([tx_hex])[0]
assert_equal(rejected['allowed'], False)
assert_equal(rejected['reject-reason'], 'assetlocktx-too-many-inputs')

txid = node.sendrawtransaction(tx_hex)
block_hash = self.generate(node, 1)[0]
for checked_node in self.nodes:
assert txid in checked_node.getblock(block_hash)['tx']
self.restart_node(1, self.extra_args[1])
self.connect_nodes(1, 0)


Comment thread
PastaPastaPasta marked this conversation as resolved.
if __name__ == '__main__':
AssetLocksTest().main()
Loading