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

[Test] [DMNs] Split special transaction validation #2915

Merged
merged 6 commits into from
Mar 30, 2024
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
2 changes: 2 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ JSON_TEST_FILES = \
test/data/tx_invalid.json \
test/data/tx_valid.json \
test/data/sighash.json \
test/data/specialtx_valid.json \
test/data/specialtx_invalid.json \
test/data/merkle_roots_sapling.json \
test/data/merkle_serialization_sapling.json \
test/data/merkle_witness_serialization_sapling.json \
Expand Down
88 changes: 88 additions & 0 deletions src/evo/providertx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,49 @@ void ProRegPL::ToJson(UniValue& obj) const
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool ProRegPL::IsTriviallyValid(CValidationState& state) const
{
if (nVersion == 0 || nVersion > ProRegPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
if (nType != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-type");
}
if (nMode != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
}

if (keyIDOwner.IsNull() || keyIDVoting.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null");
}
if (!pubKeyOperator.IsValid()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-key-invalid");
}
// we may support other kinds of scripts later, but restrict it for now
if (!scriptPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee");
}
if (!scriptOperatorPayout.empty() && !scriptOperatorPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee");
}

CTxDestination payoutDest;
if (!ExtractDestination(scriptPayout, payoutDest)) {
// should not happen as we checked script types before
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest");
}
// don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server)
if (payoutDest == CTxDestination(keyIDOwner) ||
payoutDest == CTxDestination(keyIDVoting)) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse");
}

if (nOperatorReward > 10000) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward");
}
return true;
}

