More work on CoinbaseGuard and validation
This commit is contained in:
@@ -112,7 +112,8 @@ int32_t iguana_rwbignum(int32_t rwflag,uint8_t *serialized,int32_t len,uint8_t *
|
||||
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
|
||||
int64_t CCaddress_balance(char *coinaddr);
|
||||
CPubKey CCtxidaddr(char *txidaddr,uint256 txid);
|
||||
bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, std::vector<std::vector<unsigned char>> &preConditions, std::vector<std::vector<unsigned char>> ¶ms);
|
||||
bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn,
|
||||
CTransaction &txOut, std::vector<std::vector<unsigned char>> &preConditions, std::vector<std::vector<unsigned char>> ¶ms);
|
||||
|
||||
int64_t OraclePrice(int32_t height,uint256 reforacletxid,char *markeraddr,char *format);
|
||||
uint8_t DecodeOraclesCreateOpRet(const CScript &scriptPubKey,std::string &name,std::string &description,std::string &format);
|
||||
|
||||
@@ -197,9 +197,9 @@ bool Getscriptaddress(char *destaddr,const CScript &scriptPubKey)
|
||||
return(false);
|
||||
}
|
||||
|
||||
bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, std::vector<std::vector<unsigned char>> &preConditions, std::vector<std::vector<unsigned char>> ¶ms)
|
||||
bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn,
|
||||
CTransaction &txOut, std::vector<std::vector<unsigned char>> &preConditions, std::vector<std::vector<unsigned char>> ¶ms)
|
||||
{
|
||||
CTransaction txOut;
|
||||
uint256 blockHash;
|
||||
bool isValid = false;
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "CoinbaseGuard.h"
|
||||
#include "script/script.h"
|
||||
#include "main.h"
|
||||
#include "hash.h"
|
||||
|
||||
#include "streams.h"
|
||||
|
||||
extern int32_t VERUS_MIN_STAKEAGE;
|
||||
|
||||
@@ -97,6 +100,22 @@ CStakeParams::CStakeParams(std::vector<std::vector<unsigned char>> vData)
|
||||
}
|
||||
}
|
||||
|
||||
bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams)
|
||||
{
|
||||
std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>();
|
||||
|
||||
if (stakeTx.vin.size() == 1 &&
|
||||
stakeTx.vout.size() == 2 &&
|
||||
stakeTx.vout[0].nValue > 0 &&
|
||||
stakeTx.vout[1].scriptPubKey.IsOpReturn() &&
|
||||
UnpackStakeOpRet(stakeTx, vData))
|
||||
{
|
||||
stakeParams = CStakeParams(vData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// this validates everything, except the PoS eligibility and the actual stake spend. the only time it matters
|
||||
// is to validate a properly formed stake transaction for either pre-check before PoS validity check, or to
|
||||
// validate the stake transaction on a fork that will be used to spend a winning stake that cheated by being posted
|
||||
@@ -108,7 +127,7 @@ bool ValidateStakeTransaction(const CTransaction &stakeTx, CStakeParams &stakePa
|
||||
// a valid stake transaction has one input and two outputs, one output is the monetary value and one is an op_ret with CStakeParams
|
||||
// stake output #1 must be P2PK or P2PKH, unless a delegate for the coinbase is specified
|
||||
|
||||
bool isValid = false;;
|
||||
bool isValid = false;
|
||||
if (stakeTx.vin.size() == 1 &&
|
||||
stakeTx.vout.size() == 2 &&
|
||||
stakeTx.vout[0].nValue > 0 &&
|
||||
@@ -171,36 +190,100 @@ bool MakeGuardedOutput(CAmount value, CPubKey &dest, CTransaction &stakeTx, CTxO
|
||||
// destination address or a properly signed stake transaction of the same utxo on a fork
|
||||
vout = MakeCC1of2vout(EVAL_COINBASEGUARD, value, dest, ccAddress);
|
||||
|
||||
// add parameters to scriptPubKey
|
||||
COptCCParams p = COptCCParams(COptCCParams::VERSION, EVAL_COINBASEGUARD, 1, 2);
|
||||
std::vector<CPubKey> vPubKeys = std::vector<CPubKey>();
|
||||
vPubKeys.push_back(dest);
|
||||
vPubKeys.push_back(ccAddress);
|
||||
|
||||
std::vector<std::vector<unsigned char>> vData = std::vector<std::vector<unsigned char>>();
|
||||
|
||||
std::vector<unsigned char> a1, a2;
|
||||
CKeyID id1 = dest.GetID();
|
||||
CKeyID id2 = ccAddress.GetID();
|
||||
a1 = std::vector<unsigned char>(id1.begin(), id1.end());
|
||||
a2 = std::vector<unsigned char>(id2.begin(), id2.end());
|
||||
CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION);
|
||||
|
||||
// version
|
||||
// utxo source hash
|
||||
// utxo source output
|
||||
// destination's pubkey
|
||||
CKeyID key = dest.GetID();
|
||||
vout.scriptPubKey << p.AsVector() << OP_DROP
|
||||
<< a1 << OP_DROP << a2 << OP_DROP
|
||||
<< std::vector<unsigned char>(stakeTx.vin[0].prevout.hash.begin(), stakeTx.vin[0].prevout.hash.end()) << OP_DROP
|
||||
<< stakeTx.vin[0].prevout.n << OP_DROP;
|
||||
hw << stakeTx.vin[0].prevout.hash;
|
||||
hw << stakeTx.vin[0].prevout.n;
|
||||
|
||||
uint256 utxo = hw.GetHash();
|
||||
vData.push_back(std::vector<unsigned char>(utxo.begin(), utxo.end()));
|
||||
|
||||
CStakeParams p;
|
||||
if (GetStakeParams(stakeTx, p))
|
||||
{
|
||||
// prev block hash and height is here to make validation easy
|
||||
vData.push_back(std::vector<unsigned char>(p.prevHash.begin(), p.prevHash.end()));
|
||||
std::vector<unsigned char> height = std::vector<unsigned char>(4);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
height[i] = (p.blkHeight >> (8 * i)) & 0xff;
|
||||
}
|
||||
|
||||
COptCCParams ccp = COptCCParams(COptCCParams::VERSION, EVAL_COINBASEGUARD, 1, 2, vPubKeys, vData);
|
||||
|
||||
vout.scriptPubKey << ccp.AsVector() << OP_DROP;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// this creates a spend using a stake transaction
|
||||
bool MakeGuardedSpend(CTxIn &vin, CPubKey &dest, CTransaction &pCheater)
|
||||
// validates if a stake transaction is both valid and cheating, defined by:
|
||||
// the same exact utxo source, a target block height of later than that of this tx that is also targeting a fork
|
||||
// of the chain. we know the transaction is a coinbase
|
||||
bool ValidateCheatingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx)
|
||||
{
|
||||
if (ccTx.IsCoinBase())
|
||||
{
|
||||
CStakeParams p;
|
||||
if (ValidateStakeTransaction(cheatTx, p))
|
||||
{
|
||||
std::vector<std::vector<unsigned char>> vParams = std::vector<std::vector<unsigned char>>();
|
||||
CScript dummy;
|
||||
|
||||
if (ccTx.vout[voutNum].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) && vParams.size() > 0)
|
||||
{
|
||||
COptCCParams ccp = COptCCParams(vParams[0]);
|
||||
if (ccp.IsValid() & ccp.vData.size() >= 3 && ccp.vData[2].size() <= 4)
|
||||
{
|
||||
CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION);
|
||||
|
||||
hw << cheatTx.vin[0].prevout.hash;
|
||||
hw << cheatTx.vin[0].prevout.n;
|
||||
uint256 utxo = hw.GetHash();
|
||||
uint256 hash = uint256(ccp.vData[1]);
|
||||
|
||||
uint32_t height = 0;
|
||||
for (int i = 0; i < ccp.vData[2].size(); i++)
|
||||
{
|
||||
height = height << 8 + ccp.vData[2][i];
|
||||
}
|
||||
|
||||
if (hash == uint256(ccp.vData[0]) && p.prevHash != hash && p.blkHeight >= height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// this attaches an opret to a mutable transaction that provides the necessary evidence of a signed, cheating stake transaction
|
||||
bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx)
|
||||
{
|
||||
CCcontract_info *cp,C;
|
||||
std::vector<unsigned char> vch;
|
||||
CDataStream s = CDataStream(SER_DISK, CLIENT_VERSION);
|
||||
|
||||
cp = CCinit(&C,EVAL_COINBASEGUARD);
|
||||
CC cc;
|
||||
vin.scriptSig = CCPubKey(MakeCCcond1of2(EVAL_COINBASEGUARD, dest, CPubKey(ParseHex(cp->CChexstr))));
|
||||
if (ValidateCheatingStake(ccTx, voutNum, cheatTx))
|
||||
{
|
||||
CTxOut vOut = CTxOut();
|
||||
|
||||
CScript vData = CScript();
|
||||
cheatTx.Serialize(s);
|
||||
vch = std::vector<unsigned char>(s.begin(), s.end());
|
||||
vData << OPRETTYPE_STAKECHEAT << vch;
|
||||
vOut.scriptPubKey << OP_RETURN << std::vector<unsigned char>(vData.begin(), vData.end());
|
||||
vOut.nValue = 0;
|
||||
mtx.vout.push_back(vOut);
|
||||
}
|
||||
}
|
||||
|
||||
bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn)
|
||||
@@ -209,7 +292,7 @@ bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransa
|
||||
// validate this spend of a transaction with it being past any applicable time lock and one of the following statements being true:
|
||||
// 1. the spend is signed by the original output destination's private key and normal payment requirements, spends as normal
|
||||
// 2. the spend is signed by the private key of the CoinbaseGuard contract and pushes a signed stake transaction
|
||||
// with the same exact utxo source, a target block height of later than that of this tx that is also targeting a fork
|
||||
// with the same exact utxo source, a target block height of later than or equal to this tx that is targeting a fork
|
||||
// of the chain
|
||||
|
||||
// first, check to see if the spending contract is signed by the default destination address
|
||||
@@ -218,12 +301,19 @@ bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransa
|
||||
// get preConditions and parameters
|
||||
std::vector<std::vector<unsigned char>> preConditions = std::vector<std::vector<unsigned char>>();
|
||||
std::vector<std::vector<unsigned char>> params = std::vector<std::vector<unsigned char>>();
|
||||
CTransaction txOut;
|
||||
|
||||
if (GetCCParams(eval, tx, nIn, preConditions, params))
|
||||
if (GetCCParams(eval, tx, nIn, txOut, preConditions, params))
|
||||
{
|
||||
CC *cc = GetCryptoCondition(tx.vin[nIn].scriptSig);
|
||||
|
||||
printf("CryptoCondition code %x\n", *cc->code);
|
||||
|
||||
if (preConditions.size() > 0 && params.size() > 0)
|
||||
{
|
||||
COptCCParams ccp = COptCCParams(preConditions[1]);
|
||||
}
|
||||
|
||||
// check any applicable time lock
|
||||
// determine who signed
|
||||
// if from receiver's priv key, success
|
||||
|
||||
@@ -41,9 +41,11 @@ class CStakeParams
|
||||
std::vector<unsigned char> AsVector()
|
||||
{
|
||||
std::vector<unsigned char> ret;
|
||||
CScript scr = CScript() << OPRETTYPE_STAKEPARAMS
|
||||
<< srcHeight << blkHeight
|
||||
<< std::vector<unsigned char>(prevHash.begin(), prevHash.end());
|
||||
CScript scr = CScript();
|
||||
scr << OPRETTYPE_STAKEPARAMS;
|
||||
scr << srcHeight;
|
||||
scr << blkHeight;
|
||||
scr << std::vector<unsigned char>(prevHash.begin(), prevHash.end());
|
||||
|
||||
if (pk.IsValid())
|
||||
{
|
||||
@@ -59,11 +61,15 @@ class CStakeParams
|
||||
|
||||
bool UnpackStakeOpRet(const CTransaction &stakeTx, std::vector<std::vector<unsigned char>> &vData);
|
||||
|
||||
bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams);
|
||||
|
||||
bool ValidateStakeTransaction(const CTransaction &stakeTx, CStakeParams &stakeParams);
|
||||
|
||||
bool ValidateCheatingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx);
|
||||
|
||||
bool MakeGuardedOutput(CAmount value, CPubKey &dest, CTransaction &stakeTx, CTxOut &vout);
|
||||
|
||||
bool MakeGuardedSpend(CTxIn &vin, CPubKey &dest, CTransaction &pCheater);
|
||||
bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx);
|
||||
|
||||
bool CoinbaseGuardValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#define OPRETTYPE_TIMELOCK 1
|
||||
#define OPRETTYPE_STAKEPARAMS 2
|
||||
#define OPRETTYPE_STAKECHEAT 3
|
||||
|
||||
static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
|
||||
|
||||
|
||||
@@ -19,6 +19,94 @@ typedef vector<unsigned char> valtype;
|
||||
|
||||
unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY;
|
||||
|
||||
COptCCParams::COptCCParams(std::vector<unsigned char> &vch)
|
||||
{
|
||||
CScript inScr = CScript(vch.begin(), vch.end());
|
||||
if (inScr.size() > 1)
|
||||
{
|
||||
CScript::const_iterator pc = inScr.begin();
|
||||
opcodetype opcode;
|
||||
std::vector<std::vector<unsigned char>> data;
|
||||
std::vector<unsigned char> param;
|
||||
bool valid = true;
|
||||
|
||||
while (pc < inScr.end())
|
||||
{
|
||||
param.clear();
|
||||
if (inScr.GetOp(pc, opcode, param))
|
||||
{
|
||||
if (opcode > 0 && opcode <= OP_PUSHDATA4 && param.size() > 0)
|
||||
{
|
||||
data.push_back(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid && pc == inScr.end() && data.size() > 0)
|
||||
{
|
||||
version = 0;
|
||||
param = data[0];
|
||||
if (param.size() == 4)
|
||||
{
|
||||
version = param[0];
|
||||
evalCode = param[1];
|
||||
n = param[2];
|
||||
m = param[3];
|
||||
if (version != VERSION || m != 1 || (n != 1 && n != 2) || data.size() <= n)
|
||||
{
|
||||
// we only support one version, and 1 of 1 or 1 of 2 now, so set invalid
|
||||
version = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// load keys and data
|
||||
vKeys.clear();
|
||||
vData.clear();
|
||||
int i;
|
||||
for (i = 1; i <= n; i++)
|
||||
{
|
||||
vKeys.push_back(CPubKey(data[i]));
|
||||
if (!vKeys[vKeys.size() - 1].IsValid())
|
||||
{
|
||||
version = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (version != 0)
|
||||
{
|
||||
// get the rest of the data
|
||||
for ( ; i < data.size(); i++)
|
||||
{
|
||||
vData.push_back(data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> COptCCParams::AsVector()
|
||||
{
|
||||
CScript cData = CScript();
|
||||
|
||||
cData << std::vector<unsigned char>({version, evalCode, n, m});
|
||||
for (auto k : vKeys)
|
||||
{
|
||||
cData << std::vector<unsigned char>(k.begin(), k.end());
|
||||
}
|
||||
for (auto d : vData)
|
||||
{
|
||||
cData << std::vector<unsigned char>(d);
|
||||
}
|
||||
return std::vector<unsigned char>(cData.begin(), cData.end());
|
||||
}
|
||||
|
||||
CScriptID::CScriptID(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {}
|
||||
|
||||
const char* GetTxnOutputType(txnouttype t)
|
||||
|
||||
@@ -91,34 +91,19 @@ class COptCCParams
|
||||
uint8_t version;
|
||||
uint8_t evalCode;
|
||||
uint8_t m, n; // for m of n sigs required, n pub keys for sigs will follow
|
||||
std::vector<CPubKey> vKeys; // n public keys to aid in signing
|
||||
std::vector<std::vector<unsigned char>> vData; // extra parameters
|
||||
|
||||
COptCCParams() : version(0), evalCode(0), n(0), m(0) {}
|
||||
|
||||
COptCCParams(uint8_t ver, uint8_t code, uint8_t _n, uint8_t _m) : version(ver), evalCode(code), n(_n), m(_m) {}
|
||||
COptCCParams(uint8_t ver, uint8_t code, uint8_t _n, uint8_t _m, std::vector<CPubKey> &vkeys, std::vector<std::vector<unsigned char>> &vdata) :
|
||||
version(ver), evalCode(code), n(_n), m(_m), vKeys(vkeys), vData(vdata) {}
|
||||
|
||||
COptCCParams(std::vector<unsigned char> &vch)
|
||||
{
|
||||
version = 0;
|
||||
if (vch.size() == 4)
|
||||
{
|
||||
version = vch[0];
|
||||
evalCode = vch[1];
|
||||
n = vch[2];
|
||||
m = vch[3];
|
||||
if (version != VERSION && m == 1 && (n == 1 || n == 2))
|
||||
{
|
||||
// we only support one version, and 1 of 1 or 1 of 2 now, so set invalid
|
||||
version = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
COptCCParams(std::vector<unsigned char> &vch);
|
||||
|
||||
bool IsValid() { return version != 0; }
|
||||
|
||||
std::vector<unsigned char> AsVector()
|
||||
{
|
||||
std::vector<unsigned char> vch = std::vector<unsigned char>({version, evalCode, n, m});
|
||||
}
|
||||
std::vector<unsigned char> AsVector();
|
||||
};
|
||||
|
||||
/** Check whether a CTxDestination is a CNoDestination. */
|
||||
|
||||
@@ -1306,7 +1306,7 @@ int32_t CWallet::VerusStakeTransaction(CBlock *pBlock, CMutableTransaction &txNe
|
||||
|
||||
// if we are staking with the extended format, add the opreturn data required
|
||||
// TODO: uncomment the line below to save a little space after testing, remove this one
|
||||
// if (extendedStake)
|
||||
if (extendedStake)
|
||||
{
|
||||
uint256 srcBlock = uint256();
|
||||
CBlockIndex *pSrcIndex;
|
||||
|
||||
Reference in New Issue
Block a user