std::string ProUpServPL::ToString() const
{
CTxDestination dest;
Expand All @@ -79,6 +122,14 @@ void ProUpServPL::ToJson(UniValue& obj) const
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool ProUpServPL::IsTriviallyValid(CValidationState& state) const
{
if (nVersion == 0 || nVersion > ProUpServPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
return true;
}

std::string ProUpRegPL::ToString() const
{
CTxDestination dest;
Expand All @@ -103,6 +154,29 @@ void ProUpRegPL::ToJson(UniValue& obj) const
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool ProUpRegPL::IsTriviallyValid(CValidationState& state) const
{
if (nVersion == 0 || nVersion > ProUpRegPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
if (nMode != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
}

if (!pubKeyOperator.IsValid()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-key-invalid");
}
if (keyIDVoting.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-voting-key-null");
}
// !TODO: enable other scripts
if (!scriptPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee");
}

return true;
}

std::string ProUpRevPL::ToString() const
{
return strprintf("ProUpRevPL(nVersion=%d, proTxHash=%s, nReason=%d)",
Expand All @@ -119,6 +193,20 @@ void ProUpRevPL::ToJson(UniValue& obj) const
obj.pushKV("inputsHash", inputsHash.ToString());
}

bool ProUpRevPL::IsTriviallyValid(CValidationState& state) const
{
if (nVersion == 0 || nVersion > ProUpRevPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}

// pl.nReason < ProUpRevPL::REASON_NOT_SPECIFIED is always `false` since
// pl.nReason is unsigned and ProUpRevPL::REASON_NOT_SPECIFIED == 0
if (nReason > ProUpRevPL::REASON_LAST) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-reason");
}
return true;
}

bool GetProRegCollateral(const CTransactionRef& tx, COutPoint& outRet)
{
if (tx == nullptr) {
Expand Down
13 changes: 12 additions & 1 deletion src/evo/providertx.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
#define PIVX_PROVIDERTX_H

#include "bls/bls_wrapper.h"
#include "primitives/transaction.h"
#include "netaddress.h"
#include "primitives/transaction.h"
#include <consensus/validation.h>

#include <univalue.h>

class CValidationState;

// Provider-Register tx payload

class ProRegPL
{
public:
static const uint16_t CURRENT_VERSION = 1;
static constexpr int16_t SPECIALTX_TYPE = CTransaction::TxType::PROREG;

public:
uint16_t nVersion{CURRENT_VERSION}; // message version
Expand Down Expand Up @@ -60,6 +64,7 @@ class ProRegPL

std::string ToString() const;
void ToJson(UniValue& obj) const;
bool IsTriviallyValid(CValidationState& state) const;
};

// Provider-Update-Service tx payload
Expand All @@ -68,6 +73,7 @@ class ProUpServPL
{
public:
static const uint16_t CURRENT_VERSION = 1;
static constexpr int16_t SPECIALTX_TYPE = CTransaction::TxType::PROUPSERV;

public:
uint16_t nVersion{CURRENT_VERSION}; // message version
Expand All @@ -89,13 +95,15 @@ class ProUpServPL
public:
std::string ToString() const;
void ToJson(UniValue& obj) const;
bool IsTriviallyValid(CValidationState& state) const;
};

// Provider-Update-Registrar tx payload
class ProUpRegPL
{
public:
static const uint16_t CURRENT_VERSION = 1;
static constexpr int16_t SPECIALTX_TYPE = CTransaction::TxType::PROUPREG;

public:
uint16_t nVersion{CURRENT_VERSION}; // message version
Expand All @@ -119,13 +127,15 @@ class ProUpRegPL
public:
std::string ToString() const;
void ToJson(UniValue& obj) const;
bool IsTriviallyValid(CValidationState& state) const;
};

// Provider-Update-Revoke tx payload
class ProUpRevPL
{
public:
static const uint16_t CURRENT_VERSION = 1;
static constexpr int16_t SPECIALTX_TYPE = CTransaction::TxType::PROUPREV;

// these are just informational and do not have any effect on the revocation
enum RevocationReason {
Expand Down Expand Up @@ -155,6 +165,7 @@ class ProUpRevPL
public:
std::string ToString() const;
void ToJson(UniValue& obj) const;
bool IsTriviallyValid(CValidationState& state) const;
};


Expand Down
107 changes: 24 additions & 83 deletions src/evo/specialtx_validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,46 +108,11 @@ static bool CheckCollateralOut(const CTxOut& out, const ProRegPL& pl, CValidatio
// Provider Register Payload
static bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache* view, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROREG);

ProRegPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProRegPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
if (pl.nType != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-type");
}
if (pl.nMode != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
}

if (pl.keyIDOwner.IsNull() || pl.keyIDVoting.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null");
}
if (!pl.pubKeyOperator.IsValid()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-key-invalid");
}
// we may support other kinds of scripts later, but restrict it for now
if (!pl.scriptPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee");
}
if (!pl.scriptOperatorPayout.empty() && !pl.scriptOperatorPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee");
}

CTxDestination payoutDest;
if (!ExtractDestination(pl.scriptPayout, payoutDest)) {
// should not happen as we checked script types before
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest");
}
// don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server)
if (payoutDest == CTxDestination(pl.keyIDOwner) ||
payoutDest == CTxDestination(pl.keyIDVoting)) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse");
if (!GetValidatedTxPayload(tx, pl, state)) {
// pass the state returned by the function above
return false;
}

// It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be issues later
Expand All @@ -157,10 +122,6 @@ static bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev,
return false;
}

if (pl.nOperatorReward > 10000) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward");
}

if (pl.collateralOutpoint.hash.IsNull()) {
// collateral included in the proReg tx
if (pl.collateralOutpoint.n >= tx.vout.size()) {
Expand Down Expand Up @@ -229,15 +190,11 @@ static bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev,
// Provider Update Service Payload
static bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROUPSERV);

ProUpServPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProUpServPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
if (!GetValidatedTxPayload(tx, pl, state)) {
// pass the state returned by the function above
return false;
}

if (!CheckService(pl.addr, state)) {
Expand Down Expand Up @@ -286,29 +243,11 @@ static bool CheckProUpServTx(const CTransaction& tx, const CBlockIndex* pindexPr
// Provider Update Registrar Payload
static bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache* view, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROUPREG);

ProUpRegPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProUpRegPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}
if (pl.nMode != 0) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode");
}

if (!pl.pubKeyOperator.IsValid()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-key-invalid");
}
if (pl.keyIDVoting.IsNull()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-voting-key-null");
}
// !TODO: enable other scripts
if (!pl.scriptPayout.IsPayToPublicKeyHash()) {
return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee");
if (!GetValidatedTxPayload(tx, pl, state)) {
// pass the state returned by the function above
return false;
}

CTxDestination payoutDest;
Expand Down Expand Up @@ -382,21 +321,11 @@ static bool CheckProUpRegTx(const CTransaction& tx, const CBlockIndex* pindexPre
// Provider Update Revoke Payload
static bool CheckProUpRevTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state)
{
assert(tx.nType == CTransaction::TxType::PROUPREV);

ProUpRevPL pl;
if (!GetTxPayload(tx, pl)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}

if (pl.nVersion == 0 || pl.nVersion > ProUpRevPL::CURRENT_VERSION) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-version");
}

// pl.nReason < ProUpRevPL::REASON_NOT_SPECIFIED is always `false` since
// pl.nReason is unsigned and ProUpRevPL::REASON_NOT_SPECIFIED == 0
if (pl.nReason > ProUpRevPL::REASON_LAST) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-reason");
if (!GetValidatedTxPayload(tx, pl, state)) {
// pass the state returned by the function above
return false;
}

if (!CheckInputsHash(tx, pl, state)) {
Expand Down Expand Up @@ -656,3 +585,15 @@ uint256 CalcTxInputsHash(const CTransaction& tx)
}
return hw.GetHash();
}

template <typename T>
bool GetValidatedTxPayload(const CTransaction& tx, T& obj, CValidationState& state)
{
if (tx.nType != T::SPECIALTX_TYPE) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-type");
}
if (!GetTxPayload(tx, obj)) {
return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload");
}
return obj.IsTriviallyValid(state);
}
3 changes: 3 additions & 0 deletions src/evo/specialtx_validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ bool VerifyLLMQCommitment(const llmq::CFinalCommitment& qfc, const CBlockIndex*

uint256 CalcTxInputsHash(const CTransaction& tx);

template <typename T>
bool GetValidatedTxPayload(const CTransaction& tx, T& obj, CValidationState& state);

#endif // PIVX_SPECIALTX_H
2 changes: 2 additions & 0 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ set(JSON_TEST_FILES
${CMAKE_CURRENT_SOURCE_DIR}/data/tx_invalid.json
${CMAKE_CURRENT_SOURCE_DIR}/data/tx_valid.json
${CMAKE_CURRENT_SOURCE_DIR}/data/sighash.json
${CMAKE_CURRENT_SOURCE_DIR}/data/specialtx_valid.json
${CMAKE_CURRENT_SOURCE_DIR}/data/specialtx_invalid.json
${CMAKE_CURRENT_SOURCE_DIR}/data/merkle_roots_sapling.json
${CMAKE_CURRENT_SOURCE_DIR}/data/merkle_serialization_sapling.json
${CMAKE_CURRENT_SOURCE_DIR}/data/merkle_witness_serialization_sapling.json
Expand Down
Loading