Verus Coin - this coin was backdoored by it's lead dev and should not be trusted!
https://git.hush.is/duke/backdoors/src/branch/master/vrsc.md
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5832 lines
266 KiB
5832 lines
266 KiB
/********************************************************************
|
|
* (C) 2019 Michael Toutonghi
|
|
*
|
|
* Distributed under the MIT software license, see the accompanying
|
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
*
|
|
* This implements the public blockchains as a service (PBaaS) notarization protocol, VerusLink.
|
|
* VerusLink is a new distributed consensus protocol that enables multiple public blockchains
|
|
* to operate as a decentralized ecosystem of chains, which can interact and easily engage in cross
|
|
* chain transactions.
|
|
*
|
|
*/
|
|
|
|
#include <univalue.h>
|
|
#include "main.h"
|
|
#include "txdb.h"
|
|
#include "rpc/pbaasrpc.h"
|
|
#include "transaction_builder.h"
|
|
|
|
#include <assert.h>
|
|
|
|
using namespace std;
|
|
|
|
extern uint160 VERUS_CHAINID;
|
|
extern uint160 ASSETCHAINS_CHAINID;
|
|
extern string VERUS_CHAINNAME;
|
|
extern string PBAAS_HOST;
|
|
extern string PBAAS_USERPASS;
|
|
extern int32_t PBAAS_PORT;
|
|
extern int32_t VERUS_MIN_STAKEAGE;
|
|
|
|
CNotaryEvidence::CNotaryEvidence(const UniValue &uni)
|
|
{
|
|
version = uni_get_int(find_value(uni, "version"), VERSION_CURRENT);
|
|
type = uni_get_int(find_value(uni, "type"));
|
|
systemID = GetDestinationID(DecodeDestination(uni_get_str(find_value(uni, "systemid"))));
|
|
output = CUTXORef(find_value(uni, "output"));
|
|
state = uni_get_int(find_value(uni, "state"));
|
|
evidence = CCrossChainProof(find_value(uni, "evidence"));
|
|
}
|
|
|
|
std::vector<CNotarySignature> CNotaryEvidence::GetConfirmedAndRejectedSignatureMaps(
|
|
const std::vector<CNotarySignature> &allSigVec,
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> &confirmedByHeight,
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> &rejectedByHeight)
|
|
{
|
|
CUTXORef output;
|
|
uint160 systemID;
|
|
for (auto notarySig : allSigVec)
|
|
{
|
|
if (output.hash.IsNull())
|
|
{
|
|
output = notarySig.output;
|
|
systemID = notarySig.systemID;
|
|
}
|
|
if (notarySig.IsConfirmed())
|
|
{
|
|
// get the signature proof and add to it, do not make a new one
|
|
for (auto &oneSignature : notarySig.signatures)
|
|
{
|
|
if (confirmedByHeight[oneSignature.second.blockHeight].count(oneSignature.first))
|
|
{
|
|
// all signatures are the same height, merge the CIdentitySignature
|
|
for (auto &oneKeySig : oneSignature.second.signatures)
|
|
{
|
|
confirmedByHeight[oneSignature.second.blockHeight][oneSignature.first].AddSignature(oneKeySig);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
confirmedByHeight[oneSignature.second.blockHeight].insert(oneSignature);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// get the signature proof and add to it, do not make a new one
|
|
for (auto &oneSignature : notarySig.signatures)
|
|
{
|
|
if (rejectedByHeight[oneSignature.second.blockHeight].count(oneSignature.first))
|
|
{
|
|
// all signatures are the same height, merge the CIdentitySignature
|
|
for (auto &oneKeySig : oneSignature.second.signatures)
|
|
{
|
|
rejectedByHeight[oneSignature.second.blockHeight][oneSignature.first].AddSignature(oneKeySig);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rejectedByHeight[oneSignature.second.blockHeight].insert(oneSignature);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<CNotarySignature> retVal;
|
|
|
|
std::multimap<uint32_t, CNotarySignature> combinedSignatures;
|
|
for (auto oneHeightEntry : rejectedByHeight)
|
|
{
|
|
combinedSignatures.insert(std::make_pair(oneHeightEntry.first, CNotarySignature(systemID, output, false, oneHeightEntry.second)));
|
|
}
|
|
for (auto oneHeightEntry : confirmedByHeight)
|
|
{
|
|
combinedSignatures.insert(std::make_pair(oneHeightEntry.first, CNotarySignature(systemID, output, true, oneHeightEntry.second)));
|
|
}
|
|
// place all signature blocks at the beginning of the evidence in order of height by inserting them at 0 in reverse order
|
|
for (auto it = combinedSignatures.rbegin(); it != combinedSignatures.rend(); it++)
|
|
{
|
|
retVal.push_back(it->second);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CNotaryEvidence &CNotaryEvidence::MergeEvidence(const CNotaryEvidence &mergeWith, const std::set<uint160> ¬arySet, bool aggregateSignatures)
|
|
{
|
|
std::vector<CNotarySignature> notarySignatures;
|
|
|
|
if (output.IsNull() && !mergeWith.output.IsNull())
|
|
{
|
|
output = mergeWith.output;
|
|
}
|
|
if (systemID.IsNull() && !mergeWith.systemID.IsNull())
|
|
{
|
|
systemID = mergeWith.systemID;
|
|
}
|
|
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> confirmedByHeight;
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> rejectedByHeight;
|
|
|
|
for (auto oneRef : mergeWith.evidence.chainObjects)
|
|
{
|
|
if (aggregateSignatures && oneRef->objectType == CHAINOBJ_NOTARYSIGNATURE)
|
|
{
|
|
notarySignatures.push_back(((CChainObject<CNotarySignature> *)oneRef)->object);
|
|
}
|
|
else
|
|
{
|
|
evidence << oneRef;
|
|
}
|
|
}
|
|
|
|
if (notarySignatures.size())
|
|
{
|
|
auto orderedNotarySigs = GetConfirmedAndRejectedSignatureMaps(notarySignatures, confirmedByHeight, rejectedByHeight);
|
|
|
|
// place all signature blocks at the beginning of the evidence in order of height by inserting them at 0 in reverse order
|
|
for (auto it = orderedNotarySigs.rbegin(); it != orderedNotarySigs.rend(); it++)
|
|
{
|
|
evidence.insert(0, *it);
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
CIdentitySignature::ESignatureVerification CNotaryEvidence::SignConfirmed(const std::set<uint160> ¬arySet, int minConfirming, const CKeyStore &keyStore, const CTransaction &txToConfirm, const CIdentityID &signWithID, uint32_t height, CCurrencyDefinition::EProofProtocol hashType)
|
|
{
|
|
if (!notarySet.count(signWithID))
|
|
{
|
|
LogPrintf("%s: Attempting to sign as notary with unauthorized ID\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
COptCCParams p;
|
|
|
|
if (txToConfirm.GetHash() != output.hash ||
|
|
txToConfirm.vout.size() <= output.n ||
|
|
!txToConfirm.vout[output.n].scriptPubKey.IsPayToCryptoCondition(p) ||
|
|
!p.vData.size() ||
|
|
!p.vData[0].size() ||
|
|
p.evalCode == EVAL_NONE)
|
|
{
|
|
LogPrintf("%s: Attempting to sign an invalid or incompatible object\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
// write the object to the hash writer without a vector length prefix
|
|
CNativeHashWriter hw(hashType);
|
|
uint256 objHash = hw.write((const char *)&(p.vData[0][0]), p.vData[0].size()).GetHash();
|
|
|
|
uint32_t decisionHeight;
|
|
std::map<CIdentityID, CIdentitySignature> confirmedAtHeight;
|
|
std::map<CIdentityID, CIdentitySignature> rejectedAtHeight;
|
|
|
|
CNotaryEvidence::EStates sigCheckResult = CheckSignatureConfirmation(objHash,
|
|
notarySet,
|
|
minConfirming,
|
|
height,
|
|
&decisionHeight,
|
|
&confirmedAtHeight,
|
|
&rejectedAtHeight);
|
|
|
|
if (sigCheckResult == CNotaryEvidence::EStates::STATE_CONFIRMED || sigCheckResult == CNotaryEvidence::EStates::STATE_REJECTED)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_COMPLETE;
|
|
}
|
|
else if (sigCheckResult == CNotaryEvidence::EStates::STATE_INVALID)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID;
|
|
}
|
|
|
|
// sign for anything we can that is not already in the confirmedAtHeight signature block if decisionHeight is our height
|
|
int currentNumSigs = (decisionHeight == height && confirmedAtHeight.count(signWithID)) ? confirmedAtHeight[signWithID].signatures.size() : 0;
|
|
|
|
// all signatures present for this ID are correct, so we recover all pub keys and IDs,
|
|
// then see if we have any of the keys in our wallet that are in the ID and have not already
|
|
// signed
|
|
CIdentity sigIdentity = CIdentity::LookupIdentity(signWithID, height);
|
|
if (!sigIdentity.IsValid())
|
|
{
|
|
LogPrint("notarization", "%s: failed lookup of identity for rejection: %s\n", __func__, EncodeDestination(signWithID).c_str());
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID;
|
|
}
|
|
else
|
|
{
|
|
int sigsNeeded = std::max(sigIdentity.minSigs - currentNumSigs, 0);
|
|
if (!sigsNeeded)
|
|
{
|
|
LogPrint("notarization", "%s: Already signed with ID\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_COMPLETE;
|
|
}
|
|
|
|
CIdentitySignature idSignature(height, std::set<std::vector<unsigned char>>(), hashType);
|
|
|
|
uint256 signatureHash = idSignature.IdentitySignatureHash(std::vector<uint160>({NotaryConfirmedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
height,
|
|
signWithID,
|
|
"",
|
|
objHash);
|
|
|
|
std::set<CTxDestination> validKeys;
|
|
|
|
for (auto &oneDest : sigIdentity.primaryAddresses)
|
|
{
|
|
if (oneDest.which() == COptCCParams::ADDRTYPE_PK)
|
|
{
|
|
validKeys.insert(CKeyID(GetDestinationID(oneDest)));
|
|
}
|
|
else
|
|
{
|
|
validKeys.insert(oneDest);
|
|
}
|
|
}
|
|
|
|
if (currentNumSigs)
|
|
{
|
|
for (auto &oneSig : confirmedAtHeight[signWithID].signatures)
|
|
{
|
|
CPubKey checkKey;
|
|
checkKey.RecoverCompact(signatureHash, oneSig);
|
|
validKeys.erase(checkKey.GetID());
|
|
}
|
|
}
|
|
|
|
// as long as we can continue to make new signatures, we do
|
|
for (auto keyIT = validKeys.begin(); sigsNeeded > 0 && keyIT != validKeys.end(); keyIT++)
|
|
{
|
|
CKey signingKey;
|
|
std::vector<unsigned char> newSig;
|
|
if (keyStore.GetKey(GetDestinationID(*keyIT), signingKey) && signingKey.SignCompact(signatureHash, newSig))
|
|
{
|
|
idSignature.signatures.insert(newSig);
|
|
sigsNeeded--;
|
|
}
|
|
}
|
|
|
|
if (idSignature.signatures.size())
|
|
{
|
|
AddToSignatures(notarySet, signWithID, idSignature, STATE_CONFIRMING);
|
|
}
|
|
|
|
CIdentitySignature::ESignatureVerification sigResult = idSignature.CheckSignature(sigIdentity,
|
|
std::vector<uint160>({NotaryConfirmedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
"",
|
|
objHash);
|
|
|
|
if (sigResult == CIdentitySignature::ESignatureVerification::SIGNATURE_EMPTY ||
|
|
sigResult == CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID)
|
|
{
|
|
LogPrintf("%s: Error signing confirmed with ID: %s\n", __func__, sigIdentity.ToUniValue().write(1,2).c_str());
|
|
return sigResult;
|
|
}
|
|
// if we have enough signatures for the ID, make sure we return complete
|
|
if (sigsNeeded == 0)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_COMPLETE;
|
|
}
|
|
return sigResult;
|
|
}
|
|
}
|
|
|
|
CIdentitySignature::ESignatureVerification CNotaryEvidence::SignRejected(const std::set<uint160> ¬arySet,
|
|
int minConfirming,
|
|
const CKeyStore &keyStore,
|
|
const CTransaction &txToConfirm,
|
|
const CIdentityID &signWithID,
|
|
uint32_t height,
|
|
CCurrencyDefinition::EProofProtocol hashType)
|
|
{
|
|
if (!notarySet.count(signWithID))
|
|
{
|
|
LogPrintf("%s: Attempting to sign as notary with unauthorized ID\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
COptCCParams p;
|
|
|
|
if (txToConfirm.GetHash() != output.hash ||
|
|
txToConfirm.vout.size() <= output.n ||
|
|
!txToConfirm.vout[output.n].scriptPubKey.IsPayToCryptoCondition(p) ||
|
|
!p.vData.size() ||
|
|
!p.vData[0].size() ||
|
|
p.evalCode == EVAL_NONE)
|
|
{
|
|
LogPrintf("%s: Attempting to sign an invalid or incompatible object\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
// write the object to the hash writer without a vector length prefix
|
|
CNativeHashWriter hw(hashType);
|
|
std::map<CIdentityID, CIdentitySignature> confirmedAtHeight;
|
|
std::map<CIdentityID, CIdentitySignature> rejectedAtHeight;
|
|
uint256 objHash = hw.write((const char *)&(p.vData[0][0]), p.vData[0].size()).GetHash();
|
|
|
|
uint32_t decisionHeight;
|
|
|
|
CNotaryEvidence::EStates sigCheckResult = CheckSignatureConfirmation(objHash,
|
|
notarySet,
|
|
minConfirming,
|
|
height,
|
|
&decisionHeight,
|
|
&confirmedAtHeight,
|
|
&rejectedAtHeight);
|
|
|
|
if ((sigCheckResult == CNotaryEvidence::EStates::STATE_CONFIRMED && decisionHeight != height) ||
|
|
sigCheckResult == CNotaryEvidence::EStates::STATE_REJECTED)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_COMPLETE;
|
|
}
|
|
else if (sigCheckResult == CNotaryEvidence::EStates::STATE_INVALID)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID;
|
|
}
|
|
|
|
// sign for anything we can that is not already in the rejectedAtHeight signature block if decisionHeight is our height
|
|
int currentNumSigs = (decisionHeight == height && rejectedAtHeight.count(signWithID)) ? rejectedAtHeight[signWithID].signatures.size() : 0;
|
|
|
|
// all signatures present for this ID are correct, so we recover all pub keys and IDs,
|
|
// then see if we have any of the keys in our wallet that are in the ID and have not already
|
|
// signed
|
|
CIdentity sigIdentity = CIdentity::LookupIdentity(signWithID, height);
|
|
if (!sigIdentity.IsValid())
|
|
{
|
|
LogPrint("notarization", "%s: failed lookup of identity for rejection: %s\n", __func__, EncodeDestination(signWithID).c_str());
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID;
|
|
}
|
|
else
|
|
{
|
|
int sigsNeeded = std::max(sigIdentity.minSigs - currentNumSigs, 0);
|
|
if (!sigsNeeded)
|
|
{
|
|
LogPrint("notarization", "%s: Already signed with ID\n", __func__);
|
|
return CIdentitySignature::SIGNATURE_COMPLETE;
|
|
}
|
|
|
|
CIdentitySignature idSignature(height, std::set<std::vector<unsigned char>>(), hashType);
|
|
|
|
uint256 signatureHash = idSignature.IdentitySignatureHash(std::vector<uint160>({NotaryRejectedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
height,
|
|
signWithID,
|
|
"",
|
|
objHash);
|
|
|
|
std::set<CTxDestination> validKeys;
|
|
|
|
for (auto &oneDest : sigIdentity.primaryAddresses)
|
|
{
|
|
if (oneDest.which() == COptCCParams::ADDRTYPE_PK)
|
|
{
|
|
validKeys.insert(CKeyID(GetDestinationID(oneDest)));
|
|
}
|
|
else
|
|
{
|
|
validKeys.insert(oneDest);
|
|
}
|
|
}
|
|
|
|
if (currentNumSigs)
|
|
{
|
|
for (auto &oneSig : rejectedAtHeight[signWithID].signatures)
|
|
{
|
|
CPubKey checkKey;
|
|
checkKey.RecoverCompact(signatureHash, oneSig);
|
|
validKeys.erase(checkKey.GetID());
|
|
}
|
|
}
|
|
|
|
// as long as we can continue to make new signatures, we do
|
|
for (auto keyIT = validKeys.begin(); sigsNeeded > 0 && keyIT != validKeys.end(); keyIT++)
|
|
{
|
|
CKey signingKey;
|
|
std::vector<unsigned char> newSig;
|
|
if (keyStore.GetKey(GetDestinationID(*keyIT), signingKey) && signingKey.SignCompact(signatureHash, newSig))
|
|
{
|
|
idSignature.signatures.insert(newSig);
|
|
sigsNeeded--;
|
|
}
|
|
}
|
|
|
|
if (idSignature.signatures.size())
|
|
{
|
|
AddToSignatures(notarySet, signWithID, idSignature, STATE_REJECTING);
|
|
}
|
|
|
|
CIdentitySignature::ESignatureVerification sigResult = idSignature.CheckSignature(sigIdentity,
|
|
std::vector<uint160>({NotaryRejectedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
"",
|
|
objHash);
|
|
|
|
if (sigResult == CIdentitySignature::ESignatureVerification::SIGNATURE_EMPTY ||
|
|
sigResult == CIdentitySignature::ESignatureVerification::SIGNATURE_INVALID)
|
|
{
|
|
LogPrintf("%s: Error signing rejected with ID: %s\n", __func__, sigIdentity.ToUniValue().write(1,2).c_str());
|
|
return sigResult;
|
|
}
|
|
// if we have enough signatures for the ID, make sure we return complete
|
|
if (sigsNeeded == 0)
|
|
{
|
|
return CIdentitySignature::ESignatureVerification::SIGNATURE_COMPLETE;
|
|
}
|
|
return sigResult;
|
|
}
|
|
}
|
|
|
|
CNotaryEvidence::EStates CNotaryEvidence::CheckSignatureConfirmation(const uint256 &objHash,
|
|
const std::set<uint160> ¬arySet,
|
|
int minConfirming,
|
|
uint32_t checkHeight,
|
|
uint32_t *pDecisionHeight,
|
|
std::map<CIdentityID, CIdentitySignature> *pConfirmedAtHeight,
|
|
std::map<CIdentityID, CIdentitySignature> *pRejectedAtHeight) const
|
|
{
|
|
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> confirmedByHeight;
|
|
std::map<uint32_t, std::map<CIdentityID, CIdentitySignature>> rejectedByHeight;
|
|
std::vector<CNotarySignature> notarySignatures = GetNotarySignatures(&confirmedByHeight, &rejectedByHeight);
|
|
|
|
CNotaryEvidence::EStates retVal = CNotaryEvidence::EStates::STATE_CONFIRMING;
|
|
|
|
if (!checkHeight)
|
|
{
|
|
checkHeight = UINT32_MAX;
|
|
}
|
|
|
|
// if we have full rejection from any IDs in any period, that is considered rejection and that ID is blacklisted from accepting
|
|
// if we have full confirmation in the most recent period, that is considered confirmation
|
|
|
|
// for every height, we check and merge
|
|
uint32_t lastHeight = 0;
|
|
std::map<CIdentityID, std::set<std::vector<unsigned char>>> notarySetRejects;
|
|
std::map<CIdentityID, std::set<std::vector<unsigned char>>> notarySetConfirms;
|
|
|
|
for (auto &oneSigBlock : notarySignatures)
|
|
{
|
|
// we only care up to signing height
|
|
uint32_t height = oneSigBlock.signatures.size() ? oneSigBlock.signatures.begin()->second.blockHeight : 0;
|
|
if (!height)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if this was signed on this chain, it isn't valid if signed after the check height
|
|
if (oneSigBlock.systemID == ASSETCHAINS_CHAINID && height > checkHeight)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (height != lastHeight)
|
|
{
|
|
notarySetRejects.clear();
|
|
notarySetConfirms.clear();
|
|
}
|
|
|
|
if (oneSigBlock.IsConfirmed())
|
|
{
|
|
for (auto &oneIDSig : oneSigBlock.signatures)
|
|
{
|
|
if (!notarySet.count(oneIDSig.first))
|
|
{
|
|
LogPrint("notarization", "%s: unauthorized notary identity: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
|
|
CIdentity sigIdentity = CIdentity::LookupIdentity(oneIDSig.first, oneSigBlock.systemID == ASSETCHAINS_CHAINID ?
|
|
oneIDSig.second.blockHeight :
|
|
checkHeight);
|
|
if (!sigIdentity.IsValid())
|
|
{
|
|
LogPrint("notarization", "%s: invalid notary identity: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
|
|
// we cannot confirm based on the signature of a revoked identity, but that state does not veto either
|
|
if (sigIdentity.IsRevoked())
|
|
{
|
|
confirmedByHeight[oneIDSig.second.blockHeight].erase(oneIDSig.first);
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::vector<unsigned char>> dupSigs;
|
|
CIdentitySignature::ESignatureVerification result = oneIDSig.second.CheckSignature(sigIdentity,
|
|
std::vector<uint160>({NotaryConfirmedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
"",
|
|
objHash,
|
|
&dupSigs);
|
|
|
|
for (auto &oneDup : dupSigs)
|
|
{
|
|
rejectedByHeight[oneIDSig.second.blockHeight][oneIDSig.first].signatures.erase(oneDup);
|
|
}
|
|
|
|
if (result == oneIDSig.second.SIGNATURE_INVALID)
|
|
{
|
|
LogPrint("notarization", "%s: invalid notary signature: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
else if (result != oneIDSig.second.SIGNATURE_COMPLETE)
|
|
{
|
|
continue;
|
|
}
|
|
notarySetConfirms[oneIDSig.first] = oneIDSig.second.signatures;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneIDSig : oneSigBlock.signatures)
|
|
{
|
|
if (!notarySet.count(oneIDSig.first))
|
|
{
|
|
LogPrint("notarization", "%s: unauthorized notary identity for rejection: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
|
|
CIdentity sigIdentity = CIdentity::LookupIdentity(oneIDSig.first, oneIDSig.second.blockHeight);
|
|
if (!sigIdentity.IsValid())
|
|
{
|
|
LogPrint("notarization", "%s: invalid notary identity for rejection: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
|
|
// we cannot reject based on the signature of a revoked identity, but that state does not veto either
|
|
if (sigIdentity.IsRevoked())
|
|
{
|
|
rejectedByHeight[oneIDSig.second.blockHeight].erase(oneIDSig.first);
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::vector<unsigned char>> dupSigs;
|
|
CIdentitySignature::ESignatureVerification result = oneIDSig.second.CheckSignature(sigIdentity,
|
|
std::vector<uint160>({NotaryRejectedKey()}),
|
|
std::vector<uint256>(),
|
|
systemID,
|
|
"",
|
|
objHash,
|
|
&dupSigs);
|
|
|
|
for (auto &oneDup : dupSigs)
|
|
{
|
|
rejectedByHeight[oneIDSig.second.blockHeight][oneIDSig.first].signatures.erase(oneDup);
|
|
}
|
|
|
|
if (result == oneIDSig.second.SIGNATURE_INVALID)
|
|
{
|
|
LogPrint("notarization", "%s: invalid notary signature for rejection: %s\n", __func__, EncodeDestination(oneIDSig.first).c_str());
|
|
return EStates::STATE_INVALID;
|
|
}
|
|
else if (result != oneIDSig.second.SIGNATURE_COMPLETE)
|
|
{
|
|
continue;
|
|
}
|
|
notarySetRejects.insert(std::make_pair(oneIDSig.first, oneIDSig.second.signatures));
|
|
}
|
|
}
|
|
|
|
lastHeight = height;
|
|
|
|
if (notarySetRejects.size() >= minConfirming)
|
|
{
|
|
retVal = EStates::STATE_REJECTED;
|
|
break;
|
|
}
|
|
else if (notarySetConfirms.size() >= minConfirming)
|
|
{
|
|
retVal = EStates::STATE_CONFIRMED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pDecisionHeight)
|
|
{
|
|
*pDecisionHeight = lastHeight;
|
|
}
|
|
if (pConfirmedAtHeight && lastHeight && confirmedByHeight.count(lastHeight))
|
|
{
|
|
*pConfirmedAtHeight = confirmedByHeight[lastHeight];
|
|
}
|
|
if (pRejectedAtHeight && lastHeight && rejectedByHeight.count(lastHeight))
|
|
{
|
|
*pRejectedAtHeight = rejectedByHeight[lastHeight];
|
|
}
|
|
|
|
if (retVal != EStates::STATE_CONFIRMED && retVal != EStates::STATE_REJECTED)
|
|
{
|
|
if (lastHeight == checkHeight &&
|
|
notarySetConfirms.size() >= notarySetRejects.size())
|
|
{
|
|
retVal = EStates::STATE_CONFIRMING;
|
|
}
|
|
else
|
|
{
|
|
retVal = EStates::STATE_REJECTING;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CPBaaSNotarization::CPBaaSNotarization(const CScript &scriptPubKey) :
|
|
nVersion(VERSION_INVALID),
|
|
flags(0),
|
|
notarizationHeight(0),
|
|
prevHeight(0)
|
|
{
|
|
COptCCParams p;
|
|
if (scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size())
|
|
{
|
|
::FromVector(p.vData[0], *this);
|
|
}
|
|
}
|
|
|
|
CPBaaSNotarization::CPBaaSNotarization(const CTransaction &tx, int32_t *pOutIdx) :
|
|
nVersion(VERSION_INVALID),
|
|
flags(0),
|
|
notarizationHeight(0),
|
|
prevHeight(0)
|
|
{
|
|
// the PBaaS notarization itself is a combination of proper inputs, one output, and
|
|
// a sequence of opret chain objects as proof of the output values on the chain to which the
|
|
// notarization refers, the opret can be reconstructed from chain data in order to validate
|
|
// the txid of a transaction that does not contain the opret itself
|
|
|
|
int32_t _outIdx;
|
|
int32_t &outIdx = pOutIdx ? *pOutIdx : _outIdx;
|
|
|
|
// a notarization must have notarization output that spends to the address indicated by the
|
|
// ChainID, an opret, that there is only one, and that it can be properly decoded to a notarization
|
|
// output, whether or not validate is true
|
|
bool found = false;
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size())
|
|
{
|
|
if (found)
|
|
{
|
|
nVersion = VERSION_INVALID;
|
|
proofRoots.clear();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
outIdx = i;
|
|
::FromVector(p.vData[0], *this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint160 ValidateCurrencyName(std::string currencyStr, bool ensureCurrencyValid=false, CCurrencyDefinition *pCurrencyDef=NULL);
|
|
|
|
CPBaaSNotarization::CPBaaSNotarization(const UniValue &obj)
|
|
{
|
|
nVersion = (uint32_t)uni_get_int64(find_value(obj, "version"));
|
|
flags = FLAGS_NONE;
|
|
SetDefinitionNotarization(uni_get_bool(find_value(obj, "isdefinition")));
|
|
SetBlockOneNotarization(uni_get_bool(find_value(obj, "isblockonenotarization")));
|
|
SetPreLaunch(uni_get_bool(find_value(obj, "prelaunch")));
|
|
SetLaunchCleared(uni_get_bool(find_value(obj, "launchcleared")));
|
|
SetRefunding(uni_get_bool(find_value(obj, "refunding")));
|
|
SetLaunchConfirmed(uni_get_bool(find_value(obj, "launchconfirmed")));
|
|
SetLaunchComplete(uni_get_bool(find_value(obj, "launchcomplete")));
|
|
if (uni_get_bool(find_value(obj, "ismirror")))
|
|
{
|
|
flags |= FLAG_ACCEPTED_MIRROR;
|
|
}
|
|
SetSameChain(uni_get_bool(find_value(obj, "samechain")));
|
|
|
|
currencyID = ValidateCurrencyName(uni_get_str(find_value(obj, "currencyid")));
|
|
if (currencyID.IsNull())
|
|
{
|
|
nVersion = VERSION_INVALID;
|
|
return;
|
|
}
|
|
|
|
UniValue transferID = find_value(obj, "proposer");
|
|
if (transferID.isObject())
|
|
{
|
|
proposer = CTransferDestination(transferID);
|
|
}
|
|
|
|
notarizationHeight = (uint32_t)uni_get_int64(find_value(obj, "notarizationheight"));
|
|
currencyState = CCoinbaseCurrencyState(find_value(obj, "currencystate"));
|
|
prevNotarization = CUTXORef(uint256S(uni_get_str(find_value(obj, "prevnotarizationtxid"))), (uint32_t)uni_get_int(find_value(obj, "prevnotarizationout")));
|
|
hashPrevCrossNotarization = uint256S(uni_get_str(find_value(obj, "hashprevcrossnotarization")));
|
|
prevHeight = (uint32_t)uni_get_int64(find_value(obj, "prevheight"));
|
|
|
|
auto curStateArr = find_value(obj, "currencystates");
|
|
auto proofRootArr = find_value(obj, "proofroots");
|
|
auto nodesUni = find_value(obj, "nodes");
|
|
|
|
if (curStateArr.isArray())
|
|
{
|
|
for (int i = 0; i < curStateArr.size(); i++)
|
|
{
|
|
std::vector<std::string> keys = curStateArr[i].getKeys();
|
|
std::vector<UniValue> values = curStateArr[i].getValues();
|
|
if (keys.size() != 1 or values.size() != 1)
|
|
{
|
|
nVersion = VERSION_INVALID;
|
|
return;
|
|
}
|
|
currencyStates.insert(std::make_pair(GetDestinationID(DecodeDestination(keys[0])), CCoinbaseCurrencyState(values[0])));
|
|
}
|
|
}
|
|
|
|
if (proofRootArr.isArray())
|
|
{
|
|
for (int i = 0; i < proofRootArr.size(); i++)
|
|
{
|
|
CProofRoot oneRoot(proofRootArr[i]);
|
|
if (!oneRoot.IsValid())
|
|
{
|
|
nVersion = VERSION_INVALID;
|
|
return;
|
|
}
|
|
proofRoots.insert(std::make_pair(oneRoot.systemID, oneRoot));
|
|
}
|
|
}
|
|
|
|
if (nodesUni.isArray())
|
|
{
|
|
vector<UniValue> nodeVec = nodesUni.getValues();
|
|
for (auto node : nodeVec)
|
|
{
|
|
nodes.push_back(CNodeData(uni_get_str(find_value(node, "networkaddress")), uni_get_str(find_value(node, "nodeidentity"))));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool operator==(const CProofRoot &op1, const CProofRoot &op2)
|
|
{
|
|
return op1.version == op2.version &&
|
|
op1.type == op2.type &&
|
|
op1.rootHeight == op2.rootHeight &&
|
|
op1.stateRoot == op2.stateRoot &&
|
|
op1.systemID == op2.systemID &&
|
|
op1.blockHash == op2.blockHash &&
|
|
op1.compactPower == op2.compactPower;
|
|
}
|
|
|
|
bool operator!=(const CProofRoot &op1, const CProofRoot &op2)
|
|
{
|
|
return !(op1 == op2);
|
|
}
|
|
|
|
CProofRoot CProofRoot::GetProofRoot(uint32_t blockHeight)
|
|
{
|
|
if (blockHeight > chainActive.Height())
|
|
{
|
|
return CProofRoot();
|
|
}
|
|
auto mmv = chainActive.GetMMV();
|
|
mmv.resize(blockHeight + 1);
|
|
return CProofRoot(ASSETCHAINS_CHAINID,
|
|
blockHeight,
|
|
mmv.GetRoot(),
|
|
chainActive[blockHeight]->GetBlockHash(),
|
|
chainActive[blockHeight]->chainPower.CompactChainPower());
|
|
}
|
|
|
|
CNotaryEvidence::CNotaryEvidence(const CTransaction &tx, int outputNum, int &afterEvidence, uint8_t EvidenceType) :
|
|
type(EvidenceType), version(CNotaryEvidence::VERSION_INVALID)
|
|
{
|
|
afterEvidence = outputNum;
|
|
|
|
COptCCParams p;
|
|
|
|
if (afterEvidence >= 0 &&
|
|
tx.vout.size() > afterEvidence &&
|
|
tx.vout[afterEvidence++].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
p.vData.size() &&
|
|
(*this = CNotaryEvidence(p.vData[0])).IsValid())
|
|
{
|
|
if (IsMultipartProof())
|
|
{
|
|
COptCCParams eP;
|
|
CNotaryEvidence supplementalEvidence;
|
|
std::vector<CNotaryEvidence> partsVector({*this});
|
|
while (tx.vout.size() > afterEvidence &&
|
|
tx.vout[afterEvidence++].scriptPubKey.IsPayToCryptoCondition(eP) &&
|
|
eP.IsValid() &&
|
|
eP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
eP.vData.size() &&
|
|
(supplementalEvidence = CNotaryEvidence(eP.vData[0])).IsValid() &&
|
|
supplementalEvidence.IsMultipartProof() &&
|
|
supplementalEvidence.evidence.chainObjects.size() == 1)
|
|
{
|
|
partsVector.push_back(supplementalEvidence);
|
|
}
|
|
*this = CNotaryEvidence(partsVector);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPBaaSNotarization::GetLastNotarization(const uint160 ¤cyID,
|
|
int32_t startHeight,
|
|
int32_t endHeight,
|
|
uint256 *txIDOut,
|
|
CTransaction *txOut)
|
|
{
|
|
CPBaaSNotarization notarization;
|
|
std::vector<CAddressIndexDbEntry> notarizationIndex;
|
|
// get the last notarization in the indicated height for this currency, which is valid by definition for a token
|
|
if (GetAddressIndex(CCrossChainRPCData::GetConditionID(currencyID, CPBaaSNotarization::NotaryNotarizationKey()), CScript::P2IDX, notarizationIndex, startHeight, endHeight))
|
|
{
|
|
// filter out all transactions that do not spend from the notarization thread, or originate as the
|
|
// chain definition
|
|
for (auto it = notarizationIndex.rbegin(); it != notarizationIndex.rend(); it++)
|
|
{
|
|
// first unspent notarization that is valid is the one we want, skip spending
|
|
if (it->first.spending)
|
|
{
|
|
continue;
|
|
}
|
|
LOCK(mempool.cs);
|
|
CTransaction oneTx;
|
|
uint256 blkHash;
|
|
if (myGetTransaction(it->first.txhash, oneTx, blkHash))
|
|
{
|
|
if ((notarization = CPBaaSNotarization(oneTx.vout[it->first.index].scriptPubKey)).IsValid())
|
|
{
|
|
*this = notarization;
|
|
if (txIDOut)
|
|
{
|
|
*txIDOut = it->first.txhash;
|
|
}
|
|
if (txOut)
|
|
{
|
|
*txOut = oneTx;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: error transaction %s not found, may need reindexing\n", __func__, it->first.txhash.GetHex().c_str());
|
|
printf("%s: error transaction %s not found, may need reindexing\n", __func__, it->first.txhash.GetHex().c_str());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return notarization.IsValid();
|
|
}
|
|
|
|
bool CPBaaSNotarization::GetLastUnspentNotarization(const uint160 ¤cyID,
|
|
uint256 &txIDOut,
|
|
int32_t &txOutNum,
|
|
CTransaction *txOut)
|
|
{
|
|
CPBaaSNotarization notarization;
|
|
std::vector<CAddressUnspentDbEntry> notarizationIndex;
|
|
// get the last notarization in the indicated height for this currency, which is valid by definition for a token
|
|
if (GetAddressUnspent(CCrossChainRPCData::GetConditionID(currencyID, CPBaaSNotarization::NotaryNotarizationKey()), CScript::P2IDX, notarizationIndex))
|
|
{
|
|
// first valid, unspent notarization found is the one we return
|
|
for (auto it = notarizationIndex.rbegin(); it != notarizationIndex.rend(); it++)
|
|
{
|
|
LOCK(mempool.cs);
|
|
CTransaction oneTx;
|
|
uint256 blkHash;
|
|
if (myGetTransaction(it->first.txhash, oneTx, blkHash))
|
|
{
|
|
if ((notarization = CPBaaSNotarization(oneTx.vout[it->first.index].scriptPubKey)).IsValid())
|
|
{
|
|
*this = notarization;
|
|
txIDOut = it->first.txhash;
|
|
txOutNum = it->first.index;
|
|
if (txOut)
|
|
{
|
|
*txOut = oneTx;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: error transaction %s not found, may need reindexing\n", __func__, it->first.txhash.GetHex().c_str());
|
|
printf("%s: error transaction %s not found, may need reindexing\n", __func__, it->first.txhash.GetHex().c_str());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return notarization.IsValid();
|
|
}
|
|
|
|
bool CPBaaSNotarization::NextNotarizationInfo(const CCurrencyDefinition &sourceSystem,
|
|
const CCurrencyDefinition &destCurrency,
|
|
uint32_t lastExportHeight,
|
|
uint32_t notaHeight,
|
|
std::vector<CReserveTransfer> &exportTransfers, // both in and out. this may refund conversions
|
|
uint256 &transferHash,
|
|
CPBaaSNotarization &newNotarization,
|
|
std::vector<CTxOut> &importOutputs,
|
|
CCurrencyValueMap &importedCurrency,
|
|
CCurrencyValueMap &gatewayDepositsUsed,
|
|
CCurrencyValueMap &spentCurrencyOut,
|
|
CTransferDestination feeRecipient,
|
|
bool forcedRefund) const
|
|
{
|
|
uint160 sourceSystemID = sourceSystem.GetID();
|
|
uint160 destSystemID = destCurrency.IsGateway() ? destCurrency.gatewayID : destCurrency.systemID;
|
|
|
|
newNotarization = *this;
|
|
newNotarization.SetDefinitionNotarization(false);
|
|
newNotarization.SetBlockOneNotarization(false);
|
|
newNotarization.prevNotarization = CUTXORef();
|
|
newNotarization.prevHeight = newNotarization.notarizationHeight;
|
|
newNotarization.notarizationHeight = notaHeight;
|
|
|
|
uint32_t currentHeight = chainActive.Height() + 1;
|
|
|
|
// if we are communicating with an external system that uses a different hash, use it for everything
|
|
CCurrencyDefinition::EProofProtocol hashType = CCurrencyDefinition::EProofProtocol::PROOF_PBAASMMR;
|
|
|
|
uint160 externalSystemID = sourceSystem.SystemOrGatewayID() == ASSETCHAINS_CHAINID ?
|
|
((destSystemID == ASSETCHAINS_CHAINID) ? uint160() : destSystemID) :
|
|
sourceSystem.GetID();
|
|
|
|
CCurrencyDefinition externalSystemDef;
|
|
if (externalSystemID.IsNull() || externalSystemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
externalSystemDef = ConnectedChains.ThisChain();
|
|
}
|
|
else if (externalSystemID == sourceSystemID)
|
|
{
|
|
externalSystemDef = sourceSystem;
|
|
}
|
|
else if (externalSystemID == destSystemID)
|
|
{
|
|
if (destCurrency.GetID() == destSystemID)
|
|
{
|
|
externalSystemDef = destCurrency;
|
|
}
|
|
else
|
|
{
|
|
externalSystemDef = ConnectedChains.GetCachedCurrency(externalSystemID);
|
|
if (!externalSystemDef.IsValid())
|
|
{
|
|
LogPrintf("%s: cannot retrieve system definition for %s\n", __func__, EncodeDestination(CIdentityID(externalSystemID)));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
CTransferDestination notaryPayee;
|
|
|
|
if (!externalSystemID.IsNull())
|
|
{
|
|
hashType = (CCurrencyDefinition::EProofProtocol)externalSystemDef.proofProtocol;
|
|
}
|
|
|
|
CNativeHashWriter hwPrevNotarization;
|
|
hwPrevNotarization << *this;
|
|
newNotarization.hashPrevCrossNotarization = hwPrevNotarization.GetHash();
|
|
|
|
CNativeHashWriter hw(hashType);
|
|
|
|
CCurrencyValueMap newPreConversionReservesIn;
|
|
|
|
if (!IsPreLaunch() && !IsLaunchComplete())
|
|
{
|
|
newPreConversionReservesIn = CCurrencyValueMap(currencyState.currencies, currencyState.primaryCurrencyIn);
|
|
}
|
|
|
|
for (int i = 0; i < exportTransfers.size(); i++)
|
|
{
|
|
CReserveTransfer reserveTransfer = exportTransfers[i];
|
|
|
|
//auto transferVec = ::AsVector(reserveTransfer);
|
|
//printf("%s: ReserveTransfer:\n%s\nSerialized:\n%s\n", __func__, reserveTransfer.ToUniValue().write(1,2).c_str(), HexBytes(&(transferVec[0]), transferVec.size()).c_str());
|
|
|
|
// add the pre-mutation reserve transfer to the hash
|
|
hw << reserveTransfer;
|
|
|
|
if (currencyState.IsRefunding())
|
|
{
|
|
reserveTransfer = reserveTransfer.GetRefundTransfer();
|
|
}
|
|
// ensure that any pre-conversions or conversions are all valid, based on mined height and
|
|
// maximum pre-conversions
|
|
else if (reserveTransfer.IsPreConversion())
|
|
{
|
|
if (IsLaunchComplete())
|
|
{
|
|
//printf("%s: Invalid pre-conversion, mined on or after start block\n", __func__);
|
|
LogPrintf("%s: Invalid pre-conversion, mined on or after start block\n", __func__);
|
|
reserveTransfer = reserveTransfer.GetRefundTransfer();
|
|
}
|
|
else
|
|
{
|
|
// check if it exceeds pre-conversion maximums, and refund if so
|
|
CCurrencyValueMap newReserveIn = CCurrencyValueMap(std::vector<uint160>({reserveTransfer.FirstCurrency()}),
|
|
std::vector<int64_t>({reserveTransfer.FirstValue() - CReserveTransactionDescriptor::CalculateConversionFee(reserveTransfer.FirstValue())}));
|
|
CCurrencyValueMap newTotalReserves;
|
|
if (IsPreLaunch())
|
|
{
|
|
newTotalReserves = CCurrencyValueMap(destCurrency.currencies, newNotarization.currencyState.reserves) + newReserveIn + newPreConversionReservesIn;
|
|
}
|
|
else
|
|
{
|
|
newTotalReserves = CCurrencyValueMap(destCurrency.currencies, newNotarization.currencyState.primaryCurrencyIn) + newReserveIn + newPreConversionReservesIn;
|
|
}
|
|
|
|
if (destCurrency.maxPreconvert.size() && newTotalReserves > CCurrencyValueMap(destCurrency.currencies, destCurrency.maxPreconvert))
|
|
{
|
|
LogPrintf("%s: refunding pre-conversion over maximum\n", __func__);
|
|
reserveTransfer = reserveTransfer.GetRefundTransfer();
|
|
}
|
|
else
|
|
{
|
|
newPreConversionReservesIn += newReserveIn;
|
|
}
|
|
}
|
|
}
|
|
else if (reserveTransfer.IsConversion())
|
|
{
|
|
if (!IsLaunchComplete())
|
|
{
|
|
//printf("%s: Invalid conversion, mined before start block\n", __func__);
|
|
LogPrintf("%s: Invalid conversion, mined before start block\n", __func__);
|
|
reserveTransfer = reserveTransfer.GetRefundTransfer();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (exportTransfers.size())
|
|
{
|
|
transferHash = hw.GetHash();
|
|
}
|
|
|
|
CReserveTransactionDescriptor rtxd;
|
|
std::vector<CTxOut> dummyImportOutputs;
|
|
bool thisIsLaunchSys = destCurrency.launchSystemID == ASSETCHAINS_CHAINID;
|
|
|
|
// if this is the clear launch notarization after start, make the notarization and determine if we should launch or refund
|
|
|
|
uint256 weakEntropy = proofRoots.count(sourceSystemID) ? proofRoots.find(sourceSystemID)->second.stateRoot : uint256();
|
|
|
|
// TODO: HARDENING ensure that the latest proof root of this chain is in on gateway
|
|
|
|
if (destCurrency.launchSystemID == sourceSystemID &&
|
|
destCurrency.startBlock &&
|
|
((thisIsLaunchSys && notaHeight <= (destCurrency.startBlock - 1)) ||
|
|
(!thisIsLaunchSys &&
|
|
destCurrency.systemID == ASSETCHAINS_CHAINID &&
|
|
notaHeight == 1)))
|
|
{
|
|
// we get one pre-launch coming through here, initial supply is set and ready for pre-convert
|
|
if (((thisIsLaunchSys && notaHeight == (destCurrency.startBlock - 1)) || sourceSystemID != ASSETCHAINS_CHAINID) &&
|
|
newNotarization.IsPreLaunch())
|
|
{
|
|
// the first block executes the second time through
|
|
if (newNotarization.IsLaunchCleared())
|
|
{
|
|
newNotarization.SetPreLaunch(false);
|
|
newNotarization.currencyState.SetLaunchClear();
|
|
newNotarization.currencyState.SetPrelaunch(false);
|
|
newNotarization.currencyState.RevertReservesAndSupply();
|
|
}
|
|
else
|
|
{
|
|
newNotarization.SetLaunchCleared();
|
|
newNotarization.currencyState.SetLaunchClear();
|
|
|
|
// if we are connected to another currency, make sure it will also start before we confirm that we can
|
|
CCurrencyDefinition coLaunchCurrency;
|
|
CCoinbaseCurrencyState coLaunchState;
|
|
bool coLaunching = false;
|
|
if (destCurrency.IsGatewayConverter())
|
|
{
|
|
// PBaaS or gateway converters have a parent which is the PBaaS chain or gateway
|
|
coLaunching = true;
|
|
coLaunchCurrency = ConnectedChains.GetCachedCurrency(destCurrency.parent);
|
|
}
|
|
else if (destCurrency.IsPBaaSChain() && !destCurrency.GatewayConverterID().IsNull())
|
|
{
|
|
coLaunching = true;
|
|
coLaunchCurrency = ConnectedChains.GetCachedCurrency(destCurrency.GatewayConverterID());
|
|
}
|
|
|
|
if (coLaunching)
|
|
{
|
|
if (!coLaunchCurrency.IsValid())
|
|
{
|
|
printf("%s: Invalid co-launch currency - likely corruption\n", __func__);
|
|
LogPrintf("%s: Invalid co-launch currency - likely corruption\n", __func__);
|
|
return false;
|
|
}
|
|
coLaunchState = ConnectedChains.GetCurrencyState(coLaunchCurrency, notaHeight);
|
|
|
|
if (!coLaunchState.IsValid())
|
|
{
|
|
printf("%s: Invalid co-launch currency state - likely corruption\n", __func__);
|
|
LogPrintf("%s: Invalid co-launch currency state - likely corruption\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
// check our currency and any co-launch currency to determine our eligibility, as ALL
|
|
// co-launch currencies must launch for one to launch
|
|
if (CCurrencyValueMap(coLaunchCurrency.currencies, coLaunchState.reserveIn) < CCurrencyValueMap(coLaunchCurrency.currencies, coLaunchCurrency.minPreconvert) ||
|
|
(coLaunchCurrency.IsFractional() &&
|
|
CCurrencyValueMap(coLaunchCurrency.currencies, coLaunchState.reserveIn).CanonicalMap().valueMap.size() != coLaunchCurrency.currencies.size()))
|
|
{
|
|
forcedRefund = true;
|
|
}
|
|
}
|
|
|
|
// first time through is export, second is import, then we finish clearing the launch
|
|
// check if the chain is qualified to launch or should refund
|
|
CCurrencyValueMap minPreMap, fees;
|
|
CCurrencyValueMap preConvertedMap = (CCurrencyValueMap(destCurrency.currencies, newNotarization.currencyState.reserveIn) +
|
|
newPreConversionReservesIn).CanonicalMap();
|
|
|
|
if (destCurrency.minPreconvert.size() && destCurrency.minPreconvert.size() == destCurrency.currencies.size())
|
|
{
|
|
minPreMap = CCurrencyValueMap(destCurrency.currencies, destCurrency.minPreconvert).CanonicalMap();
|
|
}
|
|
|
|
if (forcedRefund ||
|
|
(minPreMap.valueMap.size() && preConvertedMap < minPreMap) ||
|
|
(destCurrency.IsFractional() &&
|
|
(CCurrencyValueMap(destCurrency.currencies, newNotarization.currencyState.reserveIn) +
|
|
newPreConversionReservesIn).CanonicalMap().valueMap.size() != destCurrency.currencies.size()))
|
|
{
|
|
// we force the reserves and supply to zero
|
|
// in any case where there was less than minimum participation,
|
|
newNotarization.currencyState.supply = 0;
|
|
newNotarization.currencyState.reserves = std::vector<int64_t>(newNotarization.currencyState.reserves.size(), 0);
|
|
newNotarization.currencyState.SetRefunding(true);
|
|
newNotarization.SetRefunding(true);
|
|
}
|
|
else
|
|
{
|
|
newNotarization.SetLaunchConfirmed();
|
|
newNotarization.currencyState.SetLaunchConfirmed();
|
|
|
|
// if this is a launch notarization for a PBaaS chain, add a proofroot of the
|
|
// current chain as an anchor for the block one import
|
|
if (destCurrency.IsPBaaSChain() &&
|
|
destCurrency.launchSystemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
newNotarization.proofRoots[ASSETCHAINS_CHAINID] = CProofRoot::GetProofRoot(destCurrency.startBlock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (thisIsLaunchSys && notaHeight < (destCurrency.startBlock - 1))
|
|
{
|
|
newNotarization.currencyState.SetPrelaunch();
|
|
}
|
|
|
|
// NOTE: destcurrency systemID is correct here, since this is only prelaunch or block 1, which doesn't apply for gateway's
|
|
CCurrencyDefinition destSystem = newNotarization.IsRefunding() ? ConnectedChains.GetCachedCurrency(destCurrency.launchSystemID) :
|
|
ConnectedChains.GetCachedCurrency(destCurrency.systemID);
|
|
|
|
CCoinbaseCurrencyState tempState = newNotarization.currencyState;
|
|
if (destCurrency.IsFractional() &&
|
|
!(newNotarization.IsLaunchCleared() && !newNotarization.currencyState.IsLaunchCompleteMarker()) &&
|
|
exportTransfers.size())
|
|
{
|
|
// normalize prices on the way in to prevent overflows on first pass
|
|
std::vector<int64_t> newReservesVector = newPreConversionReservesIn.AsCurrencyVector(tempState.currencies);
|
|
tempState.reserves = tempState.AddVectors(tempState.reserves, newReservesVector);
|
|
newNotarization.currencyState.conversionPrice = tempState.PricesInReserve();
|
|
}
|
|
|
|
std::vector<CTxOut> tempOutputs;
|
|
bool retVal = rtxd.AddReserveTransferImportOutputs(newNotarization.IsRefunding() ? destSystem : sourceSystem,
|
|
newNotarization.IsRefunding() ? sourceSystem : destSystem,
|
|
destCurrency,
|
|
newNotarization.currencyState,
|
|
exportTransfers,
|
|
currentHeight,
|
|
tempOutputs,
|
|
importedCurrency,
|
|
gatewayDepositsUsed,
|
|
spentCurrencyOut,
|
|
&tempState,
|
|
feeRecipient,
|
|
proposer,
|
|
weakEntropy);
|
|
|
|
if (retVal)
|
|
{
|
|
//printf("%s: importedCurrency: %s\ngatewaysDepositsUsed: %s\n", __func__, importedCurrency.ToUniValue().write(1,2).c_str(), gatewayDepositsUsed.ToUniValue().write(1,2).c_str());
|
|
importedCurrency.valueMap.clear();
|
|
gatewayDepositsUsed.valueMap.clear();
|
|
spentCurrencyOut.valueMap.clear();
|
|
newNotarization.currencyState.conversionPrice = tempState.conversionPrice;
|
|
newNotarization.currencyState.viaConversionPrice = tempState.viaConversionPrice;
|
|
rtxd = CReserveTransactionDescriptor();
|
|
|
|
retVal = rtxd.AddReserveTransferImportOutputs(newNotarization.IsRefunding() ? destSystem : sourceSystem,
|
|
newNotarization.IsRefunding() ? sourceSystem : destSystem,
|
|
destCurrency,
|
|
newNotarization.currencyState,
|
|
exportTransfers,
|
|
currentHeight,
|
|
importOutputs,
|
|
importedCurrency,
|
|
gatewayDepositsUsed,
|
|
spentCurrencyOut,
|
|
&tempState,
|
|
feeRecipient,
|
|
proposer,
|
|
weakEntropy);
|
|
}
|
|
else
|
|
{
|
|
return retVal;
|
|
}
|
|
|
|
//printf("%s: importedCurrency: %s\ngatewaysDepositsUsed: %s\n", __func__, importedCurrency.ToUniValue().write(1,2).c_str(), gatewayDepositsUsed.ToUniValue().write(1,2).c_str());
|
|
|
|
// if we are in the pre-launch phase, all reserves in are cumulative and then calculated together at launch
|
|
// reserves in represent all reserves in, and fees are taken out after launch or refund as well
|
|
//
|
|
// we add up until the end, then stop adding reserves at launch clear. this gives us the ability to reverse the
|
|
// pre-launch state for validation. we continue adding fees up to pre-launch to easily get a total of unconverted
|
|
// fees, which we need when creating a PBaaS chain, as all currency, both reserves and fees exported to the new
|
|
// chain must be either output to specific addresses, taken as fees by miners, or stored in reserve deposits.
|
|
if (tempState.IsPrelaunch())
|
|
{
|
|
tempState.reserveIn = tempState.AddVectors(tempState.reserveIn, this->currencyState.reserveIn);
|
|
if (!destCurrency.IsFractional() && !this->IsDefinitionNotarization() && !tempState.IsLaunchClear())
|
|
{
|
|
tempState.supply += this->currencyState.supply - this->currencyState.emitted;
|
|
tempState.preConvertedOut += this->currencyState.preConvertedOut;
|
|
tempState.primaryCurrencyOut += this->currencyState.primaryCurrencyOut;
|
|
}
|
|
}
|
|
else if (!newNotarization.currencyState.IsLaunchCompleteMarker() && !tempState.IsRefunding() && !this->currencyState.IsPrelaunch())
|
|
{
|
|
// accumulate reserves during pre-conversions import to enforce max pre-convert
|
|
CCurrencyValueMap tempReserves;
|
|
for (auto &oneCurrencyID : tempState.currencies)
|
|
{
|
|
if (rtxd.currencies.count(oneCurrencyID))
|
|
{
|
|
int64_t reservesConverted = rtxd.currencies[oneCurrencyID].nativeOutConverted;
|
|
if (reservesConverted)
|
|
{
|
|
tempReserves.valueMap[oneCurrencyID] = reservesConverted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// use double entry to enable pass through of the accumulated reserve such that when
|
|
// reverting supply and reserves, we end up with what we started, after prelaunch and
|
|
// before all post launch functions are complete, we use primaryCurrencyIn to accumulate
|
|
// reserves to enforce maxPreconvert
|
|
tempState.primaryCurrencyIn = tempState.AddVectors(this->currencyState.primaryCurrencyIn, tempReserves.AsCurrencyVector(tempState.currencies));
|
|
tempState.reserveOut =
|
|
tempState.AddVectors(tempState.reserveOut,
|
|
(CCurrencyValueMap(tempState.currencies, tempState.reserveIn) * -1).AsCurrencyVector(tempState.currencies));
|
|
tempState.reserveIn = tempReserves.AsCurrencyVector(tempState.currencies);
|
|
}
|
|
if (destCurrency.IsPBaaSChain() && tempState.IsLaunchClear() && this->currencyState.IsLaunchClear())
|
|
{
|
|
tempState.reserveIn = this->currencyState.reserveIn;
|
|
}
|
|
newNotarization.currencyState = tempState;
|
|
return retVal;
|
|
}
|
|
else
|
|
{
|
|
if (sourceSystemID != destCurrency.launchSystemID || lastExportHeight >= destCurrency.startBlock || newNotarization.IsLaunchComplete())
|
|
{
|
|
newNotarization.currencyState.SetLaunchCompleteMarker();
|
|
}
|
|
newNotarization.currencyState.SetLaunchClear(false);
|
|
|
|
CCurrencyDefinition destSystem = newNotarization.IsRefunding() ? ConnectedChains.GetCachedCurrency(destCurrency.launchSystemID) :
|
|
ConnectedChains.GetCachedCurrency(destCurrency.SystemOrGatewayID());
|
|
|
|
// calculate new state from processing all transfers
|
|
// we are not refunding, and it is possible that we also have
|
|
// normal conversions in addition to pre-conversions. add any conversions that may
|
|
// be present into the new currency state
|
|
CCoinbaseCurrencyState intermediateState = newNotarization.currencyState;
|
|
bool isValidExport = rtxd.AddReserveTransferImportOutputs(sourceSystem,
|
|
destSystem,
|
|
destCurrency,
|
|
intermediateState,
|
|
exportTransfers,
|
|
currentHeight,
|
|
dummyImportOutputs,
|
|
importedCurrency,
|
|
gatewayDepositsUsed,
|
|
spentCurrencyOut,
|
|
&newNotarization.currencyState,
|
|
feeRecipient,
|
|
proposer,
|
|
weakEntropy);
|
|
if (!newNotarization.currencyState.IsPrelaunch() &&
|
|
isValidExport &&
|
|
destCurrency.IsFractional())
|
|
{
|
|
// we want the new price and the old state as a starting point to ensure no rounding error impact
|
|
// on reserves
|
|
importedCurrency = CCurrencyValueMap();
|
|
gatewayDepositsUsed = CCurrencyValueMap();
|
|
CCoinbaseCurrencyState tempCurState = intermediateState;
|
|
tempCurState.conversionPrice = newNotarization.currencyState.conversionPrice;
|
|
tempCurState.viaConversionPrice = newNotarization.currencyState.viaConversionPrice;
|
|
rtxd = CReserveTransactionDescriptor();
|
|
isValidExport = rtxd.AddReserveTransferImportOutputs(sourceSystem,
|
|
destSystem,
|
|
destCurrency,
|
|
tempCurState,
|
|
exportTransfers,
|
|
currentHeight,
|
|
importOutputs,
|
|
importedCurrency,
|
|
gatewayDepositsUsed,
|
|
spentCurrencyOut,
|
|
&newNotarization.currencyState,
|
|
feeRecipient,
|
|
proposer,
|
|
weakEntropy);
|
|
if (isValidExport)
|
|
{
|
|
newNotarization.currencyState.conversionPrice = tempCurState.conversionPrice;
|
|
newNotarization.currencyState.viaConversionPrice = tempCurState.viaConversionPrice;
|
|
}
|
|
}
|
|
else if (isValidExport)
|
|
{
|
|
importOutputs.insert(importOutputs.end(), dummyImportOutputs.begin(), dummyImportOutputs.end());
|
|
}
|
|
|
|
if (!isValidExport)
|
|
{
|
|
LogPrintf("%s: invalid export\n", __func__);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// based on the last notarization and existing
|
|
return false;
|
|
}
|
|
|
|
CObjectFinalization::CObjectFinalization(const CScript &script) : version(VERSION_INVALID)
|
|
{
|
|
COptCCParams p;
|
|
if (script.IsPayToCryptoCondition(p) && p.IsValid())
|
|
{
|
|
if ((p.evalCode == EVAL_FINALIZE_NOTARIZATION || p.evalCode == EVAL_FINALIZE_EXPORT) &&
|
|
p.vData.size())
|
|
{
|
|
*this = CObjectFinalization(p.vData[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
CObjectFinalization::CObjectFinalization(const CTransaction &tx, uint32_t *pEcode, int32_t *pFinalizationOutNum, uint32_t minFinalHeight, uint8_t Version) :
|
|
minFinalizationHeight(minFinalHeight),
|
|
version(Version)
|
|
{
|
|
uint32_t _ecode;
|
|
uint32_t &ecode = pEcode ? *pEcode : _ecode;
|
|
int32_t _finalizeOutNum;
|
|
int32_t &finalizeOutNum = pFinalizationOutNum ? *pFinalizationOutNum : _finalizeOutNum;
|
|
finalizeOutNum = -1;
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
COptCCParams p;
|
|
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid())
|
|
{
|
|
if (p.evalCode == EVAL_FINALIZE_NOTARIZATION || p.evalCode == EVAL_FINALIZE_EXPORT)
|
|
{
|
|
if (finalizeOutNum != -1)
|
|
{
|
|
version = VERSION_INVALID;
|
|
finalizeOutNum = -1;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
finalizeOutNum = i;
|
|
ecode = p.evalCode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CChainNotarizationData::CChainNotarizationData(UniValue &obj)
|
|
{
|
|
version = (uint32_t)uni_get_int(find_value(obj, "version"));
|
|
UniValue vtxUni = find_value(obj, "notarizations");
|
|
if (vtxUni.isArray())
|
|
{
|
|
vector<UniValue> vvtx = vtxUni.getValues();
|
|
for (auto o : vvtx)
|
|
{
|
|
vtx.push_back(make_pair(CUTXORef(uint256S(uni_get_str(find_value(o, "txid"))),
|
|
uni_get_int(find_value(o, "vout"))),
|
|
CPBaaSNotarization(find_value(o, "notarization"))));
|
|
}
|
|
}
|
|
|
|
lastConfirmed = (uint32_t)uni_get_int(find_value(obj, "lastconfirmed"));
|
|
UniValue forksUni = find_value(obj, "forks");
|
|
if (forksUni.isArray())
|
|
{
|
|
vector<UniValue> forksVec = forksUni.getValues();
|
|
for (auto fv : forksVec)
|
|
{
|
|
if (fv.isArray())
|
|
{
|
|
forks.push_back(vector<int32_t>());
|
|
vector<UniValue> forkVec = fv.getValues();
|
|
for (auto fidx : forkVec)
|
|
{
|
|
forks.back().push_back(uni_get_int(fidx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bestChain = (uint32_t)uni_get_int(find_value(obj, "bestchain"));
|
|
}
|
|
|
|
UniValue CChainNotarizationData::ToUniValue() const
|
|
{
|
|
UniValue obj(UniValue::VOBJ);
|
|
obj.push_back(Pair("version", (int32_t)version));
|
|
UniValue notarizations(UniValue::VARR);
|
|
for (int64_t i = 0; i < vtx.size(); i++)
|
|
{
|
|
UniValue notarization(UniValue::VOBJ);
|
|
notarization.push_back(Pair("index", i));
|
|
notarization.push_back(Pair("txid", vtx[i].first.hash.GetHex()));
|
|
notarization.push_back(Pair("vout", (int32_t)vtx[i].first.n));
|
|
notarization.push_back(Pair("notarization", vtx[i].second.ToUniValue()));
|
|
notarizations.push_back(notarization);
|
|
}
|
|
obj.push_back(Pair("notarizations", notarizations));
|
|
UniValue Forks(UniValue::VARR);
|
|
for (int32_t i = 0; i < forks.size(); i++)
|
|
{
|
|
UniValue Fork(UniValue::VARR);
|
|
for (int32_t j = 0; j < forks[i].size(); j++)
|
|
{
|
|
Fork.push_back(forks[i][j]);
|
|
}
|
|
Forks.push_back(Fork);
|
|
}
|
|
obj.push_back(Pair("forks", Forks));
|
|
if (IsConfirmed())
|
|
{
|
|
obj.push_back(Pair("lastconfirmedheight", (int32_t)vtx[lastConfirmed].second.notarizationHeight));
|
|
}
|
|
obj.push_back(Pair("lastconfirmed", lastConfirmed));
|
|
obj.push_back(Pair("bestchain", bestChain));
|
|
return obj;
|
|
}
|
|
|
|
bool CPBaaSNotarization::IsNotarizationConfirmed(const CPBaaSNotarization ¬arization,
|
|
const CNotaryEvidence ¬aryEvidence,
|
|
CValidationState &state) const
|
|
{
|
|
// TODO: HARDENING - remove or implement and use
|
|
return false;
|
|
}
|
|
|
|
bool CPBaaSNotarization::IsNotarizationRejected(const CPBaaSNotarization ¬arization,
|
|
const CNotaryEvidence ¬aryEvidence,
|
|
CValidationState &state) const
|
|
{
|
|
// TODO: HARDENING - remove or implement and use
|
|
return false;
|
|
}
|
|
|
|
bool CChainNotarizationData::CalculateConfirmation(int confirmingIdx, std::set<int> &confirmedOutputNums, std::set<int> &invalidatedOutputNums) const
|
|
{
|
|
int forkIdx = -1, forkPos = -1;
|
|
|
|
if (confirmingIdx != lastConfirmed)
|
|
{
|
|
// if the third notarization does not equal the last confirmed notarization, it
|
|
// must equal a last pending notarization to confirm. that will always be the
|
|
// first non-confirmed entry in a fork of the notarization data, if it is present
|
|
for (int i = 0; i < forks.size(); i++)
|
|
{
|
|
if (forks[i].size() > 1)
|
|
{
|
|
for (int j = forks[i].size() - 1; j >= 0; j--)
|
|
{
|
|
if (forks[i][j] == confirmingIdx)
|
|
{
|
|
forkIdx = i;
|
|
forkPos = j;
|
|
break;
|
|
}
|
|
}
|
|
if (forkIdx > -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// if it wasn't the confirmed entry and wasn't pending confirmation, it is referring to an
|
|
// invalid predecessor and therefore, confirmingIdx is invalid
|
|
if (forkIdx == -1)
|
|
{
|
|
LogPrint("notarization", "%s: invalid prior notarization - neither pending nor confirmed\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (forkIdx > -1)
|
|
{
|
|
// if this finalization is either confirmed or invalidated by the new accepted notarization,
|
|
// add a spend here
|
|
for (int i = 0; i <= forkPos; i++)
|
|
{
|
|
confirmedOutputNums.insert(forks[forkIdx][i]);
|
|
}
|
|
|
|
for (int i = 0; i < forks.size(); i++)
|
|
{
|
|
// already did the fork that is our focus
|
|
if (i == forkIdx)
|
|
{
|
|
continue;
|
|
}
|
|
bool failed = false;
|
|
|
|
int j;
|
|
for (j = 0; j < forks[i].size(); j++)
|
|
{
|
|
// if we reach the end, the rest after are ignored
|
|
if (forks[i][j] == confirmingIdx)
|
|
{
|
|
break;
|
|
}
|
|
if (confirmedOutputNums.count(forks[i][j]))
|
|
{
|
|
continue;
|
|
}
|
|
// it did not derive from the newly confirmed index, so it is invalid and all after this
|
|
failed = true;
|
|
break;
|
|
}
|
|
// if failed, invalidate all until the end, otherwise, ignore all until the end
|
|
if (failed)
|
|
{
|
|
for (int k = j; k < forks[i].size(); k++)
|
|
{
|
|
invalidatedOutputNums.insert(forks[i][k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CChainNotarizationData::CorrelatedFinalizationSpends(const std::vector<std::pair<CTransaction, uint256>> &txes,
|
|
std::vector<std::vector<CInputDescriptor>> &spendsToClose,
|
|
std::vector<CInputDescriptor> &extraSpends,
|
|
std::vector<std::vector<CNotaryEvidence>> *pEvidenceVec) const
|
|
{
|
|
if (!(IsValid() && IsConfirmed()))
|
|
{
|
|
return false;
|
|
}
|
|
uint160 currencyID = vtx[0].second.currencyID;
|
|
|
|
if (pEvidenceVec)
|
|
{
|
|
*pEvidenceVec = std::vector<std::vector<CNotaryEvidence>>(vtx.size());
|
|
}
|
|
|
|
std::map<CUTXORef, int> notarizationOutputMap;
|
|
|
|
for (int i = 0; i < vtx.size(); i++)
|
|
{
|
|
notarizationOutputMap.insert(std::make_pair(vtx[i].first, i));
|
|
}
|
|
|
|
uint32_t confirmedBlockHeight = mapBlockIndex.count(txes[lastConfirmed].second) ? mapBlockIndex[txes[lastConfirmed].second]->GetHeight() : 0;
|
|
if (!confirmedBlockHeight)
|
|
{
|
|
LogPrint("notarization", "%s: last confirmed notarization not found in block index\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
spendsToClose = std::vector<std::vector<CInputDescriptor>>(vtx.size());
|
|
|
|
for (auto onePending : CObjectFinalization::GetUnspentPendingFinalizations(currencyID))
|
|
{
|
|
// determine the notarization output that this is referring to
|
|
COptCCParams p;
|
|
CObjectFinalization existingFinalization;
|
|
CUTXORef pendingNotarizationOutput;
|
|
std::set<CUTXORef> evidenceOutputSet;
|
|
if (onePending.second.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.vData.size())
|
|
{
|
|
if (p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
(existingFinalization = CObjectFinalization(p.vData[0])).IsValid())
|
|
{
|
|
if (existingFinalization.output.IsOnSameTransaction())
|
|
{
|
|
pendingNotarizationOutput = CUTXORef(onePending.second.txIn.prevout.hash, existingFinalization.output.n);
|
|
}
|
|
else
|
|
{
|
|
pendingNotarizationOutput = existingFinalization.output;
|
|
}
|
|
}
|
|
else if (p.evalCode == EVAL_EARNEDNOTARIZATION)
|
|
{
|
|
pendingNotarizationOutput = CUTXORef(onePending.second.txIn.prevout.hash, onePending.second.txIn.prevout.n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is likely some form of incorrect index
|
|
LogPrintf("%s: LIKELY LOCAL STATE OR INDEX CORRUPTION. Bootstrap, reindex, or resync recommended\n", __func__);
|
|
continue;
|
|
}
|
|
|
|
// if this is a finalization with some evidence, add the spends to close it that include the finalization and
|
|
// its evidence outputs
|
|
int associatedIdx = -1;
|
|
if (pendingNotarizationOutput.IsValid() && !pendingNotarizationOutput.hash.IsNull())
|
|
{
|
|
std::vector<CInputDescriptor> associatedSpends;
|
|
associatedSpends.push_back(onePending.second); // this is a finalization or earned notarization, so it is first associated spend
|
|
|
|
if (notarizationOutputMap.count(pendingNotarizationOutput))
|
|
{
|
|
associatedIdx = notarizationOutputMap[pendingNotarizationOutput];
|
|
}
|
|
|
|
// if we are asssociated with a known node,
|
|
// get additional associated evidence as well
|
|
if (associatedIdx != -1)
|
|
{
|
|
// if there is a finalization, we need to add it and its evidence,
|
|
if (existingFinalization.IsValid() && existingFinalization.evidenceOutputs.size())
|
|
{
|
|
CTransaction finalizationTx;
|
|
const CTransaction *pEvidenceOutputTx = nullptr;
|
|
if (existingFinalization.output.IsOnSameTransaction())
|
|
{
|
|
pEvidenceOutputTx = &(txes[associatedIdx].first);
|
|
}
|
|
else
|
|
{
|
|
LOCK(mempool.cs);
|
|
uint256 hashBlock;
|
|
if (myGetTransaction(onePending.second.txIn.prevout.hash, finalizationTx, hashBlock))
|
|
{
|
|
pEvidenceOutputTx = &finalizationTx;
|
|
}
|
|
else
|
|
{
|
|
LogPrint("notarization", "%s: cannot access transaction required as input for notarization\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// add evidence outs as spends to close this entry as well
|
|
int afterMultiPart = existingFinalization.evidenceOutputs.size() ? existingFinalization.evidenceOutputs[0] : 0;
|
|
for (auto oneEvidenceOutN : existingFinalization.evidenceOutputs)
|
|
{
|
|
if (pEvidenceOutputTx->vout.size() <= oneEvidenceOutN)
|
|
{
|
|
LogPrint("notarization", "%s: indexing error for notarization evidence\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (pEvidenceVec &&
|
|
oneEvidenceOutN >= afterMultiPart &&
|
|
associatedIdx != -1)
|
|
{
|
|
CNotaryEvidence oneEvidenceObj(*pEvidenceOutputTx, oneEvidenceOutN, afterMultiPart);
|
|
if (oneEvidenceObj.IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(oneEvidenceObj);
|
|
}
|
|
}
|
|
|
|
associatedSpends.push_back(
|
|
CInputDescriptor(pEvidenceOutputTx->vout[oneEvidenceOutN].scriptPubKey,
|
|
pEvidenceOutputTx->vout[oneEvidenceOutN].nValue,
|
|
CTxIn(pEvidenceOutputTx->GetHash(), oneEvidenceOutN)));
|
|
evidenceOutputSet.insert(associatedSpends.back().txIn.prevout);
|
|
}
|
|
}
|
|
|
|
// unspent evidence is specific to the target notarization
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> unspentEvidence =
|
|
CObjectFinalization::GetUnspentEvidence(currencyID, vtx[associatedIdx].first.hash, vtx[associatedIdx].first.n);
|
|
for (auto &oneEvidenceSpend : unspentEvidence)
|
|
{
|
|
// if not already added to our spends as evidence, add it
|
|
if (!evidenceOutputSet.count(oneEvidenceSpend.second.txIn.prevout))
|
|
{
|
|
COptCCParams tP;
|
|
CNotaryEvidence oneEvidenceObj;
|
|
if (pEvidenceVec &&
|
|
oneEvidenceSpend.second.scriptPubKey.IsPayToCryptoCondition(tP) &&
|
|
tP.IsValid() &&
|
|
tP.vData.size() &&
|
|
tP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
(oneEvidenceObj = CNotaryEvidence(tP.vData[0])).IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(oneEvidenceObj);
|
|
}
|
|
|
|
associatedSpends.push_back(oneEvidenceSpend.second);
|
|
}
|
|
}
|
|
|
|
spendsToClose[associatedIdx].insert(spendsToClose[associatedIdx].end(), associatedSpends.begin(), associatedSpends.end());
|
|
}
|
|
else
|
|
{
|
|
// it may be a straggler that came in before confirmation. if so, just clean it up
|
|
if (onePending.first)
|
|
{
|
|
// add it to unknown closing spends
|
|
extraSpends.insert(extraSpends.end(), associatedSpends.begin(), associatedSpends.end());
|
|
}
|
|
else
|
|
{
|
|
LogPrint("notarization", "%s: no associated notarization located for pending finalization in mempool:\n%s\n", __func__, pendingNotarizationOutput.ToString().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto oneConfirmed : CObjectFinalization::GetUnspentConfirmedFinalizations(currencyID))
|
|
{
|
|
// since we are creating a new, confirmed finalization, spend old one here
|
|
// determine the notarization output that this is referring to
|
|
COptCCParams p;
|
|
CObjectFinalization confirmedFinalization;
|
|
CUTXORef confirmedNotarizationOutput;
|
|
std::set<CUTXORef> evidenceOutputSet;
|
|
|
|
if (oneConfirmed.second.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid())
|
|
{
|
|
CPBaaSNotarization confirmedNotarization;
|
|
if (p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
p.vData.size() &&
|
|
(confirmedFinalization = CObjectFinalization(p.vData[0])).IsValid())
|
|
{
|
|
if (confirmedFinalization.output.IsOnSameTransaction())
|
|
{
|
|
confirmedNotarizationOutput = CUTXORef(oneConfirmed.second.txIn.prevout.hash, confirmedFinalization.output.n);
|
|
}
|
|
else
|
|
{
|
|
confirmedNotarizationOutput = confirmedFinalization.output;
|
|
}
|
|
}
|
|
else if ((p.evalCode == EVAL_EARNEDNOTARIZATION || p.evalCode == EVAL_ACCEPTEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(confirmedNotarization = CPBaaSNotarization(p.vData[0])).IsValid() &&
|
|
(confirmedNotarization.IsDefinitionNotarization() || confirmedNotarization.IsBlockOneNotarization()))
|
|
{
|
|
confirmedFinalization = CObjectFinalization(CObjectFinalization::FINALIZE_CONFIRMED + CObjectFinalization::FINALIZE_NOTARIZATION,
|
|
confirmedNotarization.currencyID,
|
|
oneConfirmed.second.txIn.prevout.hash,
|
|
oneConfirmed.second.txIn.prevout.n);
|
|
confirmedNotarizationOutput = confirmedFinalization.output;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is likely some form of incorrect index
|
|
LogPrintf("%s: LIKELY LOCAL STATE OR INDEX CORRUPTION. Bootstrap, reindex, or resync recommended\n", __func__);
|
|
continue;
|
|
}
|
|
|
|
// if this is a finalization, add the spends to close it that include the finalization and
|
|
// its evidence outputs
|
|
int associatedIdx = -1;
|
|
if (confirmedFinalization.IsValid())
|
|
{
|
|
std::vector<CInputDescriptor> associatedSpends;
|
|
associatedSpends.push_back(oneConfirmed.second);
|
|
|
|
if (notarizationOutputMap.count(confirmedNotarizationOutput))
|
|
{
|
|
associatedIdx = notarizationOutputMap[confirmedNotarizationOutput];
|
|
}
|
|
|
|
if (associatedIdx != -1)
|
|
{
|
|
if (confirmedFinalization.evidenceOutputs.size() || confirmedFinalization.evidenceInputs.size())
|
|
{
|
|
CTransaction finalizationTx;
|
|
const CTransaction *pEvidenceOutputTx = nullptr;
|
|
if (confirmedFinalization.output.IsOnSameTransaction())
|
|
{
|
|
pEvidenceOutputTx = &(txes[associatedIdx].first);
|
|
}
|
|
else
|
|
{
|
|
LOCK(mempool.cs);
|
|
uint256 hashBlock;
|
|
if (myGetTransaction(oneConfirmed.second.txIn.prevout.hash, finalizationTx, hashBlock))
|
|
{
|
|
pEvidenceOutputTx = &finalizationTx;
|
|
}
|
|
else
|
|
{
|
|
LogPrint("notarization", "%s: cannot access transaction required as input for notarization\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// add evidence outs as spends to close this entry as well
|
|
int afterMultiPart = confirmedFinalization.evidenceOutputs.size() ? confirmedFinalization.evidenceOutputs[0] : 0;
|
|
for (auto oneEvidenceOutN : confirmedFinalization.evidenceOutputs)
|
|
{
|
|
if (pEvidenceOutputTx->vout.size() <= oneEvidenceOutN)
|
|
{
|
|
LogPrint("notarization", "%s: indexing error for notarization evidence\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (pEvidenceVec &&
|
|
oneEvidenceOutN >= afterMultiPart &&
|
|
associatedIdx != -1)
|
|
{
|
|
CNotaryEvidence oneEvidenceObj(*pEvidenceOutputTx, oneEvidenceOutN, afterMultiPart);
|
|
if (oneEvidenceObj.IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(oneEvidenceObj);
|
|
}
|
|
}
|
|
|
|
associatedSpends.push_back(
|
|
CInputDescriptor(pEvidenceOutputTx->vout[oneEvidenceOutN].scriptPubKey,
|
|
pEvidenceOutputTx->vout[oneEvidenceOutN].nValue,
|
|
CTxIn(pEvidenceOutputTx->GetHash(), oneEvidenceOutN)));
|
|
evidenceOutputSet.insert(associatedSpends.back().txIn.prevout);
|
|
}
|
|
|
|
if (pEvidenceVec &&
|
|
associatedIdx != -1)
|
|
{
|
|
// on a confirmed notarization, get input evidence as well, even though it doesn't need to be cleaned up
|
|
CTransaction priorOutputTx;
|
|
CNotaryEvidence oneEvidenceObj;
|
|
std::vector<CNotaryEvidence> evidenceInVec;
|
|
for (auto oneIn : confirmedFinalization.evidenceInputs)
|
|
{
|
|
uint256 hashBlock;
|
|
if (priorOutputTx.GetHash() != pEvidenceOutputTx->vin[oneIn].prevout.hash &&
|
|
!myGetTransaction(pEvidenceOutputTx->vin[oneIn].prevout.hash, priorOutputTx, hashBlock))
|
|
{
|
|
printf("%s: cannot access transaction for notarization evidence\n", __func__);
|
|
LogPrint("%s: cannot access transaction for notarization evidence\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
COptCCParams inP;
|
|
if (priorOutputTx.vout[pEvidenceOutputTx->vin[oneIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(inP) &&
|
|
inP.IsValid() &&
|
|
inP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
inP.vData.size() &&
|
|
(oneEvidenceObj = CNotaryEvidence(inP.vData[0])).IsValid() &&
|
|
oneEvidenceObj.evidence.chainObjects.size())
|
|
{
|
|
// there is no spend to store, as it has already been spent, but we
|
|
// still want to get its evidence
|
|
|
|
// if we are starting a new object, finish the old one
|
|
if (!oneEvidenceObj.IsMultipartProof() ||
|
|
((CChainObject<CEvidenceData> *)oneEvidenceObj.evidence.chainObjects[0])->object.md.index == 0)
|
|
{
|
|
// if we have a composite evidence object, store it and clear the vector
|
|
if (evidenceInVec.size())
|
|
{
|
|
CNotaryEvidence multiPartEvidence(evidenceInVec);
|
|
if (multiPartEvidence.IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(multiPartEvidence);
|
|
}
|
|
evidenceInVec.clear();
|
|
}
|
|
}
|
|
|
|
if (!oneEvidenceObj.IsMultipartProof())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(oneEvidenceObj);
|
|
}
|
|
else
|
|
{
|
|
evidenceInVec.push_back(oneEvidenceObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have a composite evidence object, store it and clear the vector
|
|
if (evidenceInVec.size())
|
|
{
|
|
CNotaryEvidence multiPartEvidence(evidenceInVec);
|
|
if (multiPartEvidence.IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(multiPartEvidence);
|
|
}
|
|
else
|
|
{
|
|
printf("%s: invalid multipart evidence on input\n", __func__);
|
|
LogPrint("%s: invalid multipart evidence on input\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// unspent evidence is specific to the target notarization
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> unspentEvidence =
|
|
CObjectFinalization::GetUnspentEvidence(currencyID, vtx[associatedIdx].first.hash, vtx[associatedIdx].first.n);
|
|
|
|
for (auto &oneEvidenceSpend : unspentEvidence)
|
|
{
|
|
// if not already added to our spends as evidence, add it
|
|
if (!evidenceOutputSet.count(oneEvidenceSpend.second.txIn.prevout))
|
|
{
|
|
COptCCParams tP;
|
|
CNotaryEvidence oneEvidenceObj;
|
|
int afterEvidence;
|
|
if (pEvidenceVec &&
|
|
oneEvidenceSpend.second.scriptPubKey.IsPayToCryptoCondition(tP) &&
|
|
tP.IsValid() &&
|
|
tP.vData.size() &&
|
|
tP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
(oneEvidenceObj = CNotaryEvidence(tP.vData[0])).IsValid())
|
|
{
|
|
(*pEvidenceVec)[associatedIdx].push_back(oneEvidenceObj);
|
|
}
|
|
|
|
associatedSpends.push_back(oneEvidenceSpend.second);
|
|
}
|
|
}
|
|
|
|
spendsToClose[associatedIdx].insert(spendsToClose[associatedIdx].end(), associatedSpends.begin(), associatedSpends.end());
|
|
}
|
|
else
|
|
{
|
|
extraSpends.insert(extraSpends.end(), associatedSpends.begin(), associatedSpends.end());
|
|
}
|
|
}
|
|
|
|
// this output wasn't found associated with any valid confirmed or pending notarization
|
|
if (associatedIdx == -1)
|
|
{
|
|
// for finalizations that have no found association, we warn
|
|
// printf("%s: no associated notarization located for confirmed finalization:\n%s\n", __func__, confirmedNotarizationOutput.ToString().c_str());
|
|
LogPrint("notarization", "%s: no associated notarization located for confirmed finalization:\n%s\n", __func__, confirmedNotarizationOutput.ToString().c_str());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CPBaaSNotarization::CreateAcceptedNotarization(const CCurrencyDefinition &externalSystem,
|
|
const CPBaaSNotarization &earnedNotarization,
|
|
const CNotaryEvidence ¬aryEvidence,
|
|
CValidationState &state,
|
|
TransactionBuilder &txBuilder)
|
|
{
|
|
std::string errorPrefix(strprintf("%s: ", __func__));
|
|
uint160 SystemID = externalSystem.GetID();
|
|
|
|
const CCurrencyDefinition *pNotaryCurrency;
|
|
if (IsVerusActive() || !ConnectedChains.notarySystems.count(SystemID))
|
|
{
|
|
pNotaryCurrency = &externalSystem;
|
|
}
|
|
else
|
|
{
|
|
pNotaryCurrency = &ConnectedChains.ThisChain();
|
|
}
|
|
|
|
std::set<uint160> notarySet = pNotaryCurrency->GetNotarySet();
|
|
int minimumNotariesConfirm = pNotaryCurrency->MinimumNotariesConfirm();
|
|
|
|
// for an accepted notarization to be finalized, it must be either a chain ID notarization protocol or signed by all notaries
|
|
// in addition, an auto-notarization submission must be mined in and settle for one period without notary rejection before it
|
|
// can be used as proof.
|
|
//
|
|
// after a notarization is mined in, it is not considered actually finalized until one notarization period of blocks where it
|
|
// has remained on the blockchain. at the 10th block, if a majority of the voting notaries are revoked,
|
|
|
|
// now, verify the evidence. accepted notarizations for another system must have at least one
|
|
// valid piece of evidence, which currently means at least one notary signature
|
|
if (externalSystem.notarizationProtocol != externalSystem.NOTARIZATION_NOTARY_CHAINID && notaryEvidence.evidence.Empty())
|
|
{
|
|
return state.Error(errorPrefix + "Requires at least some evidence to accept notarization");
|
|
}
|
|
|
|
// create an accepted notarization based on the cross-chain notarization provided
|
|
CPBaaSNotarization newNotarization = earnedNotarization;
|
|
|
|
// this should be mirrored for us to continue, if it can't be, it is invalid
|
|
if (earnedNotarization.IsMirror() || !newNotarization.SetMirror())
|
|
{
|
|
return state.Error(errorPrefix + "invalid earned notarization");
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
uint32_t height = chainActive.Height();
|
|
CProofRoot ourRoot = newNotarization.proofRoots[ASSETCHAINS_CHAINID];
|
|
|
|
CChainNotarizationData cnd;
|
|
std::vector<std::pair<CTransaction, uint256>> txes;
|
|
if (!GetNotarizationData(SystemID, cnd, &txes))
|
|
{
|
|
return state.Error(errorPrefix + "cannot locate notarization history");
|
|
}
|
|
|
|
// any notarization submitted must include a proof root of this chain that is later than the last confirmed
|
|
// notarization
|
|
uint32_t lastHeight;
|
|
|
|
if (!cnd.IsConfirmed())
|
|
{
|
|
return state.Error(errorPrefix + "earned notarization proof root cannot be verified without at least one prior confirmed for this chain");
|
|
}
|
|
|
|
CPBaaSNotarization lastConfirmedNotarization = cnd.vtx[cnd.lastConfirmed].second;
|
|
|
|
lastHeight = lastConfirmedNotarization.IsPreLaunch() ?
|
|
lastConfirmedNotarization.notarizationHeight :
|
|
lastConfirmedNotarization.proofRoots.count(ASSETCHAINS_CHAINID) ?
|
|
lastConfirmedNotarization.proofRoots.find(ASSETCHAINS_CHAINID)->second.rootHeight :
|
|
UINT32_MAX;
|
|
|
|
if (ourRoot.rootHeight <= lastHeight)
|
|
{
|
|
return state.Error(errorPrefix + "earned notarization proof root cannot be verified as later than prior confirmed for this chain");
|
|
}
|
|
|
|
// all notarization protocols in Verus do the heavy lifting on the Verus side
|
|
// as a result, the hash is currently assumed to be the default
|
|
CNativeHashWriter hw;
|
|
|
|
std::vector<unsigned char> notarizationVec = ::AsVector(earnedNotarization);
|
|
uint256 objHash = hw.write((const char *)&(notarizationVec[0]), notarizationVec.size()).GetHash();
|
|
|
|
CNotaryEvidence::EStates confirmationState = notaryEvidence.CheckSignatureConfirmation(objHash, notarySet, minimumNotariesConfirm, height);
|
|
if (confirmationState != CNotaryEvidence::EStates::STATE_CONFIRMED)
|
|
{
|
|
return state.Error(errorPrefix + "insufficient signature evidence confirming notarization");
|
|
}
|
|
|
|
int forkIdx = -1, forkPos = -1;
|
|
int priorNotarizationIdx = -1;
|
|
|
|
CPBaaSNotarization thirdNotarization;
|
|
|
|
// auto notarization protocol requires
|
|
// full quorum of notaries to sign in order to
|
|
// enter a pending notarization or confirm a prior
|
|
// notarization
|
|
if (externalSystem.notarizationProtocol == externalSystem.NOTARIZATION_AUTO)
|
|
{
|
|
std::set<int> evidenceTypes;
|
|
evidenceTypes.insert(CHAINOBJ_PROOF_ROOT);
|
|
evidenceTypes.insert(CHAINOBJ_TRANSACTION_PROOF);
|
|
evidenceTypes.insert(CHAINOBJ_HEADER);
|
|
evidenceTypes.insert(CHAINOBJ_HEADER_REF);
|
|
CCrossChainProof autoProof(notaryEvidence.GetSelectEvidence(evidenceTypes));
|
|
|
|
// in auto-notarization, the first CPartialTransactionProof should be a proof
|
|
// of the last notarization output that was confirmed on the other chain, proven by this new notarization
|
|
|
|
// the proof must contain:
|
|
// 1) A notarization merge mined or staked that references the last modulo period of blocks on this
|
|
// chain. If mined, it must have a merge mining entry with prior data matching this chain, and it must
|
|
// contain a proof of a prior staked notarization that it agrees with. If staked, it must
|
|
// contain a proof of a prior merge mined entry that it agrees with. Further, it must have a proof using
|
|
// the second notarization that further references either the last pending notarization, or the last
|
|
// confirmed notarization. If it references the last pending then the last pending will be
|
|
// be confirmed if this notarization is accepted.
|
|
//
|
|
// 2) The new pending notarization signed by all unrevoked notaries.
|
|
//
|
|
// Once verified, the prior pending notarization will be confirmed and all alternate notarizations
|
|
// closed by being spent, and the new pending notarization that closed the last will be placed onto the
|
|
// chain in a pending state.
|
|
//
|
|
|
|
// chain objects that should be present:
|
|
//
|
|
// proof root of the chain as of the most recent notarization
|
|
// being mined in. That should contain a proof root of this chain
|
|
// within the last period of BLOCK_NOTARIZATION_MODULO number
|
|
// of blocks on this chain and be either an arguably legitimate
|
|
// PoW or PoS header on the other chain.
|
|
//
|
|
// that first notarization should point to a second notarization mined or staked
|
|
// in an alternate validation type to the first and a transaction output proof,
|
|
// proven against the proof root of the first notarization.
|
|
//
|
|
// finally, that second notarization must then refer to and prove with its proof root,
|
|
// a new notarization to enter as pending. the new notarization must include proof
|
|
// of either the last confirmed or one of the last pending notarizations on this chain.
|
|
//
|
|
// each proof includes a power proof of the block before, committing to and enabling
|
|
// determination of PoW or PoS for the block containing the earned notarization, based on
|
|
// its change in work or stake from the block before. this enables a lighter weight
|
|
// proof with a header commitment vs. requiring headers initially.
|
|
|
|
//
|
|
// An accepted notarization must be signed by an unrevoked majority of necessary notaries.
|
|
//
|
|
// Notarization evidence includes an asserted proof root
|
|
//
|
|
// A partial transaction proof of an earned notarization against the initial proof root.
|
|
// A header reference proof of the block before the notarization block to enable determination
|
|
// of validation method.
|
|
//
|
|
// A partial transaction proof of the second prior and agreed earned notarization
|
|
// A header reference proof of the block before the second notarization block to enable determination
|
|
// of validation method, which must be opposite to the validation method in the first block.
|
|
//
|
|
// A partial transaction proof of the third prior and agreed earned notarization, which is proposed
|
|
// as the new pending notarization. In addition, it has a proof for a prior notarization, which
|
|
// must be either the last confirmed on this chain or one of the last pending, which will then
|
|
// become the last confirmedd.
|
|
//
|
|
|
|
CTransaction fNTx;
|
|
|
|
if (autoProof.chainObjects.size() < 7)
|
|
{
|
|
// TODO: HARDENING - enable this error and require the evidence
|
|
// return state.Error(errorPrefix + "insufficient cross chain proof for notarization");
|
|
LogPrint("notarization", "%s: insufficient cross chain proof for notarization\n");
|
|
if (cnd.forks.size() != 1)
|
|
{
|
|
return state.Error(errorPrefix + "cannot resolve conflict without sufficient evidence");
|
|
}
|
|
thirdNotarization = cnd.vtx[cnd.forks[0].back()].second;
|
|
}
|
|
else
|
|
{
|
|
for (auto oneObjRef : autoProof.chainObjects)
|
|
{
|
|
if (!oneObjRef)
|
|
{
|
|
return state.Error(errorPrefix + "null cross chain proof for notarization");
|
|
}
|
|
}
|
|
|
|
COptCCParams tmpP;
|
|
|
|
CProofRoot firstProofRoot;
|
|
CPartialTransactionProof firstProof;
|
|
if (autoProof.chainObjects[0]->objectType != CHAINOBJ_PROOF_ROOT ||
|
|
!(firstProofRoot = ((CChainObject<CProofRoot> *)autoProof.chainObjects[0])->object).IsValid() ||
|
|
autoProof.chainObjects[1]->objectType != CHAINOBJ_TRANSACTION_PROOF ||
|
|
!(firstProof = ((CChainObject<CPartialTransactionProof> *)autoProof.chainObjects[1])->object).IsValid() ||
|
|
firstProof.CheckPartialTransaction(fNTx) != firstProofRoot.stateRoot ||
|
|
autoProof.chainObjects[2]->objectType != CHAINOBJ_HEADER_REF)
|
|
{
|
|
return state.Error(errorPrefix + "invalid cross chain proof for notarization");
|
|
}
|
|
|
|
CBlockHeaderProof priorHeaderProof = ((CChainObject<CBlockHeaderProof> *)autoProof.chainObjects[2])->object;
|
|
CChainPower thisPower = CChainPower::ExpandCompactPower(firstProof.GetBlockPower());
|
|
CChainPower priorPower = CChainPower::ExpandCompactPower(priorHeaderProof.GetBlockPower());
|
|
bool firstIsStake = (priorPower - thisPower).chainStake > arith_uint256(0);
|
|
|
|
// get the proof root to use for the next proof from firstProof notarization output
|
|
int outIdx;
|
|
CPBaaSNotarization firstNotarization(fNTx, &outIdx), secondNotarization;
|
|
CPartialTransactionProof secondProof;
|
|
|
|
if (!firstNotarization.IsValid() ||
|
|
!fNTx.vout[outIdx].scriptPubKey.IsPayToCryptoCondition(tmpP) ||
|
|
!tmpP.IsValid() ||
|
|
tmpP.evalCode != EVAL_EARNEDNOTARIZATION ||
|
|
!firstNotarization.proofRoots.count(SystemID) ||
|
|
firstNotarization.proofRoots[SystemID].rootHeight >= firstProofRoot.rootHeight ||
|
|
autoProof.chainObjects[3]->objectType != CHAINOBJ_TRANSACTION_PROOF ||
|
|
!(secondProof = ((CChainObject<CPartialTransactionProof> *)autoProof.chainObjects[3])->object).IsValid() ||
|
|
secondProof.TransactionHash() != firstNotarization.prevNotarization.hash ||
|
|
secondProof.CheckPartialTransaction(fNTx) != firstNotarization.proofRoots[SystemID].stateRoot ||
|
|
fNTx.vout.size() <= firstNotarization.prevNotarization.n ||
|
|
!fNTx.vout[firstNotarization.prevNotarization.n].scriptPubKey.IsPayToCryptoCondition(tmpP) ||
|
|
!tmpP.IsValid() ||
|
|
tmpP.evalCode != EVAL_EARNEDNOTARIZATION ||
|
|
!tmpP.vData.size() ||
|
|
!(secondNotarization = CPBaaSNotarization(tmpP.vData[0])).IsValid() ||
|
|
!secondNotarization.proofRoots.count(SystemID) ||
|
|
::AsVector(secondNotarization) != ::AsVector(earnedNotarization) ||
|
|
autoProof.chainObjects[4]->objectType != CHAINOBJ_HEADER_REF)
|
|
{
|
|
return state.Error(errorPrefix + "invalid cross chain proof for notarization - first or second notarization");
|
|
}
|
|
|
|
priorHeaderProof = ((CChainObject<CBlockHeaderProof> *)autoProof.chainObjects[4])->object;
|
|
thisPower = CChainPower::ExpandCompactPower(secondProof.GetBlockPower());
|
|
priorPower = CChainPower::ExpandCompactPower(priorHeaderProof.GetBlockPower());
|
|
bool secondIsStake = (priorPower - thisPower).chainStake > arith_uint256(0);
|
|
|
|
CPartialTransactionProof thirdProof;
|
|
|
|
if (secondNotarization.proofRoots[SystemID].rootHeight >= firstNotarization.proofRoots[SystemID].rootHeight ||
|
|
autoProof.chainObjects[5]->objectType != CHAINOBJ_TRANSACTION_PROOF ||
|
|
!(thirdProof = ((CChainObject<CPartialTransactionProof> *)autoProof.chainObjects[5])->object).IsValid() ||
|
|
thirdProof.TransactionHash() != secondNotarization.prevNotarization.hash ||
|
|
thirdProof.CheckPartialTransaction(fNTx) != secondNotarization.proofRoots[SystemID].stateRoot ||
|
|
fNTx.vout.size() <= secondNotarization.prevNotarization.n ||
|
|
!fNTx.vout[secondNotarization.prevNotarization.n].scriptPubKey.IsPayToCryptoCondition(tmpP) ||
|
|
!tmpP.IsValid() ||
|
|
!tmpP.vData.size() ||
|
|
!(thirdNotarization = CPBaaSNotarization(tmpP.vData[0])).IsValid() ||
|
|
(tmpP.evalCode != EVAL_EARNEDNOTARIZATION &&
|
|
!(thirdNotarization.IsDefinitionNotarization() || thirdNotarization.IsBlockOneNotarization())) ||
|
|
(tmpP.evalCode == EVAL_EARNEDNOTARIZATION &&
|
|
(!(thirdNotarization.proofRoots.count(SystemID) && thirdNotarization.proofRoots.count(ASSETCHAINS_CHAINID)) ||
|
|
!thirdNotarization.SetMirror())) ||
|
|
autoProof.chainObjects[6]->objectType != CHAINOBJ_HEADER_REF)
|
|
{
|
|
return state.Error(errorPrefix + "invalid cross chain proof for notarization - second notarization");
|
|
}
|
|
|
|
priorHeaderProof = ((CChainObject<CBlockHeaderProof> *)autoProof.chainObjects[6])->object;
|
|
thisPower = CChainPower::ExpandCompactPower(thirdProof.GetBlockPower());
|
|
priorPower = CChainPower::ExpandCompactPower(priorHeaderProof.GetBlockPower());
|
|
bool thirdIsStake = (priorPower - thisPower).chainStake > arith_uint256(0);
|
|
|
|
if (lastConfirmedNotarization.notarizationHeight > (VERUS_MIN_STAKEAGE << 1) && (firstIsStake == secondIsStake || secondIsStake == thirdIsStake))
|
|
{
|
|
return state.Error(errorPrefix + "invalid validation alternation for one or more notarizations");
|
|
}
|
|
}
|
|
|
|
// fNTx & thirdNotarization must match the last confirmed or one pending. if it matches one pending, that one will be confirmed
|
|
auto serializedVec = ::AsVector(thirdNotarization);
|
|
if (serializedVec != ::AsVector(cnd.vtx[cnd.lastConfirmed].second))
|
|
{
|
|
// if the third notarization does not equal the last confirmed notarization, it
|
|
// must equal a last pending notarization to confirm. that should always be a
|
|
// non-confirmed entry in a fork of the notarization data, if it is present
|
|
for (int i = 0; i < cnd.forks.size(); i++)
|
|
{
|
|
if (cnd.forks[i].size() > 1)
|
|
{
|
|
for (int j = cnd.forks[i].size() - 1; j >= 0; j--)
|
|
{
|
|
if (serializedVec == ::AsVector(cnd.vtx[cnd.forks[i][j]].second))
|
|
{
|
|
forkIdx = i;
|
|
forkPos = j;
|
|
break;
|
|
}
|
|
}
|
|
if (forkIdx > -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if it wasn't the confirmed entry and wasn't pending confirmation, it is referring to an
|
|
// invalid predecessor and is therefore invalid
|
|
if (forkIdx == -1)
|
|
{
|
|
return state.Error(errorPrefix + "invalid prior notarization - neither pending nor confirmed");
|
|
}
|
|
}
|
|
|
|
// if we should confirm a notarization, it will be the first in forkIdx
|
|
if (forkIdx > -1)
|
|
{
|
|
printf("ready to confirm notarization tx: %s, output %d\n", cnd.vtx[cnd.forks[forkIdx][forkPos]].first.hash.GetHex().c_str(), cnd.vtx[cnd.forks[forkIdx][forkPos]].first.n);
|
|
priorNotarizationIdx = cnd.forks[forkIdx][forkPos];
|
|
}
|
|
else
|
|
{
|
|
priorNotarizationIdx = cnd.lastConfirmed;
|
|
}
|
|
// earned notarization is entered as pending if we get here
|
|
}
|
|
else
|
|
{
|
|
if (cnd.forks.size() != 1)
|
|
{
|
|
LogPrint("notarization", "%s: insufficient cross chain proof for non-auto notarization\n");
|
|
return state.Error(errorPrefix + "cannot resolve conflict without sufficient evidence");
|
|
}
|
|
priorNotarizationIdx = cnd.forks[0].back();
|
|
thirdNotarization = cnd.vtx[priorNotarizationIdx].second;
|
|
thirdNotarization = cnd.vtx[cnd.forks[0].size() > 1 ? cnd.forks[0][1] : cnd.lastConfirmed].second;
|
|
}
|
|
|
|
// all proof roots in a prior, agreed notarization must be present and have moved forward
|
|
for (auto &oneProofRoot : thirdNotarization.proofRoots)
|
|
{
|
|
if (!newNotarization.proofRoots.count(oneProofRoot.first) ||
|
|
newNotarization.proofRoots[oneProofRoot.first].rootHeight <= oneProofRoot.second.rootHeight)
|
|
{
|
|
return state.Error(errorPrefix + "insufficient progress in chain " + EncodeDestination(CIdentityID(oneProofRoot.first)) + " to accept notarization");
|
|
}
|
|
}
|
|
|
|
auto mmv = chainActive.GetMMV();
|
|
mmv.resize(ourRoot.rootHeight + 1);
|
|
|
|
// we only create accepted notarizations for notarizations that are earned for this chain on another system
|
|
// currently, we support ethereum and PBaaS types.
|
|
if (!newNotarization.proofRoots.count(SystemID) ||
|
|
!newNotarization.proofRoots.count(ASSETCHAINS_CHAINID) ||
|
|
!(ourRoot = newNotarization.proofRoots[ASSETCHAINS_CHAINID]).IsValid() ||
|
|
ourRoot.rootHeight > height ||
|
|
ourRoot.blockHash != chainActive[ourRoot.rootHeight]->GetBlockHash() ||
|
|
ourRoot.stateRoot != mmv.GetRoot() ||
|
|
(ourRoot.type != ourRoot.TYPE_PBAAS && ourRoot.type != ourRoot.TYPE_ETHEREUM))
|
|
{
|
|
return state.Error(errorPrefix + "can only create accepted notarization from notarization with valid proof root of this chain");
|
|
}
|
|
|
|
// ensure that the data present is valid, as of the height
|
|
CCoinbaseCurrencyState oldCurState = ConnectedChains.GetCurrencyState(ASSETCHAINS_CHAINID, ourRoot.rootHeight);
|
|
if (!oldCurState.IsValid() ||
|
|
::GetHash(oldCurState) != ::GetHash(earnedNotarization.currencyState))
|
|
{
|
|
return state.Error(errorPrefix + "currency state is invalid in accepted notarization. is:\n" +
|
|
newNotarization.currencyState.ToUniValue().write(1,2) +
|
|
"\nshould be:\n" +
|
|
oldCurState.ToUniValue().write(1,2) + "\n");
|
|
}
|
|
|
|
// ensure that all locally provable info is valid as of our root height
|
|
// and determine if the new notarization should be already finalized or not
|
|
for (auto &oneCur : newNotarization.currencyStates)
|
|
{
|
|
if (oneCur.first == SystemID)
|
|
{
|
|
return state.Error(errorPrefix + "cannot accept redundant currency state in notarization for " + EncodeDestination(CIdentityID(SystemID)));
|
|
}
|
|
else if (oneCur.first != ASSETCHAINS_CHAINID)
|
|
{
|
|
// see if this currency is on our chain, and if so, it must be correct as of the proof root of this chain
|
|
CCurrencyDefinition curDef = ConnectedChains.GetCachedCurrency(oneCur.first);
|
|
// we must have all currencies
|
|
if (!curDef.IsValid())
|
|
{
|
|
return state.Error(errorPrefix + "all currencies in accepted notarizatoin must be registered on this chain");
|
|
}
|
|
// if the currency is not from this chain, we cannot validate it
|
|
if (curDef.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
continue;
|
|
}
|
|
// ensure that the data present is valid, as of the height
|
|
oldCurState = ConnectedChains.GetCurrencyState(oneCur.first, ourRoot.rootHeight);
|
|
if (!oldCurState.IsValid() ||
|
|
::GetHash(oldCurState) != ::GetHash(oneCur.second))
|
|
{
|
|
return state.Error(errorPrefix + "currecy state is invalid in accepted notarization. is:\n" +
|
|
oneCur.second.ToUniValue().write(1,2) +
|
|
"\nshould be:\n" +
|
|
oldCurState.ToUniValue().write(1,2) + "\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// already checked above
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (auto &oneRoot : newNotarization.proofRoots)
|
|
{
|
|
if (oneRoot.first == SystemID)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// see if this currency is on our chain, and if so, it must be correct as of the proof root of this chain
|
|
CCurrencyDefinition curDef = ConnectedChains.GetCachedCurrency(oneRoot.first);
|
|
// we must have all currencies in this notarization registered
|
|
if (!curDef.IsValid())
|
|
{
|
|
return state.Error(errorPrefix + "all currencies in accepted notarizatoin must be registered on this chain");
|
|
}
|
|
uint160 curDefID = curDef.GetID();
|
|
|
|
// only check other currencies on this chain, not the main chain itself
|
|
if (curDefID != ASSETCHAINS_CHAINID && curDef.systemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
return state.Error(errorPrefix + "proof roots are not accepted for token currencies");
|
|
}
|
|
}
|
|
}
|
|
|
|
// now create the new notarization, add the proof, finalize if appropriate, and finish
|
|
|
|
// add spend of prior notarization and then outputs
|
|
CPBaaSNotarization lastUnspentNotarization;
|
|
uint256 lastTxId;
|
|
int32_t lastTxOutNum;
|
|
CTransaction lastTx;
|
|
if (!lastUnspentNotarization.GetLastUnspentNotarization(SystemID, lastTxId, lastTxOutNum, &lastTx))
|
|
{
|
|
return state.Error(errorPrefix + "invalid prior notarization");
|
|
}
|
|
|
|
// add prior unspent accepted notarization as our input
|
|
txBuilder.AddTransparentInput(CUTXORef(lastTxId, lastTxOutNum), lastTx.vout[lastTxOutNum].scriptPubKey, lastTx.vout[lastTxOutNum].nValue);
|
|
|
|
// if we are going to confirm a prior notarization, we also should spend its prior
|
|
// pending finalizations and all conflicting as well
|
|
CUTXORef newConfirmedOutput;
|
|
|
|
std::map<CUTXORef, int> notarizationOutputMap;
|
|
|
|
for (int i = 0; i < cnd.vtx.size(); i++)
|
|
{
|
|
notarizationOutputMap.insert(std::make_pair(cnd.vtx[i].first, i));
|
|
}
|
|
|
|
std::vector<std::vector<CInputDescriptor>> spendsToClose(cnd.vtx.size());
|
|
std::vector<CInputDescriptor> extraSpends;
|
|
|
|
// now, figure out which elements we spend, whether confirming or invalidating
|
|
std::set<int> confirmedOutputNums;
|
|
std::set<int> invalidatedOutputNums;
|
|
|
|
if (forkIdx > -1)
|
|
{
|
|
if (!cnd.CorrelatedFinalizationSpends(txes, spendsToClose, extraSpends))
|
|
{
|
|
return state.Error(errorPrefix + "error correlating spends");
|
|
}
|
|
|
|
if (!cnd.CalculateConfirmation(priorNotarizationIdx, confirmedOutputNums, invalidatedOutputNums))
|
|
{
|
|
return state.Error(errorPrefix + "error calculating confirmation");
|
|
}
|
|
|
|
// any fork that does not match the confirmed fork up to the same index will be
|
|
// considered invalid from all of its entries to its end
|
|
for (auto oneConfirmedIdx : confirmedOutputNums)
|
|
{
|
|
if (oneConfirmedIdx != priorNotarizationIdx)
|
|
{
|
|
for (auto &oneInput : spendsToClose[oneConfirmedIdx])
|
|
{
|
|
txBuilder.AddTransparentInput(oneInput.txIn.prevout, oneInput.scriptPubKey, oneInput.nValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto oneInvalidatedIdx : invalidatedOutputNums)
|
|
{
|
|
for (auto &oneInput : spendsToClose[oneInvalidatedIdx])
|
|
{
|
|
txBuilder.AddTransparentInput(oneInput.txIn.prevout, oneInput.scriptPubKey, oneInput.nValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
std::vector<CTxDestination> dests;
|
|
|
|
// make the accepted notarization output
|
|
cp = CCinit(&CC, EVAL_ACCEPTEDNOTARIZATION);
|
|
|
|
if (externalSystem.notarizationProtocol == externalSystem.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(externalSystem.GetID())});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
|
|
txBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_ACCEPTEDNOTARIZATION, dests, 1, &newNotarization)), 0);
|
|
|
|
// now add the notary evidence and finalization that uses it to assert validity
|
|
// make the evidence notarization output
|
|
cp = CCinit(&CC, EVAL_NOTARY_EVIDENCE);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
CDataStream ds(SER_DISK, PROTOCOL_VERSION);
|
|
int serSize = GetSerializeSize(ds, notaryEvidence);
|
|
|
|
std::vector<int32_t> evidenceOuts;
|
|
|
|
// the value should be considered for reduction
|
|
if (serSize > CScript::MAX_SCRIPT_ELEMENT_SIZE)
|
|
{
|
|
auto evidenceVec = notaryEvidence.BreakApart(CScript::MAX_SCRIPT_ELEMENT_SIZE - 128);
|
|
if (!evidenceVec.size())
|
|
{
|
|
LogPrintf("%s: failed to package evidence from system %s\n", __func__, EncodeDestination(CIdentityID(SystemID)).c_str());
|
|
return false;
|
|
}
|
|
for (auto &oneProof : evidenceVec)
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
evidenceOuts.push_back(txBuilder.mtx.vout.size());
|
|
txBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CNotaryEvidence>(EVAL_NOTARY_EVIDENCE, dests, 1, &oneProof)), 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
evidenceOuts.push_back(txBuilder.mtx.vout.size());
|
|
txBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CNotaryEvidence>(EVAL_NOTARY_EVIDENCE, dests, 1, ¬aryEvidence)), 0);
|
|
}
|
|
|
|
// now make 1 or two finalization outputs
|
|
cp = CCinit(&CC, EVAL_FINALIZE_NOTARIZATION);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
// create the pending finalization for our new notarization first
|
|
CObjectFinalization of = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION, newNotarization.currencyID, uint256(), txBuilder.mtx.vout.size() - (1 + evidenceOuts.size()), height + 1);
|
|
of.evidenceOutputs = evidenceOuts;
|
|
txBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &of)), 0);
|
|
|
|
// if there are outputs to close for this entry, it will first be
|
|
// a finalization, then evidence
|
|
COptCCParams fP;
|
|
|
|
for (auto &oneInput : spendsToClose[priorNotarizationIdx])
|
|
{
|
|
txBuilder.AddTransparentInput(oneInput.txIn.prevout, oneInput.scriptPubKey, oneInput.nValue);
|
|
}
|
|
|
|
if (spendsToClose[priorNotarizationIdx].size() &&
|
|
spendsToClose[priorNotarizationIdx][0].scriptPubKey.IsPayToCryptoCondition(fP) &&
|
|
fP.IsValid() &&
|
|
fP.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
fP.vData.size() &&
|
|
(of = CObjectFinalization(fP.vData[0])).IsValid())
|
|
{
|
|
if (of.output.hash.IsNull())
|
|
{
|
|
of.output.hash = spendsToClose[priorNotarizationIdx][0].txIn.prevout.hash;
|
|
}
|
|
// inputs will be finalization followed by evidence
|
|
// outputs are cleared to carry it forward, as they are on inputs
|
|
of.evidenceInputs.clear();
|
|
of.evidenceOutputs.clear();
|
|
if (spendsToClose[priorNotarizationIdx].size())
|
|
{
|
|
for (int evidenceNIn = txBuilder.mtx.vin.size() - spendsToClose[priorNotarizationIdx].size();
|
|
evidenceNIn < txBuilder.mtx.vin.size();
|
|
evidenceNIn++)
|
|
{
|
|
of.evidenceInputs.push_back(evidenceNIn);
|
|
}
|
|
}
|
|
of.minFinalizationHeight = height;
|
|
}
|
|
else
|
|
{
|
|
// we have no valid finalization to close for this list of spends
|
|
// spend whatever we have and make an output finalization
|
|
of = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION,
|
|
newNotarization.currencyID,
|
|
cnd.vtx[priorNotarizationIdx].first.hash,
|
|
cnd.vtx[priorNotarizationIdx].first.n,
|
|
height);
|
|
}
|
|
of.SetConfirmed();
|
|
txBuilder.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &of)), 0);
|
|
|
|
/* UniValue univTx(UniValue::VOBJ);
|
|
TxToUniv(txBuilder.mtx, uint256(), univTx);
|
|
printf("%s: txBuilder returning:\n%s\n", __func__, univTx.write(1,2).c_str());
|
|
// */
|
|
|
|
return true;
|
|
}
|
|
|
|
extern void CopyNodeStats(std::vector<CNodeStats>& vstats);
|
|
|
|
std::vector<CNodeData> GetGoodNodes(int maxNum)
|
|
{
|
|
// good nodes ordered by time connected
|
|
std::map<int64_t, CNode *> goodNodes;
|
|
std::vector<CNodeData> retVal;
|
|
|
|
LOCK(cs_vNodes);
|
|
for (auto oneNode : vNodes)
|
|
{
|
|
if (!oneNode->fInbound)
|
|
{
|
|
if (oneNode->fWhitelisted)
|
|
{
|
|
goodNodes.insert(std::make_pair(oneNode->nTimeConnected, oneNode));
|
|
}
|
|
else if (oneNode->GetTotalBytesRecv() > (1024 * 1024))
|
|
{
|
|
goodNodes.insert(std::make_pair(oneNode->nTimeConnected, oneNode));
|
|
}
|
|
}
|
|
}
|
|
if (goodNodes.size() > 1)
|
|
{
|
|
seed_insecure_rand();
|
|
for (auto &oneNode : goodNodes)
|
|
{
|
|
if (insecure_rand() & 1)
|
|
{
|
|
retVal.push_back(CNodeData(oneNode.second->addr.ToStringIPPort(), oneNode.second->hashPaymentAddress));
|
|
if (retVal.size() >= maxNum)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!retVal.size())
|
|
{
|
|
retVal.push_back(CNodeData(goodNodes.begin()->second->addr.ToStringIPPort(), goodNodes.begin()->second->hashPaymentAddress));
|
|
}
|
|
}
|
|
else if (goodNodes.size())
|
|
{
|
|
retVal.push_back(CNodeData(goodNodes.begin()->second->addr.ToStringIPPort(), goodNodes.begin()->second->hashPaymentAddress));
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// create a notarization that is validated as part of the block, generally benefiting the miner or staker if the
|
|
// cross notarization is valid
|
|
bool CPBaaSNotarization::CreateEarnedNotarization(const CRPCChainData &externalSystem,
|
|
const CTransferDestination &Proposer,
|
|
bool isStake,
|
|
CValidationState &state,
|
|
std::vector<CTxOut> &txOutputs,
|
|
CPBaaSNotarization ¬arization)
|
|
{
|
|
std::string errorPrefix(strprintf("%s: ", __func__));
|
|
|
|
uint32_t height;
|
|
uint160 SystemID;
|
|
const CCurrencyDefinition &systemDef = externalSystem.chainDefinition;
|
|
SystemID = externalSystem.chainDefinition.GetID();
|
|
|
|
CChainNotarizationData cnd;
|
|
std::vector<std::pair<CTransaction, uint256>> txes;
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
height = chainActive.Height();
|
|
|
|
// we can only create an earned notarization for a notary chain, so there must be a notary chain and a network connection to it
|
|
// we also need to ensure that our notarization would be the first notarization in this notary block period with which we agree.
|
|
if (!externalSystem.IsValid() || externalSystem.rpcHost.empty())
|
|
{
|
|
// technically not a real error
|
|
return state.Error("no-notary");
|
|
}
|
|
|
|
if (!GetNotarizationData(SystemID, cnd, &txes) || !cnd.IsConfirmed())
|
|
{
|
|
return state.Error(errorPrefix + "no prior confirmed notarization found");
|
|
}
|
|
}
|
|
|
|
bool isGatewayFirstContact = systemDef.IsGateway() && cnd.vtx[cnd.lastConfirmed].second.IsDefinitionNotarization();
|
|
|
|
// all we really want is the system proof roots for each notarization to make the JSON for the API smaller
|
|
UniValue proofRootsUni(UniValue::VARR);
|
|
for (auto &oneNot : cnd.vtx)
|
|
{
|
|
auto rootIt = oneNot.second.proofRoots.find(SystemID);
|
|
if (rootIt != oneNot.second.proofRoots.end())
|
|
{
|
|
proofRootsUni.push_back(rootIt->second.ToUniValue());
|
|
}
|
|
else
|
|
{
|
|
auto oneProofRoot = CProofRoot();
|
|
oneProofRoot.rootHeight = 1;
|
|
proofRootsUni.push_back(oneProofRoot.ToUniValue());
|
|
}
|
|
}
|
|
|
|
if (!proofRootsUni.size() && !isGatewayFirstContact)
|
|
{
|
|
return state.Error(errorPrefix + "no valid prior state root found");
|
|
}
|
|
|
|
// call notary to determine the prior notarization that we agree with
|
|
UniValue params(UniValue::VARR);
|
|
|
|
UniValue oneParam(UniValue::VOBJ);
|
|
if (proofRootsUni.size())
|
|
{
|
|
oneParam.push_back(Pair("proofroots", proofRootsUni));
|
|
}
|
|
if (!isGatewayFirstContact)
|
|
{
|
|
oneParam.push_back(Pair("lastconfirmed", cnd.lastConfirmed));
|
|
}
|
|
params.push_back(oneParam);
|
|
|
|
//printf("%s: about to get cross notarization with %lu notarizations found\n", __func__, cnd.vtx.size());
|
|
|
|
UniValue result;
|
|
try
|
|
{
|
|
result = find_value(RPCCallRoot("getbestproofroot", params), "result");
|
|
} catch (exception e)
|
|
{
|
|
result = NullUniValue;
|
|
}
|
|
|
|
int32_t notaryIdx = uni_get_int(find_value(result, "bestindex"), isGatewayFirstContact ? 0 : -1);
|
|
|
|
UniValue lastConfirmedUni = find_value(result, "lastconfirmedproofroot");
|
|
CProofRoot lastConfirmedProofRoot;
|
|
if (!lastConfirmedUni.isNull())
|
|
{
|
|
lastConfirmedProofRoot = CProofRoot(lastConfirmedUni);
|
|
}
|
|
|
|
if (result.isNull() || notaryIdx == -1)
|
|
{
|
|
return state.Error(result.isNull() ? "no-notary" : "no-matching-proof-roots-found");
|
|
}
|
|
|
|
CProofRoot lastStableProofRoot(find_value(result, "laststableproofroot"));
|
|
|
|
// work around race condition or Infura API issue under investigation in ETH bridge, where it does not confirm
|
|
// one of our proof roots, while at the same time returning identical data in laststableproofroot.
|
|
if (!lastConfirmedProofRoot.IsValid() && lastStableProofRoot.IsValid())
|
|
{
|
|
for (int i = cnd.vtx.size() - 1; i >= 0; i--)
|
|
{
|
|
auto it = cnd.vtx[i].second.proofRoots.find(SystemID);
|
|
if (it != cnd.vtx[i].second.proofRoots.end())
|
|
{
|
|
if (it->second.rootHeight < lastStableProofRoot.rootHeight)
|
|
{
|
|
break;
|
|
}
|
|
else if (it->second.rootHeight == lastStableProofRoot.rootHeight &&
|
|
it->second == lastStableProofRoot && notaryIdx != i)
|
|
{
|
|
LogPrintf("%s: notarization was rejected with identical proof root to last stable: %s\n", __func__, cnd.vtx[i].second.ToUniValue().write(1,2).c_str());
|
|
notaryIdx = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now, we have the index for the transaction and notarization we agree with, a list of those we consider invalid,
|
|
// and the most recent notarization to use when creating the new one
|
|
const CTransaction &priorNotarizationTx = txes[notaryIdx].first;
|
|
uint256 priorBlkHash = txes[notaryIdx].second;
|
|
const CUTXORef &priorUTXO = cnd.vtx[notaryIdx].first;
|
|
const CPBaaSNotarization &priorNotarization = cnd.vtx[notaryIdx].second;
|
|
|
|
// find out the block height holding the last notarization we agree with
|
|
auto mapBlockIt = mapBlockIndex.find(priorBlkHash);
|
|
if (mapBlockIt == mapBlockIndex.end() || !chainActive.Contains(mapBlockIt->second))
|
|
{
|
|
return state.Error(errorPrefix + "prior notarization not in blockchain");
|
|
}
|
|
|
|
// first determine if the prior notarization we agree with would make this one moot
|
|
int blockPeriodNumber = (height + 1) / BLOCK_NOTARIZATION_MODULO;
|
|
int priorBlockPeriod = mapBlockIt->second->GetHeight() / BLOCK_NOTARIZATION_MODULO;
|
|
|
|
// for decentralized notarization, we must alternate between proof of stake and proof of work blocks
|
|
// to confirm a prior earned notarization
|
|
if (blockPeriodNumber <= priorBlockPeriod ||
|
|
(height > (VERUS_MIN_STAKEAGE << 1) &&
|
|
(ConnectedChains.ThisChain().notarizationProtocol == CCurrencyDefinition::NOTARIZATION_AUTO &&
|
|
((isStake && mapBlockIt->second->IsVerusPOSBlock()) || (!isStake && !mapBlockIt->second->IsVerusPOSBlock())))))
|
|
{
|
|
return state.Error("ineligible");
|
|
}
|
|
|
|
notarization = priorNotarization;
|
|
notarization.SetBlockOneNotarization(false);
|
|
notarization.SetDefinitionNotarization(false);
|
|
notarization.SetSameChain(false);
|
|
notarization.SetPreLaunch(false);
|
|
notarization.SetLaunchCleared(true);
|
|
notarization.SetLaunchConfirmed(true);
|
|
notarization.proposer = Proposer;
|
|
notarization.prevHeight = priorNotarization.notarizationHeight;
|
|
|
|
// get the latest notarization information for the new, earned notarization
|
|
// one system may provide one proof root and multiple currency states
|
|
CProofRoot latestProofRoot = lastConfirmedProofRoot.IsValid() ? lastConfirmedProofRoot : CProofRoot(find_value(result, "laststableproofroot"));
|
|
|
|
if (!latestProofRoot.IsValid() || notarization.proofRoots[latestProofRoot.systemID].rootHeight >= latestProofRoot.rootHeight)
|
|
{
|
|
return state.Error("no-new-stable-proof-root");
|
|
}
|
|
|
|
notarization.proofRoots[latestProofRoot.systemID] = latestProofRoot;
|
|
notarization.notarizationHeight = latestProofRoot.rootHeight;
|
|
|
|
UniValue currencyStatesUni = find_value(result, "currencystates");
|
|
|
|
if (!systemDef.IsGateway() && !(currencyStatesUni.isArray() && currencyStatesUni.size()))
|
|
{
|
|
return state.Error(errorPrefix + "invalid or missing currency state data from notary");
|
|
}
|
|
|
|
params = UniValue(UniValue::VARR);
|
|
params.push_back(EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)));
|
|
try
|
|
{
|
|
result = find_value(RPCCallRoot("getnotarizationdata", params), "result");
|
|
} catch (exception e)
|
|
{
|
|
result = NullUniValue;
|
|
}
|
|
CChainNotarizationData crosschainCND;
|
|
if (result.isNull() ||
|
|
!(crosschainCND = CChainNotarizationData(result)).IsValid() ||
|
|
(!externalSystem.chainDefinition.IsGateway() && !crosschainCND.IsConfirmed()))
|
|
{
|
|
LogPrint("notarization", "Unable to get notarization data from %s\n", EncodeDestination(CIdentityID(externalSystem.GetID())).c_str());
|
|
return state.Error("invalid crosschain notarization data");
|
|
}
|
|
|
|
// take the lock again, now that we're back from calling out
|
|
LOCK2(cs_main, mempool.cs);
|
|
|
|
// if height changed, we need to fail and possibly try again
|
|
if (height != chainActive.Height())
|
|
{
|
|
return state.Error("stale-block");
|
|
}
|
|
|
|
if (crosschainCND.vtx.size())
|
|
{
|
|
int prevNotarizationIdx;
|
|
CPBaaSNotarization lastPBN;
|
|
for (prevNotarizationIdx = crosschainCND.vtx.size() - 1; prevNotarizationIdx >= 0; prevNotarizationIdx--)
|
|
{
|
|
lastPBN = crosschainCND.vtx[prevNotarizationIdx].second;
|
|
|
|
// TODO: HARDENING check all currency states on this chain in last valid as well
|
|
std::map<uint160, CProofRoot>::iterator pIT = lastPBN.proofRoots.find(ASSETCHAINS_CHAINID);
|
|
if (pIT != lastPBN.proofRoots.end() &&
|
|
(!priorNotarization.proofRoots.count(ASSETCHAINS_CHAINID) ||
|
|
pIT->second.rootHeight <= priorNotarization.proofRoots.find(ASSETCHAINS_CHAINID)->second.rootHeight) &&
|
|
CProofRoot::GetProofRoot(pIT->second.rootHeight) == pIT->second)
|
|
{
|
|
break;
|
|
}
|
|
else if (pIT == crosschainCND.vtx[prevNotarizationIdx].second.proofRoots.end() &&
|
|
!prevNotarizationIdx &&
|
|
(crosschainCND.vtx[prevNotarizationIdx].second.IsDefinitionNotarization() || crosschainCND.vtx[prevNotarizationIdx].second.IsLaunchCleared()))
|
|
{
|
|
// use the 0th element if no proof root and it is definition or start, since it has no proof root to be wrong
|
|
break;
|
|
}
|
|
}
|
|
if (prevNotarizationIdx >= 0 &&
|
|
lastPBN.SetMirror(false) &&
|
|
!lastPBN.IsMirror())
|
|
{
|
|
if (LogAcceptCategory("notarization") && LogAcceptCategory("verbose"))
|
|
{
|
|
std::vector<unsigned char> checkHex = ::AsVector(lastPBN);
|
|
LogPrintf("%s: hex of notarization: %s\n", __func__, HexBytes(&(checkHex[0]), checkHex.size()).c_str());
|
|
printf("%s: hex of notarization: %s\n", __func__, HexBytes(&(checkHex[0]), checkHex.size()).c_str());
|
|
}
|
|
|
|
CNativeHashWriter hw;
|
|
hw << lastPBN;
|
|
notarization.hashPrevCrossNotarization = hw.GetHash();
|
|
}
|
|
else
|
|
{
|
|
notarization.hashPrevCrossNotarization.SetNull();
|
|
}
|
|
}
|
|
|
|
notarization.currencyStates.clear();
|
|
for (int i = 0; i < currencyStatesUni.size(); i++)
|
|
{
|
|
CCoinbaseCurrencyState oneCurState(currencyStatesUni[i]);
|
|
CCurrencyDefinition oneCurDef;
|
|
if (!oneCurState.IsValid())
|
|
{
|
|
return state.Error(errorPrefix + "invalid or missing currency state data from notary");
|
|
}
|
|
if (!(oneCurDef = ConnectedChains.GetCachedCurrency(oneCurState.GetID())).IsValid())
|
|
{
|
|
// if we don't have the currency for the state specified, and it isn't critical, ignore
|
|
if (oneCurDef.GetID() == SystemID)
|
|
{
|
|
return state.Error(errorPrefix + "system currency invalid - possible corruption");
|
|
}
|
|
continue;
|
|
}
|
|
if (oneCurDef.systemID == SystemID)
|
|
{
|
|
uint160 oneCurDefID = oneCurDef.GetID();
|
|
if (notarization.currencyID == oneCurDefID)
|
|
{
|
|
notarization.currencyState = oneCurState;
|
|
}
|
|
else
|
|
{
|
|
notarization.currencyStates[oneCurDefID] = oneCurState;
|
|
}
|
|
}
|
|
}
|
|
notarization.currencyStates[ASSETCHAINS_CHAINID] = ConnectedChains.GetCurrencyState(height);
|
|
if (!systemDef.GatewayConverterID().IsNull())
|
|
{
|
|
notarization.currencyStates[systemDef.GatewayConverterID()] = ConnectedChains.GetCurrencyState(systemDef.GatewayConverterID(), height);
|
|
}
|
|
|
|
// add this blockchain's info, based on the requested height
|
|
uint160 thisChainID = ConnectedChains.ThisChain().GetID();
|
|
notarization.proofRoots[thisChainID] = CProofRoot::GetProofRoot(height);
|
|
|
|
// add currency states that we should include and then we're done
|
|
// currency states to include are either a gateway currency indicated by the
|
|
// gateway or our gateway converter for our PBaaS chain
|
|
uint160 gatewayConverterID = systemDef.GatewayConverterID();
|
|
if (systemDef.IsGateway() && (!systemDef.GatewayConverterID().IsNull()))
|
|
{
|
|
gatewayConverterID = systemDef.GatewayConverterID();
|
|
}
|
|
else if (SystemID == ConnectedChains.FirstNotaryChain().chainDefinition.GetID() && !ConnectedChains.ThisChain().GatewayConverterID().IsNull())
|
|
{
|
|
gatewayConverterID = ConnectedChains.ThisChain().GatewayConverterID();
|
|
}
|
|
if (!gatewayConverterID.IsNull())
|
|
{
|
|
// get the gateway converter currency from the gateway definition
|
|
CChainNotarizationData gatewayCND;
|
|
if (GetNotarizationData(gatewayConverterID, gatewayCND) && gatewayCND.vtx.size())
|
|
{
|
|
notarization.currencyStates[gatewayConverterID] = gatewayCND.vtx[gatewayCND.lastConfirmed].second.currencyState;
|
|
}
|
|
}
|
|
|
|
notarization.nodes = GetGoodNodes(CPBaaSNotarization::MAX_NODES);
|
|
|
|
notarization.prevNotarization = cnd.vtx[notaryIdx].first;
|
|
notarization.prevHeight = cnd.vtx[notaryIdx].second.notarizationHeight;
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
std::vector<CTxDestination> dests;
|
|
|
|
// make the earned notarization output
|
|
cp = CCinit(&CC, EVAL_EARNEDNOTARIZATION);
|
|
|
|
if (systemDef.notarizationProtocol == systemDef.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(systemDef.GetID())});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
|
|
txOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_EARNEDNOTARIZATION, dests, 1, ¬arization))));
|
|
|
|
if (systemDef.notarizationProtocol != systemDef.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
// make the finalization output
|
|
cp = CCinit(&CC, EVAL_FINALIZE_NOTARIZATION);
|
|
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
// we need to store the input that we confirmed if we spent finalization outputs
|
|
CObjectFinalization of = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION, notarization.currencyID, uint256(), txOutputs.size() - 1, height + 15);
|
|
txOutputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &of))));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> CObjectFinalization::GetUnspentConfirmedFinalizations(const uint160 ¤cyID)
|
|
{
|
|
LOCK(mempool.cs);
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> retVal;
|
|
std::vector<CAddressUnspentDbEntry> indexUnspent;
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> mempoolUnspent;
|
|
|
|
uint160 indexKey = CCrossChainRPCData::GetConditionID(
|
|
CCrossChainRPCData::GetConditionID(currencyID, CObjectFinalization::ObjectFinalizationNotarizationKey()),
|
|
ObjectFinalizationConfirmedKey());
|
|
if ((GetAddressUnspent(indexKey, CScript::P2IDX, indexUnspent) &&
|
|
mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({{indexKey, CScript::P2IDX}}), mempoolUnspent)) &&
|
|
(indexUnspent.size() || mempoolUnspent.size()))
|
|
{
|
|
/* printf("%s: confirmedNotarizationKey: %s / 0x%s\nconfirmed finalizations\n",
|
|
__func__,
|
|
EncodeDestination(CIdentityID(indexKey)).c_str(),
|
|
indexKey.GetHex().c_str()); */
|
|
std::vector<int> toRemove;
|
|
std::map<COutPoint, int> outputMap;
|
|
for (int i = 0; i < mempoolUnspent.size(); i++)
|
|
{
|
|
if (mempoolUnspent[i].first.spending)
|
|
{
|
|
auto it = outputMap.find(COutPoint(mempoolUnspent[i].second.prevhash, mempoolUnspent[i].second.prevout));
|
|
if (it != outputMap.end())
|
|
{
|
|
it->second < i ? toRemove.push_back(it->second) : toRemove.push_back(i);
|
|
it->second < i ? toRemove.push_back(i) : toRemove.push_back(it->second);
|
|
}
|
|
else
|
|
{
|
|
toRemove.push_back(i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outputMap.insert(std::make_pair(COutPoint(mempoolUnspent[i].first.txhash, mempoolUnspent[i].first.index), i));
|
|
}
|
|
}
|
|
for (auto it = toRemove.rbegin(); it != toRemove.rend(); it++)
|
|
{
|
|
mempoolUnspent.erase(mempoolUnspent.begin() + *it);
|
|
}
|
|
|
|
for (auto &oneConfirmed : indexUnspent)
|
|
{
|
|
//printf("%s: txid: %s, vout: %lu, blockheight: %d\n", __func__, oneConfirmed.first.txhash.GetHex().c_str(), oneConfirmed.first.index, oneConfirmed.second.blockHeight);
|
|
retVal.push_back(std::make_pair(oneConfirmed.second.blockHeight,
|
|
CInputDescriptor(oneConfirmed.second.script, oneConfirmed.second.satoshis, CTxIn(oneConfirmed.first.txhash, oneConfirmed.first.index))));
|
|
}
|
|
std::set<std::pair<uint256,int>> mempoolSpent;
|
|
for (auto &oneConfirmed : mempoolUnspent)
|
|
{
|
|
if (oneConfirmed.first.spending)
|
|
{
|
|
if (retVal.size() &&
|
|
retVal.back().second.txIn.prevout == COutPoint(oneConfirmed.first.txhash, oneConfirmed.first.index))
|
|
{
|
|
// remove it if spent
|
|
//printf("%s: REMOVING txid: %s, vout: %u\n", __func__, oneConfirmed.first.txhash.GetHex().c_str(), oneConfirmed.first.index);
|
|
retVal.pop_back();
|
|
}
|
|
mempoolSpent.insert(std::make_pair(oneConfirmed.first.txhash, oneConfirmed.first.index));
|
|
}
|
|
}
|
|
for (auto &oneConfirmed : mempoolUnspent)
|
|
{
|
|
if (mempoolSpent.count(std::make_pair(oneConfirmed.first.txhash, oneConfirmed.first.index)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto txProxy = mempool.mapTx.find(oneConfirmed.first.txhash);
|
|
if (txProxy != mempool.mapTx.end())
|
|
{
|
|
auto &mpEntry = *txProxy;
|
|
auto &tx = mpEntry.GetTx();
|
|
//printf("%s: txid: %s, vout: %u\n", __func__, oneConfirmed.first.txhash.GetHex().c_str(), oneConfirmed.first.index);
|
|
retVal.push_back(std::make_pair(0,
|
|
CInputDescriptor(tx.vout[oneConfirmed.first.index].scriptPubKey,
|
|
tx.vout[oneConfirmed.first.index].nValue,
|
|
CTxIn(oneConfirmed.first.txhash, oneConfirmed.first.index))));
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> CObjectFinalization::GetUnspentPendingFinalizations(const uint160 ¤cyID)
|
|
{
|
|
LOCK(mempool.cs);
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> retVal;
|
|
std::vector<CAddressUnspentDbEntry> indexUnspent;
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> mempoolUnspent;
|
|
|
|
uint160 indexKey = CCrossChainRPCData::GetConditionID(
|
|
CCrossChainRPCData::GetConditionID(currencyID, CObjectFinalization::ObjectFinalizationNotarizationKey()),
|
|
ObjectFinalizationPendingKey());
|
|
if ((GetAddressUnspent(indexKey, CScript::P2IDX, indexUnspent) &&
|
|
mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({{indexKey, CScript::P2IDX}}), mempoolUnspent)) &&
|
|
(indexUnspent.size() || mempoolUnspent.size()))
|
|
{
|
|
/* printf("%s: pendingNotarizationKey: %s / 0x%s\npending finalizations\n",
|
|
__func__,
|
|
EncodeDestination(CIdentityID(indexKey)).c_str(),
|
|
indexKey.GetHex().c_str()); */
|
|
|
|
std::vector<int> toRemove;
|
|
std::map<COutPoint, int> outputMap;
|
|
for (int i = 0; i < mempoolUnspent.size(); i++)
|
|
{
|
|
if (mempoolUnspent[i].first.spending)
|
|
{
|
|
auto it = outputMap.find(COutPoint(mempoolUnspent[i].second.prevhash, mempoolUnspent[i].second.prevout));
|
|
if (it != outputMap.end())
|
|
{
|
|
it->second < i ? toRemove.push_back(it->second) : toRemove.push_back(i);
|
|
it->second < i ? toRemove.push_back(i) : toRemove.push_back(it->second);
|
|
}
|
|
else
|
|
{
|
|
toRemove.push_back(i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outputMap.insert(std::make_pair(COutPoint(mempoolUnspent[i].first.txhash, mempoolUnspent[i].first.index), i));
|
|
}
|
|
}
|
|
for (auto it = toRemove.rbegin(); it != toRemove.rend(); it++)
|
|
{
|
|
mempoolUnspent.erase(mempoolUnspent.begin() + *it);
|
|
}
|
|
|
|
for (auto &oneConfirmed : indexUnspent)
|
|
{
|
|
//printf("%s: txid: %s, vout: %lu, blockheight: %d\n", __func__, oneConfirmed.first.txhash.GetHex().c_str(), oneConfirmed.first.index, oneConfirmed.second.blockHeight);
|
|
retVal.push_back(std::make_pair(oneConfirmed.second.blockHeight,
|
|
CInputDescriptor(oneConfirmed.second.script, oneConfirmed.second.satoshis, CTxIn(oneConfirmed.first.txhash, oneConfirmed.first.index))));
|
|
}
|
|
std::set<std::pair<uint256,int>> mempoolSpent;
|
|
for (auto &oneUnconfirmed : mempoolUnspent)
|
|
{
|
|
if (oneUnconfirmed.first.spending)
|
|
{
|
|
if (retVal.size() &&
|
|
retVal.back().second.txIn.prevout == COutPoint(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index))
|
|
{
|
|
// remove it if spent
|
|
//printf("%s: REMOVING txid: %s, vout: %u\n", __func__, oneUnconfirmed.first.txhash.GetHex().c_str(), oneUnconfirmed.first.index);
|
|
retVal.pop_back();
|
|
}
|
|
mempoolSpent.insert(std::make_pair(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index));
|
|
}
|
|
}
|
|
for (auto &oneUnconfirmed : mempoolUnspent)
|
|
{
|
|
if (mempoolSpent.count(std::make_pair(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto txProxy = mempool.mapTx.find(oneUnconfirmed.first.txhash);
|
|
if (txProxy != mempool.mapTx.end())
|
|
{
|
|
auto &mpEntry = *txProxy;
|
|
auto &tx = mpEntry.GetTx();
|
|
retVal.push_back(std::make_pair(0,
|
|
CInputDescriptor(tx.vout[oneUnconfirmed.first.index].scriptPubKey,
|
|
tx.vout[oneUnconfirmed.first.index].nValue,
|
|
CTxIn(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index))));
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> CObjectFinalization::GetUnspentEvidence(const uint160 ¤cyID,
|
|
const uint256 ¬arizationTxId,
|
|
int32_t notarizationOutNum)
|
|
{
|
|
LOCK(mempool.cs);
|
|
std::vector<std::pair<uint32_t, CInputDescriptor>> retVal;
|
|
std::vector<CAddressUnspentDbEntry> indexUnspent;
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> mempoolUnspent;
|
|
uint160 indexKey = CCrossChainRPCData::GetConditionID(CNotaryEvidence::NotarySignatureKey(), notarizationTxId, notarizationOutNum);
|
|
|
|
if ((GetAddressUnspent(indexKey, CScript::P2IDX, indexUnspent) &&
|
|
mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({{indexKey, CScript::P2IDX}}), mempoolUnspent)) &&
|
|
(indexUnspent.size() || mempoolUnspent.size()))
|
|
{
|
|
/* printf("%s: unspentEvidenceKey: %s / 0x%s\nunspent evidence\n",
|
|
__func__,
|
|
EncodeDestination(CIdentityID(indexKey)).c_str(),
|
|
indexKey.GetHex().c_str()); */
|
|
|
|
std::vector<int> toRemove;
|
|
std::map<COutPoint, int> outputMap;
|
|
for (int i = 0; i < mempoolUnspent.size(); i++)
|
|
{
|
|
if (mempoolUnspent[i].first.spending)
|
|
{
|
|
auto it = outputMap.find(COutPoint(mempoolUnspent[i].second.prevhash, mempoolUnspent[i].second.prevout));
|
|
if (it != outputMap.end())
|
|
{
|
|
it->second < i ? toRemove.push_back(it->second) : toRemove.push_back(i);
|
|
it->second < i ? toRemove.push_back(i) : toRemove.push_back(it->second);
|
|
}
|
|
else
|
|
{
|
|
toRemove.push_back(i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outputMap.insert(std::make_pair(COutPoint(mempoolUnspent[i].first.txhash, mempoolUnspent[i].first.index), i));
|
|
}
|
|
}
|
|
for (auto it = toRemove.rbegin(); it != toRemove.rend(); it++)
|
|
{
|
|
mempoolUnspent.erase(mempoolUnspent.begin() + *it);
|
|
}
|
|
|
|
for (auto &oneConfirmed : indexUnspent)
|
|
{
|
|
//printf("%s: txid: %s, vout: %lu, blockheight: %d\n", __func__, oneConfirmed.first.txhash.GetHex().c_str(), oneConfirmed.first.index, oneConfirmed.second.blockHeight);
|
|
retVal.push_back(std::make_pair(oneConfirmed.second.blockHeight,
|
|
CInputDescriptor(oneConfirmed.second.script, oneConfirmed.second.satoshis, CTxIn(oneConfirmed.first.txhash, oneConfirmed.first.index))));
|
|
}
|
|
std::set<std::pair<uint256,int>> mempoolSpent;
|
|
for (auto &oneUnconfirmed : mempoolUnspent)
|
|
{
|
|
if (oneUnconfirmed.first.spending)
|
|
{
|
|
if (retVal.size() &&
|
|
retVal.back().second.txIn.prevout == COutPoint(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index))
|
|
{
|
|
// remove it if spent
|
|
//printf("%s: REMOVING txid: %s, vout: %u\n", __func__, oneUnconfirmed.first.txhash.GetHex().c_str(), oneUnconfirmed.first.index);
|
|
retVal.pop_back();
|
|
}
|
|
mempoolSpent.insert(std::make_pair(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index));
|
|
}
|
|
}
|
|
for (auto &oneUnconfirmed : mempoolUnspent)
|
|
{
|
|
if (mempoolSpent.count(std::make_pair(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto txProxy = mempool.mapTx.find(oneUnconfirmed.first.txhash);
|
|
if (txProxy != mempool.mapTx.end())
|
|
{
|
|
auto &mpEntry = *txProxy;
|
|
auto &tx = mpEntry.GetTx();
|
|
retVal.push_back(std::make_pair(0,
|
|
CInputDescriptor(tx.vout[oneUnconfirmed.first.index].scriptPubKey,
|
|
tx.vout[oneUnconfirmed.first.index].nValue,
|
|
CTxIn(oneUnconfirmed.first.txhash, oneUnconfirmed.first.index))));
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
int CChainNotarizationData::BestConfirmedNotarization(int minConfirms)
|
|
{
|
|
if (!IsConfirmed() || !vtx.size())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
uint160 currencyID = vtx[0].second.currencyID;
|
|
|
|
std::vector<int> bestFork;
|
|
if (forks.size() > 1)
|
|
{
|
|
// need to get most recent forks. if we have
|
|
// no conflict for the past 3 earned notarizations,
|
|
// we are good to confirm, otherwise after 10 blocks, the one
|
|
// that ends up > 2 longer can be notarized/finalized.
|
|
std::multimap<int, std::pair<int, int>> forkAndIndexByBlock;
|
|
for (int i = 0; i < forks.size(); i++)
|
|
{
|
|
auto &oneFork = forks[i];
|
|
for (auto &oneRoot : oneFork)
|
|
{
|
|
if (vtx[oneRoot].second.proofRoots.count(currencyID))
|
|
{
|
|
forkAndIndexByBlock.insert(std::make_pair(vtx[oneRoot].second.proofRoots[currencyID].rootHeight, std::make_pair(i, oneRoot)));
|
|
}
|
|
}
|
|
}
|
|
|
|
int forkNum = -1;
|
|
int elemCount = 0;
|
|
for (auto firstIt = forkAndIndexByBlock.rbegin(); firstIt != forkAndIndexByBlock.rend(); firstIt++)
|
|
{
|
|
if (forkNum == -1 || forkNum == firstIt->second.first)
|
|
{
|
|
elemCount++;
|
|
forkNum = firstIt->second.first;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (elemCount >= minConfirms)
|
|
{
|
|
bestFork = forks[forkNum];
|
|
}
|
|
}
|
|
else if (forks.size() &&
|
|
(forks[0].size() > std::max(minConfirms, 1) || ConnectedChains.ThisChain().notarizationProtocol != CCurrencyDefinition::NOTARIZATION_AUTO))
|
|
{
|
|
bestFork = forks[0];
|
|
}
|
|
|
|
if (bestFork.size() < minConfirms || !vtx[lastConfirmed].second.proofRoots.count(currencyID))
|
|
{
|
|
LogPrint("notarization", "insufficient validator confirmations\n");
|
|
return -1;
|
|
}
|
|
|
|
// all we really want is the system proof roots for each notarization to make the JSON for the API smaller
|
|
UniValue proofRootsUni(UniValue::VARR);
|
|
|
|
proofRootsUni.push_back(vtx[lastConfirmed].second.proofRoots[currencyID].ToUniValue());
|
|
|
|
for (auto forkIdxIt = bestFork.begin(); forkIdxIt != bestFork.end(); forkIdxIt++)
|
|
{
|
|
// don't enter the confirmed notarization twice
|
|
if (*forkIdxIt != lastConfirmed)
|
|
{
|
|
auto &oneNot = vtx[*forkIdxIt];
|
|
auto rootIt = oneNot.second.proofRoots.find(currencyID);
|
|
if (rootIt != oneNot.second.proofRoots.end())
|
|
{
|
|
proofRootsUni.push_back(rootIt->second.ToUniValue());
|
|
}
|
|
else
|
|
{
|
|
proofRootsUni.push_back(CProofRoot().ToUniValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proofRootsUni.size())
|
|
{
|
|
LogPrint("notarization", "no valid prior state root found\n");
|
|
return -1;
|
|
}
|
|
|
|
UniValue firstParam(UniValue::VOBJ);
|
|
firstParam.push_back(Pair("proofroots", proofRootsUni));
|
|
firstParam.push_back(Pair("lastconfirmed", 0));
|
|
|
|
// call notary to determine the notarization that we should notarize
|
|
UniValue params(UniValue::VARR);
|
|
params.push_back(firstParam);
|
|
|
|
//printf("%s: about to getbestproofroot with:\n%s\n", __func__, params.write(1,2).c_str());
|
|
|
|
UniValue result;
|
|
try
|
|
{
|
|
result = find_value(RPCCallRoot("getbestproofroot", params), "result");
|
|
} catch (exception e)
|
|
{
|
|
result = NullUniValue;
|
|
}
|
|
|
|
CProofRoot lastConfirmedRoot(find_value(result, "lastconfirmedproofroot"));
|
|
int32_t notaryIdx = lastConfirmedRoot.IsValid() ? uni_get_int(find_value(result, "lastconfirmedindex"), -1) :
|
|
uni_get_int(find_value(result, "bestindex"), -1);
|
|
|
|
if (result.isNull() || notaryIdx == -1)
|
|
{
|
|
LogPrint("notarization", "no-matching-notarization-found\n");
|
|
return -1;
|
|
}
|
|
|
|
// now, assuming that our notarization of the other chain is confirmed, sign the latest valid one on this chain
|
|
UniValue proofRootArr = find_value(result, "validindexes");
|
|
|
|
if (!proofRootArr.isArray() || !proofRootArr.size())
|
|
{
|
|
LogPrint("notarization", "no valid notarizations found\n");
|
|
return -1;
|
|
}
|
|
return bestFork[notaryIdx];
|
|
}
|
|
|
|
// this is called by notaries to locate any notarizations of a specific system that they can notarize, to determine if we
|
|
// agree with the notarization in question, and to confirm or reject the notarization
|
|
bool CPBaaSNotarization::ConfirmOrRejectNotarizations(CWallet *pWallet,
|
|
const CRPCChainData &externalSystem,
|
|
CValidationState &state,
|
|
std::vector<TransactionBuilder> &txBuilders,
|
|
uint32_t nHeight,
|
|
bool &finalized)
|
|
{
|
|
auto txBuilder = TransactionBuilder(Params().GetConsensus(), nHeight, pWallet);
|
|
|
|
std::string errorPrefix(strprintf("%s: ", __func__));
|
|
|
|
finalized = false;
|
|
|
|
CCurrencyDefinition::EProofProtocol hashType = (CCurrencyDefinition::EProofProtocol)externalSystem.chainDefinition.proofProtocol;
|
|
|
|
CChainNotarizationData cnd;
|
|
std::vector<std::pair<CTransaction, uint256>> txes;
|
|
|
|
uint32_t height, signingHeight;
|
|
uint160 SystemID = externalSystem.chainDefinition.GetID();
|
|
|
|
const CCurrencyDefinition *pNotaryCurrency;
|
|
if (IsVerusActive() || !ConnectedChains.notarySystems.count(SystemID))
|
|
{
|
|
pNotaryCurrency = &externalSystem.chainDefinition;
|
|
}
|
|
else
|
|
{
|
|
pNotaryCurrency = &ConnectedChains.ThisChain();
|
|
}
|
|
|
|
std::set<uint160> notarySet = pNotaryCurrency->GetNotarySet();
|
|
int minimumNotariesConfirm = pNotaryCurrency->MinimumNotariesConfirm();
|
|
|
|
std::vector<std::pair<CIdentityMapKey, CIdentityMapValue>> mine;
|
|
{
|
|
std::vector<std::pair<CIdentityMapKey, CIdentityMapValue>> imsigner, watchonly;
|
|
LOCK(pWallet->cs_wallet);
|
|
pWallet->GetIdentities(pNotaryCurrency->notaries, mine, imsigner, watchonly);
|
|
}
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
signingHeight = height = chainActive.Height();
|
|
|
|
// we can only create an earned notarization for a notary chain, so there must be a notary chain and a network connection to it
|
|
// we also need to ensure that our notarization would be the first notarization in this notary block period with which we agree.
|
|
if (!externalSystem.IsValid() || externalSystem.rpcHost.empty())
|
|
{
|
|
// technically not a real error
|
|
return state.Error("no-notary");
|
|
}
|
|
|
|
if (!GetNotarizationData(SystemID, cnd, &txes))
|
|
{
|
|
return state.Error(errorPrefix + "no prior notarization found");
|
|
}
|
|
}
|
|
|
|
if (cnd.IsConfirmed() && cnd.vtx.size() == 1)
|
|
{
|
|
return state.Error("no-unconfirmed");
|
|
}
|
|
|
|
if (height <= ((CPBaaSNotarization::MIN_BLOCKS_BEFORE_NOTARY_FINALIZED << 1) + 1))
|
|
{
|
|
return state.Error(errorPrefix + "too early");
|
|
}
|
|
// TODO: HARDENING - make sure we can spend all outputs we need to, even if there gets to be too many for 1 tx
|
|
// rules to confirm or reject an earned notarization
|
|
// 1) Notaries may confirm an earned notarization if and only if the following is true:
|
|
// a) There is no previous, valid notarization in the same eligible period with which the notarization agrees and should confirm
|
|
// b) If auto-notarization and in a staked block, the last entry to confirm was an earned notarization from a mining block
|
|
// c) If auto-notarization and making a mined block, the last entry to confirm must be an earned notarization from a staked block
|
|
// d) The miner or staker has entered accurate information about the other system, which must be confirmed by the notary
|
|
//
|
|
// 2) Each earned notarization in a staked block contains a proof of the stake transaction in the header.
|
|
//
|
|
// 3) Each includes a proof of the prior notarization with which it agrees and either a PoW or PoS proof of its own.
|
|
//
|
|
// 4) Once a notarization gets 3 uncontested and agreed notarizations in a row, notaries of an auto-notarized chain
|
|
// may sign and confirm, also including the proof of each of the confirmation outputs.
|
|
//
|
|
// if we are auto-notarizing, the following conditions must be true to qualify for us to confirm a notarization:
|
|
// 1) the notarization must be agreed to by 2 or greater valid earned notarizations since the next longest fork.
|
|
//
|
|
std::vector<int> bestFork;
|
|
if (cnd.forks.size() > 1)
|
|
{
|
|
// need to get most recent forks. if we have
|
|
// no conflict for the past 3 earned notarizations,
|
|
// we are good to confirm, otherwise after 10 blocks, the one
|
|
// that ends up > 2 longer can be notarized/finalized.
|
|
std::multimap<uint32_t, std::pair<int, int>> forkAndIndexByBlock;
|
|
for (int i = 0; i < cnd.forks.size(); i++)
|
|
{
|
|
auto &oneFork = cnd.forks[i];
|
|
for (auto &oneRoot : oneFork)
|
|
{
|
|
if (cnd.vtx[oneRoot].second.proofRoots.count(SystemID))
|
|
{
|
|
forkAndIndexByBlock.insert(std::make_pair(cnd.vtx[oneRoot].second.proofRoots[SystemID].rootHeight, std::make_pair(i, oneRoot)));
|
|
}
|
|
}
|
|
}
|
|
|
|
int forkNum = -1;
|
|
int elemCount = 0;
|
|
int disagreements = 0;
|
|
for (auto firstIt = forkAndIndexByBlock.rbegin(); firstIt != forkAndIndexByBlock.rend(); firstIt++)
|
|
{
|
|
if (forkNum == -1 || forkNum == firstIt->second.first)
|
|
{
|
|
elemCount++;
|
|
forkNum = firstIt->second.first;
|
|
}
|
|
else
|
|
{
|
|
// this is an element that does not agree with the fork being processed
|
|
// before we fail as a result, ensure that the associated earned notarization is a PoS block's
|
|
// notarization to prevent successful periodic mining/notarization DoS attacks, where the cost is
|
|
// 1/5th the actual mining power to effectively block cross-chain notarization. PoS is much harder
|
|
// to periodically attack without having statistical holes, enabling notarizations to get confirmed
|
|
// and defeating the attack, without attackers having much >50% network staking power to continuously deny
|
|
// notary confirmation.
|
|
uint256 blockHash = txes[firstIt->second.second].second;
|
|
|
|
// if inexplicable error or PoS block disagrees, abort
|
|
if (blockHash.IsNull() ||
|
|
!mapBlockIndex.count(blockHash) ||
|
|
mapBlockIndex[blockHash]->IsVerusPOSBlock())
|
|
{
|
|
break;
|
|
}
|
|
// PoW disagreements do not veto, or DoS attacks on the cross-chain protocols become too easy
|
|
continue;
|
|
}
|
|
}
|
|
if (elemCount >= 2)
|
|
{
|
|
bestFork = cnd.forks[forkNum];
|
|
}
|
|
}
|
|
else if (cnd.forks.size() &&
|
|
(cnd.forks[0].size() > 2 || pNotaryCurrency->notarizationProtocol != CCurrencyDefinition::NOTARIZATION_AUTO))
|
|
{
|
|
bestFork = cnd.forks[0];
|
|
}
|
|
|
|
bool isFirstConfirmedCND = cnd.vtx[cnd.lastConfirmed].second.IsBlockOneNotarization() || cnd.vtx[cnd.lastConfirmed].second.IsDefinitionNotarization();
|
|
|
|
if ((bestFork.size() < ((pNotaryCurrency->notarizationProtocol == CCurrencyDefinition::NOTARIZATION_AUTO) ? 3 : 2)) ||
|
|
(!isFirstConfirmedCND && !cnd.vtx[cnd.lastConfirmed].second.proofRoots.count(SystemID)))
|
|
{
|
|
return state.Error("insufficient validator confirmations");
|
|
}
|
|
|
|
// any valid earned notarization that we choose to use for our proof, which will determine
|
|
// the signing height, enabling all of them to match without additional coordination
|
|
uint32_t eligibleHeight = height - CPBaaSNotarization::MIN_BLOCKS_BEFORE_NOTARY_FINALIZED;
|
|
|
|
// all we really want is the system proof roots for each notarization to make the JSON for the API smaller
|
|
UniValue proofRootsUni(UniValue::VARR);
|
|
|
|
// start with the last confirmed notarization, which should have originally come from the notary system,
|
|
// and whether it was ever posted to the notary system, should match what the notary system reported as final.
|
|
proofRootsUni.push_back(cnd.vtx[cnd.lastConfirmed].second.proofRoots[SystemID].ToUniValue());
|
|
|
|
for (auto forkIdxIt = bestFork.begin(); forkIdxIt != bestFork.end(); forkIdxIt++)
|
|
{
|
|
// don't enter the confirmed notarization twice
|
|
if (*forkIdxIt != cnd.lastConfirmed)
|
|
{
|
|
auto &oneNot = cnd.vtx[*forkIdxIt];
|
|
auto rootIt = oneNot.second.proofRoots.find(SystemID);
|
|
if (rootIt != oneNot.second.proofRoots.end())
|
|
{
|
|
proofRootsUni.push_back(rootIt->second.ToUniValue());
|
|
}
|
|
else
|
|
{
|
|
proofRootsUni.push_back(CProofRoot().ToUniValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proofRootsUni.size())
|
|
{
|
|
return state.Error(errorPrefix + "no valid prior state root found");
|
|
}
|
|
|
|
UniValue firstParam(UniValue::VOBJ);
|
|
firstParam.push_back(Pair("proofroots", proofRootsUni));
|
|
firstParam.push_back(Pair("lastconfirmed", 0));
|
|
|
|
// call notary to determine the notarization that we should notarize
|
|
UniValue params(UniValue::VARR);
|
|
params.push_back(firstParam);
|
|
|
|
//printf("%s: about to getbestproofroot with:\n%s\n", __func__, params.write(1,2).c_str());
|
|
|
|
UniValue result;
|
|
try
|
|
{
|
|
result = find_value(RPCCallRoot("getbestproofroot", params), "result");
|
|
} catch (exception e)
|
|
{
|
|
result = NullUniValue;
|
|
}
|
|
|
|
CProofRoot lastConfirmedRoot(find_value(result, "lastconfirmedproofroot"));
|
|
int32_t notaryIdx = lastConfirmedRoot.IsValid() ? uni_get_int(find_value(result, "lastconfirmedindex"), -1) :
|
|
uni_get_int(find_value(result, "bestindex"), -1);
|
|
|
|
if (result.isNull() || notaryIdx == -1)
|
|
{
|
|
return state.Error(result.isNull() ? "no-notary" : "no-matching-notarization-found");
|
|
}
|
|
|
|
// get the index from the best fork to make it an index into cnd.vtx
|
|
notaryIdx = bestFork[notaryIdx];
|
|
|
|
// take the lock again, now that we're back from calling out
|
|
LOCK(cs_main);
|
|
|
|
// if height changed, we need to fail and possibly try again later
|
|
if (height != chainActive.Height())
|
|
{
|
|
return state.Error("stale-block");
|
|
}
|
|
|
|
// if the most recent valid earned notarization is prior to the eligible height,
|
|
// we cannot notarize until a more recent one is mined
|
|
|
|
// now, assuming that our notarization of the other chain is confirmed, sign the latest valid one on this chain
|
|
UniValue proofRootArr = find_value(result, "validindexes");
|
|
|
|
if (!proofRootArr.isArray() || !proofRootArr.size())
|
|
{
|
|
return state.Error("no-valid-unconfirmed");
|
|
}
|
|
|
|
bool retVal = false;
|
|
int firstNotarizationIndex = -1;
|
|
|
|
LogPrint("notarization", "%s: proofRootArr: %s\n", __func__, proofRootArr.write().c_str());
|
|
|
|
// we seem to be getting an extra element at times
|
|
// TODO: HARDENING - fix this in bridgekeeper
|
|
if (proofRootArr.size() > bestFork.size())
|
|
{
|
|
UniValue tempArr(UniValue::VARR);
|
|
for (int i = 0; i < bestFork.size(); i++)
|
|
{
|
|
tempArr.push_back(proofRootArr[i]);
|
|
}
|
|
}
|
|
|
|
// look from the latest notarization that may qualify
|
|
for (int i = (proofRootArr.size() - 1); i >= 0; i--)
|
|
{
|
|
int idx = uni_get_int(proofRootArr[i]);
|
|
if (idx >= bestFork.size())
|
|
{
|
|
continue;
|
|
}
|
|
idx = bestFork[idx];
|
|
|
|
auto proofIt = cnd.vtx[idx].second.proofRoots.find(ASSETCHAINS_CHAINID);
|
|
|
|
if (proofIt != cnd.vtx[idx].second.proofRoots.end())
|
|
{
|
|
if (firstNotarizationIndex == -1 && proofIt->second.rootHeight > eligibleHeight)
|
|
{
|
|
firstNotarizationIndex = idx;
|
|
signingHeight = proofIt->second.rootHeight + 1;
|
|
continue;
|
|
}
|
|
else if (proofIt->second.rootHeight <= eligibleHeight)
|
|
{
|
|
// if no notarization is in front of us and recent, we have no more to check
|
|
if (firstNotarizationIndex == -1 || cnd.lastConfirmed == idx)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// these are deleted, mutated below, and recreated each iteration
|
|
std::set<CIdentityID> myIDSet;
|
|
for (auto &oneID : mine)
|
|
{
|
|
myIDSet.insert(oneID.first.idID);
|
|
}
|
|
|
|
// before signing the one we are about to, we want to ensure that it isn't already signed sufficiently
|
|
// if there are enough signatures to confirm it without signature, make our signature, then create a finalization
|
|
CObjectFinalization of = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION,
|
|
SystemID,
|
|
cnd.vtx[idx].first.hash,
|
|
cnd.vtx[idx].first.n,
|
|
height);
|
|
|
|
std::vector<std::pair<CInputDescriptor, CNotaryEvidence>> additionalEvidence;
|
|
|
|
std::set<CIdentityID> sigSet;
|
|
|
|
std::set<CIdentityID> notarySetRejects;
|
|
std::map<CIdentityID, CInputDescriptor> notarySetConfirms;
|
|
|
|
std::vector<std::vector<CNotaryEvidence>> evidenceVec;
|
|
std::vector<std::vector<CInputDescriptor>> spendsToClose;
|
|
std::vector<CInputDescriptor> extraSpends;
|
|
|
|
std::set<int> confirmedOutputNums;
|
|
std::set<int> invalidatedOutputNums;
|
|
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> mempoolUnspent;
|
|
if (mempool.getAddressIndex(
|
|
std::vector<std::pair<uint160, int32_t>>({
|
|
{CCrossChainRPCData::GetConditionID(CObjectFinalization::ObjectFinalizationFinalizedKey(),
|
|
cnd.vtx[idx].first.hash,
|
|
cnd.vtx[idx].first.n),
|
|
CScript::P2IDX}}),
|
|
mempoolUnspent) &&
|
|
mempoolUnspent.size())
|
|
{
|
|
/* printf("%s: confirmedNotarizationKey: %s / 0x%s\nconfirmed finalizations\n",
|
|
__func__,
|
|
EncodeDestination(CIdentityID(indexKey)).c_str(),
|
|
indexKey.GetHex().c_str()); */
|
|
std::vector<int> toRemove;
|
|
std::map<COutPoint, int> outputMap;
|
|
for (int j = 0; j < mempoolUnspent.size(); j++)
|
|
{
|
|
if (mempoolUnspent[j].first.spending)
|
|
{
|
|
auto it = outputMap.find(COutPoint(mempoolUnspent[j].second.prevhash, mempoolUnspent[j].second.prevout));
|
|
if (it != outputMap.end())
|
|
{
|
|
it->second < j ? toRemove.push_back(it->second) : toRemove.push_back(j);
|
|
it->second < j ? toRemove.push_back(j) : toRemove.push_back(it->second);
|
|
}
|
|
else
|
|
{
|
|
toRemove.push_back(j);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outputMap.insert(std::make_pair(COutPoint(mempoolUnspent[j].first.txhash, mempoolUnspent[j].first.index), j));
|
|
}
|
|
}
|
|
for (auto it = toRemove.rbegin(); it != toRemove.rend(); it++)
|
|
{
|
|
mempoolUnspent.erase(mempoolUnspent.begin() + *it);
|
|
}
|
|
for (auto &oneUnspent : mempoolUnspent)
|
|
{
|
|
// if we have a confirmed, valid entry in the mempool already, we don't have something to do
|
|
CTransaction oneTx;
|
|
COptCCParams optP;
|
|
CObjectFinalization checkOf;
|
|
if (mempool.lookup(oneUnspent.first.txhash, oneTx) &&
|
|
oneTx.vout.size() > oneUnspent.first.index &&
|
|
oneTx.vout[oneUnspent.first.index].scriptPubKey.IsPayToCryptoCondition(optP) &&
|
|
optP.IsValid() &&
|
|
optP.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
optP.vData.size() &&
|
|
(checkOf = CObjectFinalization(optP.vData[0])).IsValid() &&
|
|
checkOf.currencyID == SystemID &&
|
|
checkOf.IsConfirmed())
|
|
{
|
|
return state.Error("notarization confirmed in mempool");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cnd.CorrelatedFinalizationSpends(txes, spendsToClose, extraSpends, &evidenceVec) ||
|
|
!cnd.CalculateConfirmation(idx, confirmedOutputNums, invalidatedOutputNums))
|
|
{
|
|
return state.Error("invalid-correlated-spends");
|
|
}
|
|
|
|
if (LogAcceptCategory("notarization"))
|
|
{
|
|
for (int j = 0; j < spendsToClose.size(); j++)
|
|
{
|
|
auto &oneEntrySpends = spendsToClose[j];
|
|
LogPrintf("%s: index #%d - %s\n", __func__, j, confirmedOutputNums.count(j) ? "confirmed" : invalidatedOutputNums.count(j) ? "rejected" : "pending");
|
|
for (auto &oneSpend : oneEntrySpends)
|
|
{
|
|
UniValue scriptUni(UniValue::VOBJ);
|
|
ScriptPubKeyToUniv(oneSpend.scriptPubKey, scriptUni, false, false);
|
|
LogPrintf("txid: %s:%d, nValue: %ld\n", oneSpend.txIn.prevout.hash.GetHex().c_str(), oneSpend.txIn.prevout.n, oneSpend.nValue);
|
|
}
|
|
}
|
|
for (int j = 0; j < evidenceVec.size(); j++)
|
|
{
|
|
auto &oneEvidenceVec = evidenceVec[j];
|
|
LogPrintf("evidence vector: index #%d\n", j);
|
|
for (auto &oneEvidence : oneEvidenceVec)
|
|
{
|
|
LogPrintf("%s\n", oneEvidence.ToUniValue().write(1,2).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
CNotaryEvidence ne(ASSETCHAINS_CHAINID, cnd.vtx[idx].first, CNotaryEvidence::STATE_CONFIRMING);
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
std::vector<CTxDestination> dests;
|
|
|
|
COptCCParams p;
|
|
if (!(txes[idx].first.vout[ne.output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode != EVAL_NONE))
|
|
{
|
|
return state.Error(errorPrefix + "invalid output for notarization");
|
|
}
|
|
CNativeHashWriter hw(hashType);
|
|
uint256 objHash = hw.write((const char *)&(p.vData[0][0]), p.vData[0].size()).GetHash();
|
|
|
|
// combine signatures and evidence
|
|
// add additional evidence spends
|
|
CNotaryEvidence mergedEvidence = ne;
|
|
for (auto &oneEvidenceObj : evidenceVec[idx])
|
|
{
|
|
mergedEvidence.MergeEvidence(oneEvidenceObj, notarySet);
|
|
}
|
|
|
|
uint32_t decisionHeight;
|
|
std::map<CIdentityID, CIdentitySignature> confirmedAtHeight;
|
|
std::map<CIdentityID, CIdentitySignature> rejectedAtHeight;
|
|
CNotaryEvidence::EStates confirmationResult = mergedEvidence.CheckSignatureConfirmation(objHash,
|
|
notarySet,
|
|
minimumNotariesConfirm,
|
|
signingHeight,
|
|
&decisionHeight,
|
|
&confirmedAtHeight,
|
|
&rejectedAtHeight);
|
|
|
|
// rejected is irreversible, so we are done
|
|
if (confirmationResult == CNotaryEvidence::EStates::STATE_REJECTED)
|
|
{
|
|
// we should consider different spends to close when rejecting
|
|
// a notarization, only pruning it and all dependent on it, confirming nothing
|
|
// currently, rejected notarizations would get consumed by confirmed notarizations
|
|
}
|
|
else if (confirmationResult != CNotaryEvidence::EStates::STATE_CONFIRMED)
|
|
{
|
|
LOCK(cs_main);
|
|
|
|
// if we can still sign, do so and check confirmation
|
|
if (myIDSet.size())
|
|
{
|
|
cp = CCinit(&CC, EVAL_NOTARY_EVIDENCE);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
auto currentSignatures = mergedEvidence.GetNotarySignatures();
|
|
for (auto &oneSignature : currentSignatures)
|
|
{
|
|
if (oneSignature.IsConfirmed())
|
|
{
|
|
for (auto &oneIDSig : oneSignature.signatures)
|
|
{
|
|
std::set<CTxDestination> idDestinations;
|
|
|
|
for (auto &oneKeySig : oneIDSig.second.signatures)
|
|
{
|
|
// TODO: HARDENING - for efficiency, not security, remove destinations already signed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
LOCK(pWallet->cs_wallet);
|
|
// sign with all IDs under our control that are eligible for this currency
|
|
for (auto &oneID : myIDSet)
|
|
{
|
|
printf("Signing notarization (%s:%u) to confirm for %s\n", ne.output.hash.GetHex().c_str(), ne.output.n, EncodeDestination(oneID).c_str());
|
|
|
|
auto signResult = ne.SignConfirmed(notarySet, minimumNotariesConfirm, *pWallet, txes[idx].first, oneID, signingHeight, hashType);
|
|
|
|
if (signResult == CIdentitySignature::SIGNATURE_PARTIAL || signResult == CIdentitySignature::SIGNATURE_COMPLETE)
|
|
{
|
|
sigSet.insert(oneID);
|
|
|
|
// if our signatures altogether have provided a complete validation, we can early out
|
|
// check to see if this notarization now qualifies with signatures
|
|
if (signResult == CIdentitySignature::SIGNATURE_COMPLETE &&
|
|
ne.CheckSignatureConfirmation(objHash, notarySet, minimumNotariesConfirm, signingHeight) == CNotaryEvidence::EStates::STATE_CONFIRMED)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return state.Error(errorPrefix + "invalid identity signature");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ne.evidence.Empty())
|
|
{
|
|
/* for (auto &debugOut : ne.signatures)
|
|
{
|
|
printf("%s: onesig - ID: %s, signature: %s\n", __func__, EncodeDestination(debugOut.first).c_str(), debugOut.second.ToUniValue().write(1,2).c_str());
|
|
} */
|
|
|
|
mergedEvidence.MergeEvidence(ne, notarySet, true);
|
|
|
|
retVal = true;
|
|
CScript evidenceScript = MakeMofNCCScript(CConditionObj<CNotaryEvidence>(EVAL_NOTARY_EVIDENCE, dests, 1, &ne));
|
|
of.evidenceOutputs.push_back(txBuilder.mtx.vout.size());
|
|
txBuilder.AddTransparentOutput(evidenceScript, CNotaryEvidence::DEFAULT_OUTPUT_VALUE);
|
|
}
|
|
}
|
|
|
|
confirmationResult = mergedEvidence.CheckSignatureConfirmation(objHash, notarySet, minimumNotariesConfirm, signingHeight);
|
|
}
|
|
|
|
// if we have enough to finalize, do so as a combination of pre-existing evidence and this
|
|
if (confirmationResult == CNotaryEvidence::EStates::STATE_CONFIRMED)
|
|
{
|
|
std::set<COutPoint> inputSet;
|
|
std::vector<CInputDescriptor> nonEvidenceSpends;
|
|
for (auto &oneEvidenceSpend : spendsToClose[idx])
|
|
{
|
|
COptCCParams tP;
|
|
if (oneEvidenceSpend.scriptPubKey.IsPayToCryptoCondition(tP) &&
|
|
tP.evalCode == EVAL_NOTARY_EVIDENCE || tP.evalCode == EVAL_FINALIZE_NOTARIZATION)
|
|
{
|
|
of.evidenceInputs.push_back(txBuilder.mtx.vin.size());
|
|
if (!inputSet.count(oneEvidenceSpend.txIn.prevout))
|
|
{
|
|
inputSet.insert(oneEvidenceSpend.txIn.prevout);
|
|
txBuilder.AddTransparentInput(oneEvidenceSpend.txIn.prevout, oneEvidenceSpend.scriptPubKey, oneEvidenceSpend.nValue);
|
|
}
|
|
else if (LogAcceptCategory("notarization"))
|
|
{
|
|
printf("%s: duplicate input: %s\n", __func__, oneEvidenceSpend.txIn.prevout.ToString().c_str());
|
|
LogPrintf("%s: duplicate input: %s\n", __func__, oneEvidenceSpend.txIn.prevout.ToString().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonEvidenceSpends.push_back(oneEvidenceSpend);
|
|
}
|
|
}
|
|
for (auto &oneSpend : nonEvidenceSpends)
|
|
{
|
|
if (!inputSet.count(oneSpend.txIn.prevout))
|
|
{
|
|
inputSet.insert(oneSpend.txIn.prevout);
|
|
txBuilder.AddTransparentInput(oneSpend.txIn.prevout, oneSpend.scriptPubKey, oneSpend.nValue);
|
|
}
|
|
else if (LogAcceptCategory("notarization"))
|
|
{
|
|
printf("%s: duplicate input: %s\n", __func__, oneSpend.txIn.prevout.ToString().c_str());
|
|
LogPrintf("%s: duplicate input: %s\n", __func__, oneSpend.txIn.prevout.ToString().c_str());
|
|
}
|
|
}
|
|
|
|
int sigCount = 0;
|
|
|
|
of.SetConfirmed();
|
|
|
|
// any fork that does not match the confirmed fork up to the same index will be
|
|
// considered invalid from all of its entries to its end.
|
|
//
|
|
bool makeInputTxes = confirmedOutputNums.size() > 3;
|
|
for (auto oneConfirmedIdx : confirmedOutputNums)
|
|
{
|
|
if (oneConfirmedIdx != idx)
|
|
{
|
|
bool makeInputTx = (makeInputTxes && spendsToClose[oneConfirmedIdx].size() > 2);
|
|
// if we are confirming more
|
|
// than 2 additional pending notarizations (3 total), we break the spends up into one
|
|
// transaction to close out each pending transaction into a pending finalization, which
|
|
// then is spent into the main transaction
|
|
std::vector<int> evidenceInputs;
|
|
auto newTxBuilder = TransactionBuilder(Params().GetConsensus(), nHeight, pWallet);
|
|
TransactionBuilder &oneConfirmedBuilder = makeInputTx ? newTxBuilder : txBuilder;
|
|
|
|
for (auto &oneInput : spendsToClose[oneConfirmedIdx])
|
|
{
|
|
if (!inputSet.count(oneInput.txIn.prevout))
|
|
{
|
|
inputSet.insert(oneInput.txIn.prevout);
|
|
evidenceInputs.push_back(oneConfirmedBuilder.mtx.vin.size());
|
|
oneConfirmedBuilder.AddTransparentInput(oneInput.txIn.prevout, oneInput.scriptPubKey, oneInput.nValue);
|
|
}
|
|
else if (LogAcceptCategory("notarization"))
|
|
{
|
|
printf("%s: duplicate input: %s\n", __func__, oneInput.txIn.prevout.ToString().c_str());
|
|
LogPrintf("%s: duplicate input: %s\n", __func__, oneInput.txIn.prevout.ToString().c_str());
|
|
}
|
|
}
|
|
|
|
if (makeInputTx)
|
|
{
|
|
CObjectFinalization oneConfirmedFinalization = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION,
|
|
SystemID,
|
|
cnd.vtx[oneConfirmedIdx].first.hash,
|
|
cnd.vtx[oneConfirmedIdx].first.n,
|
|
height);
|
|
//oneConfirmedFinalization.evidenceInputs = evidenceInputs;
|
|
|
|
cp = CCinit(&CC, EVAL_FINALIZE_NOTARIZATION);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
CScript finalizeScript = MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &oneConfirmedFinalization));
|
|
oneConfirmedBuilder.AddTransparentOutput(finalizeScript, 0);
|
|
txBuilders.push_back(oneConfirmedBuilder);
|
|
}
|
|
}
|
|
}
|
|
|
|
makeInputTxes = invalidatedOutputNums.size() > 3;
|
|
for (auto oneInvalidatedIdx : invalidatedOutputNums)
|
|
{
|
|
bool makeInputTx = (makeInputTxes && spendsToClose[oneInvalidatedIdx].size() > 2);
|
|
|
|
auto newTxBuilder = TransactionBuilder(Params().GetConsensus(), nHeight, pWallet);
|
|
TransactionBuilder &oneInvalidatedBuilder = makeInputTx ? newTxBuilder : txBuilder;
|
|
|
|
for (auto &oneInput : spendsToClose[oneInvalidatedIdx])
|
|
{
|
|
if (!inputSet.count(oneInput.txIn.prevout))
|
|
{
|
|
inputSet.insert(oneInput.txIn.prevout);
|
|
oneInvalidatedBuilder.AddTransparentInput(oneInput.txIn.prevout, oneInput.scriptPubKey, oneInput.nValue);
|
|
}
|
|
else if (LogAcceptCategory("notarization"))
|
|
{
|
|
printf("%s: duplicate input: %s\n", __func__, oneInput.txIn.prevout.ToString().c_str());
|
|
LogPrintf("%s: duplicate input: %s\n", __func__, oneInput.txIn.prevout.ToString().c_str());
|
|
}
|
|
}
|
|
|
|
if (makeInputTx)
|
|
{
|
|
CObjectFinalization oneConfirmedFinalization = CObjectFinalization(CObjectFinalization::FINALIZE_NOTARIZATION,
|
|
SystemID,
|
|
cnd.vtx[oneInvalidatedIdx].first.hash,
|
|
cnd.vtx[oneInvalidatedIdx].first.n,
|
|
height);
|
|
|
|
cp = CCinit(&CC, EVAL_FINALIZE_NOTARIZATION);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
CScript finalizeScript = MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &oneConfirmedFinalization));
|
|
oneInvalidatedBuilder.AddTransparentOutput(finalizeScript, 0);
|
|
txBuilders.push_back(oneInvalidatedBuilder);
|
|
}
|
|
}
|
|
|
|
retVal = true;
|
|
finalized = true;
|
|
cp = CCinit(&CC, EVAL_FINALIZE_NOTARIZATION);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
CScript finalizeScript = MakeMofNCCScript(CConditionObj<CObjectFinalization>(EVAL_FINALIZE_NOTARIZATION, dests, 1, &of));
|
|
txBuilder.AddTransparentOutput(finalizeScript, 0);
|
|
txBuilders.push_back(txBuilder);
|
|
}
|
|
else if (txBuilder.mtx.vout.size())
|
|
{
|
|
txBuilders.push_back(txBuilder);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (finalized)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool CallNotary(const CRPCChainData ¬arySystem, std::string command, const UniValue ¶ms, UniValue &result, UniValue &error);
|
|
|
|
bool CPBaaSNotarization::FindEarnedNotarization(CObjectFinalization &confirmedFinalization, CAddressIndexDbEntry *pEarnedNotarizationIndex) const
|
|
{
|
|
CAddressIndexDbEntry __earnedNotarizationIndex;
|
|
CAddressIndexDbEntry &earnedNotarizationIndex = pEarnedNotarizationIndex ? *pEarnedNotarizationIndex : __earnedNotarizationIndex;
|
|
|
|
bool retVal = false;
|
|
CPBaaSNotarization checkNotarization = *this;
|
|
if (checkNotarization.IsMirror())
|
|
{
|
|
if (!checkNotarization.SetMirror(false))
|
|
{
|
|
LogPrintf("Unable to interpret notarization data for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
printf("Unable to interpret notarization data for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
if (LogAcceptCategory("notarization") && LogAcceptCategory("verbose"))
|
|
{
|
|
std::vector<unsigned char> checkHex = ::AsVector(checkNotarization);
|
|
LogPrintf("%s: hex of notarization: %s\n", __func__, HexBytes(&(checkHex[0]), checkHex.size()).c_str());
|
|
printf("%s: hex of notarization: %s\n", __func__, HexBytes(&(checkHex[0]), checkHex.size()).c_str());
|
|
}
|
|
|
|
CNativeHashWriter hw;
|
|
hw << checkNotarization;
|
|
uint256 objHash = hw.GetHash();
|
|
uint160 notarizationIdxKey = CCrossChainRPCData::GetConditionID(checkNotarization.currencyID, CPBaaSNotarization::EarnedNotarizationKey(), objHash);
|
|
|
|
std::vector<CAddressIndexDbEntry> addressIndex;
|
|
if (!GetAddressIndex(notarizationIdxKey, CScript::P2IDX, addressIndex) || !addressIndex.size())
|
|
{
|
|
LogPrintf("Unable to confirm notarization data for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
printf("Unable to confirm notarization data for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
for (auto &oneIndexEntry : addressIndex)
|
|
{
|
|
if (oneIndexEntry.first.spending)
|
|
{
|
|
continue;
|
|
}
|
|
earnedNotarizationIndex = oneIndexEntry;
|
|
break;
|
|
}
|
|
if (!earnedNotarizationIndex.first.blockHeight)
|
|
{
|
|
LogPrintf("Unable to locate transaction for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
printf("Unable to locate transaction for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
uint160 finalizedNotarizationKey = CCrossChainRPCData::GetConditionID(CObjectFinalization::ObjectFinalizationFinalizedKey(),
|
|
earnedNotarizationIndex.first.txhash,
|
|
earnedNotarizationIndex.first.index);
|
|
|
|
addressIndex.clear();
|
|
if (!GetAddressIndex(finalizedNotarizationKey, CScript::P2IDX, addressIndex) ||
|
|
!addressIndex.size())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
CAddressIndexDbEntry finalizationIndex;
|
|
for (auto &oneIndexEntry : addressIndex)
|
|
{
|
|
if (oneIndexEntry.first.spending)
|
|
{
|
|
continue;
|
|
}
|
|
finalizationIndex = oneIndexEntry;
|
|
break;
|
|
}
|
|
if (!finalizationIndex.first.blockHeight)
|
|
{
|
|
LogPrintf("Unable to locate indexed finalization transaction for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
printf("Unable to locate indexed finalization transaction for notarization:\n%s\n", checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
CTransaction finalizationTx;
|
|
uint256 blkHash;
|
|
COptCCParams fP;
|
|
if (!myGetTransaction(finalizationIndex.first.txhash, finalizationTx, blkHash) ||
|
|
finalizationIndex.first.index >= finalizationTx.vout.size() ||
|
|
!(finalizationTx.vout[finalizationIndex.first.index].scriptPubKey.IsPayToCryptoCondition(fP) &&
|
|
fP.IsValid() &&
|
|
fP.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
fP.vData.size() &&
|
|
(confirmedFinalization = CObjectFinalization(fP.vData[0])).IsValid()))
|
|
{
|
|
LogPrintf("Invalid finalization transaction %s for notarization:\n%s\n", finalizationIndex.first.txhash.GetHex().c_str(), checkNotarization.ToUniValue().write(1,2).c_str());
|
|
printf("Invalid finalization transaction %s for notarization:\n%s\n", finalizationIndex.first.txhash.GetHex().c_str(), checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
// if the entry is finalized and not confirmed
|
|
if (!confirmedFinalization.IsConfirmed())
|
|
{
|
|
LogPrint("notarization", "Finalization unconfirmed on transaction %s for notarization:\n%s\n", finalizationIndex.first.txhash.GetHex().c_str(), checkNotarization.ToUniValue().write(1,2).c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// look for finalized notarizations either on chain or in the mempool, which are eligible for submission
|
|
// and submit them to the notary chain, referring to the last on the notary chain that we agree with.
|
|
std::vector<uint256> CPBaaSNotarization::SubmitFinalizedNotarizations(const CRPCChainData &externalSystem,
|
|
CValidationState &state)
|
|
{
|
|
std::vector<uint256> retVal;
|
|
|
|
// look for finalized notarizations for our notary chains in recent blocks
|
|
// if we find any and are connected, submit them
|
|
uint160 systemID = externalSystem.chainDefinition.GetID();
|
|
|
|
const CCurrencyDefinition *pNotaryCurrency;
|
|
if (IsVerusActive() || !ConnectedChains.notarySystems.count(systemID))
|
|
{
|
|
pNotaryCurrency = &externalSystem.chainDefinition;
|
|
}
|
|
else
|
|
{
|
|
pNotaryCurrency = &ConnectedChains.ThisChain();
|
|
}
|
|
|
|
std::set<uint160> notarySet = pNotaryCurrency->GetNotarySet();
|
|
|
|
CChainNotarizationData cnd;
|
|
std::vector<std::pair<CTransaction, uint256>> notarizationTxes;
|
|
|
|
// define here to construct in netsted scope and then access below
|
|
CNotaryEvidence allEvidence(CNotaryEvidence::TYPE_NOTARY_EVIDENCE, CNotaryEvidence::VERSION_CURRENT);
|
|
|
|
// first, put in a current proof root of the current chain
|
|
// then, using that, prove the last earned notarization that we
|
|
// agree with
|
|
std::vector<std::vector<CInputDescriptor>> spendsToClose;
|
|
std::vector<CInputDescriptor> extraSpends;
|
|
std::vector<std::vector<CNotaryEvidence>> evidenceVec;
|
|
|
|
std::pair<CInputDescriptor, CPartialTransactionProof> notarizationTxInfo;
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
|
|
// we need to start with a confirmed notarization and one pending after that, which we agree with to move forward
|
|
// this is to ensure that any transaction we intend to finalize has both a minimum number of mining/staking confirmations
|
|
// and a minimum number of notary confirmation signatures
|
|
if (!GetNotarizationData(systemID, cnd, ¬arizationTxes))
|
|
{
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
// to submit a confirmed notarization to the alternate chain, we need another earned notarization
|
|
// that we agree with to be mined into the chain, look from the end of the vtx to find one we agree with
|
|
// the index returned is the one we agree with, and the one that we will use to prove
|
|
int startingNotarizationIdx = cnd.BestConfirmedNotarization(2);
|
|
if (startingNotarizationIdx == -1)
|
|
{
|
|
LogPrint("notarization", "Not enough confirmations to submit finalized notarizations for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
// if latest confirmed notarization on this chain is not present on notary chain, we need to submit it and reinforce
|
|
// the last matching notarization from the other system, which should correspond to a prior confirmed notarization
|
|
// on this chain
|
|
UniValue params(UniValue::VARR);
|
|
params.push_back(EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)));
|
|
|
|
UniValue result;
|
|
try
|
|
{
|
|
result = find_value(RPCCallRoot("getnotarizationdata", params), "result");
|
|
} catch (exception e)
|
|
{
|
|
result = NullUniValue;
|
|
}
|
|
|
|
CChainNotarizationData crosschainCND;
|
|
if (result.isNull() ||
|
|
!(crosschainCND = CChainNotarizationData(result)).IsValid() ||
|
|
(!externalSystem.chainDefinition.IsGateway() && !crosschainCND.IsConfirmed()))
|
|
{
|
|
LogPrintf("Unable to get notarization data from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Unable to get notarization data from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
// if the returned data is not confirmed, then it is a gateway that has not yet confirmed its first transaction
|
|
// it may still have unconfirmed notarizations
|
|
// if it is a gateway that is unconfirmed on the other side, we will insert its definition notarization as confirmed,
|
|
// until it is confirmed on its side.
|
|
if (!crosschainCND.IsConfirmed())
|
|
{
|
|
LOCK(cs_main);
|
|
CInputDescriptor notarizationRef;
|
|
CPBaaSNotarization definitionNotarization;
|
|
if (!ConnectedChains.GetDefinitionNotarization(externalSystem.chainDefinition, notarizationRef, definitionNotarization))
|
|
{
|
|
LogPrintf("Unable to get definition notarization for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Unable to get definition notarization for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
crosschainCND.lastConfirmed = 0;
|
|
crosschainCND.vtx.insert(crosschainCND.vtx.begin(), std::make_pair(CUTXORef(notarizationRef.txIn.prevout), definitionNotarization));
|
|
|
|
if (crosschainCND.vtx.size() == 1)
|
|
{
|
|
crosschainCND.bestChain = 0;
|
|
crosschainCND.forks = std::vector<std::vector<int>>({std::vector<int>({0})});
|
|
}
|
|
else
|
|
{
|
|
// loop through the forks, insert 0, and increment all following indices
|
|
for (auto &oneFork : crosschainCND.forks)
|
|
{
|
|
for (auto &oneIndex : oneFork)
|
|
{
|
|
oneIndex++;
|
|
}
|
|
oneFork.insert(oneFork.begin(), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// our latest confirmed is what we may submit.
|
|
// if it is already on that chain, we have nothing to do
|
|
CPBaaSNotarization firstProofNotarization = cnd.vtx[cnd.lastConfirmed].second;
|
|
auto searchVec = ::AsVector(firstProofNotarization);
|
|
|
|
for (auto oneNotarization : crosschainCND.vtx)
|
|
{
|
|
if (oneNotarization.second.IsMirror() &&
|
|
!oneNotarization.second.SetMirror(false))
|
|
{
|
|
LogPrintf("Failure to unmirror notarization from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Failure to unmirror notarization from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
if (searchVec == ::AsVector(oneNotarization.second))
|
|
{
|
|
LogPrint("notarization", "No new confirmed notarizations for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
// all proof roots in a prior, agreed notarization must be present and have moved forward
|
|
for (auto &oneProofRoot : oneNotarization.second.proofRoots)
|
|
{
|
|
if (!cnd.vtx[cnd.lastConfirmed].second.proofRoots.count(oneProofRoot.first) ||
|
|
cnd.vtx[cnd.lastConfirmed].second.proofRoots[oneProofRoot.first].rootHeight <= oneProofRoot.second.rootHeight)
|
|
{
|
|
LogPrint("notarization", "Skip submission - no new confirmed notarizations for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t nHeight;
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
|
|
nHeight = chainActive.Height();
|
|
|
|
// check to see if any of the pending notarizations match either our last confirmed
|
|
// or the one before it
|
|
int confirmingIdx = 0;
|
|
|
|
bool isFirstLaunchingNotarization = crosschainCND.vtx[confirmingIdx].second.IsDefinitionNotarization() ||
|
|
crosschainCND.vtx[confirmingIdx].second.IsPreLaunch() ||
|
|
crosschainCND.vtx[confirmingIdx].second.IsBlockOneNotarization();
|
|
|
|
CAddressIndexDbEntry earnedNotarizationIndexEntry;
|
|
|
|
if (crosschainCND.IsConfirmed() || crosschainCND.vtx.size() == 1)
|
|
{
|
|
// ensure that we at least agree with the last notarization, or we can't submit
|
|
confirmingIdx = crosschainCND.IsConfirmed() ? crosschainCND.lastConfirmed : 0;
|
|
CObjectFinalization finalizationObj;
|
|
|
|
if (!isFirstLaunchingNotarization)
|
|
{
|
|
if (!crosschainCND.vtx[confirmingIdx].second.FindEarnedNotarization(finalizationObj, &earnedNotarizationIndexEntry))
|
|
{
|
|
return retVal;
|
|
}
|
|
else if (!finalizationObj.IsValid() || !finalizationObj.IsConfirmed())
|
|
{
|
|
// the notarization considered confirmed on the other
|
|
// chain must be in the same notarization chain as the one confirmed on this chain that would
|
|
// be true if it is on both chains, accurate on both, and behind this confirmed notarization
|
|
CTransaction notarizationTx;
|
|
CPBaaSNotarization checkNotarization1, checkNotarization2 = crosschainCND.vtx[confirmingIdx].second;
|
|
uint256 blkHash;
|
|
COptCCParams nP;
|
|
if (!myGetTransaction(earnedNotarizationIndexEntry.first.txhash, notarizationTx, blkHash) ||
|
|
earnedNotarizationIndexEntry.first.index >= notarizationTx.vout.size() ||
|
|
!(notarizationTx.vout[earnedNotarizationIndexEntry.first.index].scriptPubKey.IsPayToCryptoCondition(nP) &&
|
|
nP.IsValid() &&
|
|
nP.evalCode == EVAL_EARNEDNOTARIZATION &&
|
|
nP.vData.size() &&
|
|
(checkNotarization1 = CPBaaSNotarization(nP.vData[0])).IsValid() &&
|
|
checkNotarization2.SetMirror(false) &&
|
|
::AsVector(checkNotarization1) == ::AsVector(checkNotarization2) &&
|
|
checkNotarization1.proofRoots.count(ASSETCHAINS_CHAINID) &&
|
|
checkNotarization2.proofRoots.count(ASSETCHAINS_CHAINID) &&
|
|
checkNotarization1.proofRoots[ASSETCHAINS_CHAINID].rootHeight < checkNotarization2.proofRoots[ASSETCHAINS_CHAINID].rootHeight))
|
|
{
|
|
LogPrintf("Invalid notarization index entry for txid: %s\n", earnedNotarizationIndexEntry.first.txhash.GetHex().c_str());
|
|
printf("Invalid notarization index entry for txid: %s\n", earnedNotarizationIndexEntry.first.txhash.GetHex().c_str());
|
|
return retVal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have pending notarizations, see which of the possible ones we agree with
|
|
if (!(crosschainCND.vtx.size() == 1 && isFirstLaunchingNotarization))
|
|
{
|
|
// go backwards until we find one to confirm or fail
|
|
for (int i = crosschainCND.vtx.size() - 1; i > 0; i--)
|
|
{
|
|
CAddressIndexDbEntry tmpIndexEntry;
|
|
CObjectFinalization finalizationObj;
|
|
if (crosschainCND.vtx[i].second.proofRoots.count(ASSETCHAINS_CHAINID) &&
|
|
crosschainCND.vtx[i].second.FindEarnedNotarization(finalizationObj, &tmpIndexEntry) &&
|
|
finalizationObj.IsConfirmed())
|
|
{
|
|
earnedNotarizationIndexEntry = tmpIndexEntry;
|
|
confirmingIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
if (!crosschainCND.vtx[confirmingIdx].second.IsDefinitionNotarization())
|
|
{
|
|
// if we found one we agree with, make it the one we prove with our own notarization
|
|
// CPartialTransactionProof(const CTransaction tx,
|
|
// const std::vector<int> &inputNums,
|
|
// const std::vector<int> &outputNums,
|
|
// const CBlockIndex *pIndex,
|
|
// uint32_t proofAtHeight)
|
|
uint256 blockHash;
|
|
CTransaction confirmedNTx;
|
|
CBlockIndex *pIndex;
|
|
if (earnedNotarizationIndexEntry.first.txhash.IsNull() ||
|
|
!myGetTransaction(earnedNotarizationIndexEntry.first.txhash, confirmedNTx, blockHash) ||
|
|
!mapBlockIndex.count(blockHash) ||
|
|
!chainActive.Contains(pIndex = mapBlockIndex[blockHash]))
|
|
{
|
|
LogPrintf("Unable to get confirmed notarization transaction for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Unable to get confirmed notarization transaction for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
CPartialTransactionProof firstProof(confirmedNTx,
|
|
std::vector<int>(),
|
|
std::vector<int>({(int)earnedNotarizationIndexEntry.first.index}),
|
|
pIndex,
|
|
crosschainCND.vtx[confirmingIdx].second.proofRoots[ASSETCHAINS_CHAINID].rootHeight);
|
|
notarizationTxInfo = std::make_pair(CInputDescriptor(confirmedNTx.vout[earnedNotarizationIndexEntry.first.index].scriptPubKey,
|
|
confirmedNTx.vout[earnedNotarizationIndexEntry.first.index].nValue,
|
|
CTxIn(earnedNotarizationIndexEntry.first.txhash, earnedNotarizationIndexEntry.first.index)),
|
|
firstProof);
|
|
}
|
|
}
|
|
|
|
if (isFirstLaunchingNotarization)
|
|
{
|
|
// TODO: HARDENING
|
|
/* // get and prove our block 1 notarization on the coinbase, if this is the definition notarization
|
|
if (firstProofNotarization.IsBlockOneNotarization())
|
|
{
|
|
CBlock block;
|
|
if (!ReadBlockFromDisk(block, chainActive[1], Params().GetConsensus(), false))
|
|
{
|
|
LogPrintf("%s: ERROR: could not read block one from disk\n", __func__);
|
|
return retVal;
|
|
}
|
|
|
|
auto blockOneMMR = block.GetBlockMMRTree()
|
|
|
|
CPartialTransactionProof blockOneCoinbaseProof = CPartialTransactionProof()
|
|
|
|
|
|
// get map and MMR for stake source transaction
|
|
CTransactionMap txMap(stakeSource);
|
|
TransactionMMView txView(txMap.transactionMMR);
|
|
uint256 txRoot = txView.GetRoot();
|
|
|
|
std::vector<CTransactionComponentProof> txProofVec;
|
|
txProofVec.push_back(CTransactionComponentProof(txView, txMap, stakeSource, CTransactionHeader::TX_HEADER, 0));
|
|
txProofVec.push_back(CTransactionComponentProof(txView, txMap, stakeSource, CTransactionHeader::TX_OUTPUT, pwinner->i));
|
|
|
|
// now, both the header and stake output are dependent on the transaction MMR root being provable up
|
|
// through the block MMR, and since we don't cache the new MMR proof for transactions yet, we need the block to create the proof.
|
|
// when we switch to the new MMR in place of a merkle tree, we can keep that in the wallet as well
|
|
|
|
BlockMMRange blockMMR(block.GetBlockMMRTree());
|
|
BlockMMView blockView(blockMMR);
|
|
|
|
int txIndexPos;
|
|
for (txIndexPos = 0; txIndexPos < blockMMR.size(); txIndexPos++)
|
|
{
|
|
uint256 txRootHashFromMMR = blockMMR[txIndexPos].hash;
|
|
if (txRootHashFromMMR == txRoot)
|
|
{
|
|
//printf("tx with root %s found in block\n", txRootHashFromMMR.GetHex().c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (txIndexPos == blockMMR.size())
|
|
{
|
|
LogPrintf("%s: ERROR: could not find source transaction root in block %u\n", __func__, srcIndex);
|
|
return false;
|
|
}
|
|
|
|
// prove the tx up to the MMR root, which also contains the block hash
|
|
CMMRProof txRootProof;
|
|
if (!blockView.GetProof(txRootProof, txIndexPos))
|
|
{
|
|
LogPrintf("%s: ERROR: could not create proof of source transaction in block %u\n", __func__, srcIndex);
|
|
return false;
|
|
}
|
|
|
|
mmrView.resize(proveBlockHeight + 1);
|
|
chainActive.GetMerkleProof(mmrView, txRootProof, srcIndex);
|
|
|
|
headerStream << CPartialTransactionProof(txRootProof, txProofVec);
|
|
|
|
CMMRProof blockHeaderProof1;
|
|
if (!chainActive.GetBlockProof(mmrView, blockHeaderProof1, proveBlockHeight))
|
|
{
|
|
LogPrintf("%s: ERROR: could not create block proof for block %u\n", __func__, srcIndex);
|
|
return false;
|
|
}
|
|
headerStream << CBlockHeaderProof(blockHeaderProof1, chainActive[proveBlockHeight]->GetBlockHeader());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} *///
|
|
|
|
|
|
//
|
|
//
|
|
CPBaaSNotarization launchNotarization;
|
|
ConnectedChains.GetLaunchNotarization(externalSystem.chainDefinition,
|
|
notarizationTxInfo,
|
|
launchNotarization,
|
|
firstProofNotarization);
|
|
|
|
if (::AsVector(firstProofNotarization) != ::AsVector(cnd.vtx[cnd.lastConfirmed].second))
|
|
{
|
|
LogPrintf("Unable to get notarization data from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Unable to get notarization data from %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
// TODO: HARDENING - ensure that notarizationTxInfo gets passed to the other side
|
|
if (!isFirstLaunchingNotarization && !(notarizationTxInfo.second.IsValid() && notarizationTxInfo.second.components.size()))
|
|
{
|
|
LogPrintf("Cannot construct notarization proof for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
printf("Cannot construct notarization proof for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
if (!cnd.CorrelatedFinalizationSpends(notarizationTxes, spendsToClose, extraSpends, &evidenceVec) ||
|
|
(!evidenceVec[cnd.lastConfirmed].size()))
|
|
{
|
|
LogPrint("notarization", "Cannot correlate adequate proof evidence for %s\n", EncodeDestination(CIdentityID(systemID)).c_str());
|
|
return retVal;
|
|
}
|
|
|
|
// now, we have our first proof of our prior, proven by our current, which is new to the notary chain
|
|
// we need to prove the confirmed notarization with one after it and a proof root after that
|
|
|
|
// we have:
|
|
// 1) Access to the confirmed earned notarization on this chain, which has been determined
|
|
// not to have been submitted and mined into the notary chain as a confirmed or pending notarization yet.
|
|
// 2) Proof using our confirmed notarization of a transaction that corresponds to either the last confirmed
|
|
// notarization on the notary chain or a pending notarization on the notary chain to confirm. In the case
|
|
// of a gateway, this proof may also apply to the definition transaction.
|
|
//
|
|
// we need to still get:
|
|
// 1) proof root from tip
|
|
// 2) partial transaction proof using the proof root in #1 of the startingNotarizationIdx notarization that we agree with
|
|
// 3) proof using the notarization in #2 of the block behind it, enabling determination of PoW or PoS for notarization
|
|
// 3) partial transaction proof using the notarization in #2 of our confirmed notarization on this chain
|
|
// 4) proof using the confirmed notarization in #3 of the block behind, enabling determination of PoW or PoS for notarization
|
|
// 5) proof using the last confirmed notarization of the notarization already known to the other chain in #2 of what we have above.
|
|
// 6) proof using the last confirmed notarization of the block header ref prior to the previous notarization, enabling
|
|
// confirmation that the last notarization on the notary chain was earned from a PoS or PoW block
|
|
|
|
// get all evidence for the confirmed notarization and combine it
|
|
// all we care about in PBaaS v1 is the signatures, which we will add to cross-chain proofs
|
|
for (auto &oneEvidenceObj : evidenceVec[cnd.lastConfirmed])
|
|
{
|
|
allEvidence.MergeEvidence(oneEvidenceObj, notarySet, true);
|
|
}
|
|
|
|
// now, allEvidence has all signatures that were used to confirm on this chain
|
|
// now add the supporting evidence
|
|
}
|
|
|
|
// allEvidence has all signatures that were used to confirm on this chain
|
|
// now, if we are auto-notarizing, add the supporting evidence
|
|
if (pNotaryCurrency->notarizationProtocol == pNotaryCurrency->NOTARIZATION_AUTO)
|
|
{
|
|
// make sure we have a more recent notarization, after the one we are posting, that is consistent with the other chain/system
|
|
LOCK(cs_main);
|
|
CProofRoot firstProofRoot = CProofRoot::GetProofRoot(nHeight);
|
|
|
|
|
|
|
|
/*
|
|
CHAINOBJ_TRANSACTION_PROOF - valid proof after us
|
|
CHAINOBJ_TRANSACTION_PROOF - pre header proof
|
|
|
|
CHAINOBJ_TRANSACTION_PROOF - valid proof of this confirmed notarization output
|
|
CHAINOBJ_TRANSACTION_PROOF - pre header proof of this header
|
|
|
|
CHAINOBJ_TRANSACTION_PROOF - valid proof of the last confirmed transaction
|
|
CHAINOBJ_TRANSACTION_PROOF - pre header proof of this header
|
|
*/
|
|
}
|
|
|
|
CPBaaSNotarization lastConfirmedNotarization = cnd.vtx[cnd.lastConfirmed].second;
|
|
|
|
// now, we should have enough evidence to prove
|
|
// the notarization. the API call will ensure that we do
|
|
params = UniValue(UniValue::VARR);
|
|
UniValue error;
|
|
std::string strTxId;
|
|
params.push_back(lastConfirmedNotarization.ToUniValue());
|
|
params.push_back(allEvidence.ToUniValue());
|
|
// printf("%s: sending evidence:\n%s\n", __func__, allEvidence.ToUniValue().write(1,2).c_str());
|
|
|
|
/* for (auto &debugOut : oneNotarization.second.signatures)
|
|
{
|
|
printf("%s: onesig - ID: %s, signature: %s\n", __func__, EncodeDestination(debugOut.first).c_str(), debugOut.second.ToUniValue().write(1,2).c_str());
|
|
}
|
|
printf("%s: submitting notarization with parameters:\n%s\n%s\n", __func__, params[0].write(1,2).c_str(), params[1].write(1,2).c_str());
|
|
printf("%s: initial notarization:\n%s\n", __func__, oneNotarization.first.ToUniValue().write(1,2).c_str());
|
|
std::vector<unsigned char> notVec1 = ::AsVector(oneNotarization.first);
|
|
CPBaaSNotarization checkNotarization(oneNotarization.first.ToUniValue());
|
|
std::vector<unsigned char> notVec2 = ::AsVector(checkNotarization);
|
|
printf("%s: processed notarization:\n%s\n", __func__, checkNotarization.ToUniValue().write(1,2).c_str());
|
|
std::vector<unsigned char> notVec3 = ::AsVector(CPBaaSNotarization(notVec1));
|
|
printf("%s: hex before univalue:\n%s\n", __func__, HexBytes(&(notVec1[0]), notVec1.size()).c_str());
|
|
printf("%s: hex after univalue:\n%s\n", __func__, HexBytes(&(notVec2[0]), notVec2.size()).c_str());
|
|
printf("%s: hex after reserialization:\n%s\n", __func__, HexBytes(&(notVec3[0]), notVec3.size()).c_str()); */
|
|
|
|
if (!CallNotary(externalSystem, "submitacceptednotarization", params, result, error) ||
|
|
!error.isNull() ||
|
|
(strTxId = uni_get_str(result)).empty() ||
|
|
!IsHex(strTxId))
|
|
{
|
|
return retVal;
|
|
}
|
|
// store the transaction ID and accepted notariation to prevent redundant submits
|
|
retVal.push_back(uint256S(strTxId));
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
* Validates a notarization output spend by ensuring that the spending transaction fulfills all requirements.
|
|
* to accept an earned notarization as valid on the Verus blockchain, it must prove a transaction on the alternate chain, which is
|
|
* either the original chain definition transaction, which CAN and MUST be proven ONLY in block 1, or the latest notarization transaction
|
|
* on the alternate chain that represents an accurate MMR for this chain.
|
|
* In addition, any accepted notarization must fullfill the following requirements:
|
|
* 1) Must prove either a PoS block from the alternate chain or a merge mined block that is owned by the submitter and in either case,
|
|
* the block must be exactly 8 blocks behind the submitted MMR used for proof.
|
|
* 2) Must prove a chain definition tx and be block 1 or asserts a previous, valid MMR for the notarizing
|
|
* chain and properly prove objects using that MMR.
|
|
* 3) Must spend the main notarization thread as well as any finalization outputs of either valid or invalid prior
|
|
* notarizations, and any unspent notarization contributions for this era. May also spend other inputs.
|
|
* 4) Must output:
|
|
* a) finalization output of the expected reward amount, which will be sent when finalized
|
|
* b) normal output of reward from validated/finalized input if present, 50% to recipient / 50% to block miner less miner fee this tx
|
|
* c) main notarization thread output with remaining funds, no other output or fee deduction
|
|
*
|
|
*/
|
|
bool ValidateAcceptedNotarization(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// TODO: HARDENING
|
|
// the spending transaction must be a notarization in the same thread of notarizations
|
|
// check the following things:
|
|
// 1. It represents a valid PoS or merge mined block on the other chain, and contains the header in the opret
|
|
// 2. The MMR and proof provided for the currently asserted block can prove the provided header. The provided
|
|
// header can prove the last block referenced.
|
|
// 3. This notarization is not a superset of an earlier notarization posted before it that it does not
|
|
// reference. If that is the case, it is rejected.
|
|
// 4. Has all relevant inputs, including finalizes all necessary transactions, both confirmed and orphaned
|
|
//printf("ValidateAcceptedNotarization\n");
|
|
return true;
|
|
}
|
|
|
|
bool PreCheckAcceptedOrEarnedNotarization(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
if (height < 1567999 && CConstVerusSolutionVector::activationHeight.ActiveVersion(height) < CActivationHeight::ACTIVATE_PBAAS)
|
|
{
|
|
return true;
|
|
}
|
|
// ensure that we never accept an invalid proofroot for this chain in a notarization
|
|
COptCCParams p;
|
|
CPBaaSNotarization currentNotarization;
|
|
if (!(tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(currentNotarization = CPBaaSNotarization(p.vData[0])).IsValid() &&
|
|
currentNotarization.currencyState.IsValid() &&
|
|
currentNotarization.currencyState.GetID() == currentNotarization.currencyID))
|
|
{
|
|
return state.Error("Invalid notarization output");
|
|
}
|
|
|
|
if (currentNotarization.proofRoots.count(ASSETCHAINS_CHAINID))
|
|
{
|
|
CProofRoot notarizationRoot = currentNotarization.proofRoots[ASSETCHAINS_CHAINID];
|
|
if (notarizationRoot.rootHeight >= height)
|
|
{
|
|
return true;
|
|
}
|
|
CProofRoot correctRoot = CProofRoot::GetProofRoot(notarizationRoot.rootHeight);
|
|
|
|
if (!correctRoot.IsValid())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (correctRoot != notarizationRoot)
|
|
{
|
|
return state.Error("Incorrect proof root in notarization transaction");
|
|
}
|
|
|
|
// earned notarizations are only supported for
|
|
if (p.evalCode == EVAL_EARNEDNOTARIZATION &&
|
|
!currentNotarization.IsBlockOneNotarization() &&
|
|
!currentNotarization.proofRoots.count(currentNotarization.currencyID))
|
|
{
|
|
return state.Error("Earned notarization besides PBaaS block one must contain proof root of system being notarized");
|
|
}
|
|
}
|
|
else if (p.evalCode == EVAL_EARNEDNOTARIZATION &&
|
|
!currentNotarization.IsBlockOneNotarization())
|
|
{
|
|
return state.Error("Earned notarization must contain proof root of current chain");
|
|
}
|
|
|
|
// if this is a notarization for this chain's notary chain or system, and this chain is running an auto-notarization protocol,
|
|
// only earned notarizations may be entered, and those notarizations must alternate between being entered by a staker and
|
|
// then a miner
|
|
uint32_t nHeight = chainActive.Height();
|
|
|
|
// do full checks if the chain is in sync behind us
|
|
if (nHeight >= (height - 1))
|
|
{
|
|
// ensure that on a chain requiring earned notarizations, we do not enter new chain roots in the form of
|
|
// accepted notarizations. accepted notarizations can be generated on cross-chain imports, and in that case,
|
|
// may only include proof roots from a prior confirmed notarization.
|
|
|
|
// if this is one of the recognized notary chains, the notarizations are "earned" by miners and stakers,
|
|
// if not a notary chain, the currency or chain must have been launched by this chain and notarizations are "accepted"
|
|
// either unquestioningly for notarization protocols other than auto notarization or with auto-notarization
|
|
// requirements
|
|
if (ConnectedChains.notarySystems.count(currentNotarization.currencyID))
|
|
{
|
|
// chain roots may only come from earned notarizations that alternate between staked and mined
|
|
// blocks. accepted notarizations may include chain roots from currently confirmed notarizations
|
|
if (p.evalCode == EVAL_ACCEPTEDNOTARIZATION)
|
|
{
|
|
// if this notarization is not the current chain, but it does have a proof root
|
|
// present, ensure that the proof root accurately reflects the last earned and confirmed notarization
|
|
if (currentNotarization.currencyID != ASSETCHAINS_CHAINID && currentNotarization.proofRoots.count(currentNotarization.currencyID))
|
|
{
|
|
CTransaction priorNotTx;
|
|
uint256 hashBlock;
|
|
if ((currentNotarization.prevNotarization.IsNull() && !currentNotarization.IsDefinitionNotarization()) ||
|
|
!myGetTransaction(currentNotarization.prevNotarization.hash, priorNotTx, hashBlock))
|
|
{
|
|
return state.Error("Invalid prior notarization");
|
|
}
|
|
if (currentNotarization.IsDefinitionNotarization())
|
|
{
|
|
// let it go and if valid, it will be confirmed
|
|
}
|
|
else
|
|
{
|
|
// if there is a proof root in this accepted notarization, it must be as part of an import
|
|
// and the proof root should match the evidence notarization reference used to prove the
|
|
// import.
|
|
// TODO: HARDENING - consider that it may never be necessary to have a proof root in import notarizations
|
|
//
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ensure that this earned notarization follows all relevant rules
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we should only approve accepted notarizations that have the following qualifications
|
|
// for the following protocols:
|
|
// NOTARIZATION_AUTO - must have evidence to prove that one recent earned notarization
|
|
// from either a PoW or PoS block refers to and proves another alternated
|
|
// and proven PoW or PoS earned notarization, which then alternates and proves
|
|
// the notarization being confirmed. This allows us to post it, but it must mature
|
|
// N blocks without being rejected by the notaries to be considered confirmed.
|
|
// NOTARIZATION_NOTARY_CONFIRM - requires no evidence beyond notary signatures
|
|
// NOTARIZATION_NOTARY_CHAINID - requires no evidence beyond chain signature
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// get and aggregate all evidence from a finalization
|
|
std::vector<CNotaryEvidence> CObjectFinalization::GetFinalizationEvidence(const CTransaction &thisTx, CValidationState &state, CTransaction *pOutputTx) const
|
|
{
|
|
CTransaction _outputTx;
|
|
CUTXORef fullOutput(output);
|
|
uint256 outputTxBlockHash;
|
|
CTransaction &finalizedTx = pOutputTx ? *pOutputTx : _outputTx;
|
|
|
|
if (fullOutput.hash.IsNull())
|
|
{
|
|
fullOutput.hash = thisTx.GetHash();
|
|
}
|
|
|
|
if (!GetOutputTransaction(thisTx, finalizedTx, outputTxBlockHash))
|
|
{
|
|
state.Error(std::string(__func__) + ": cannot retrieve output transaction for finalization");
|
|
LogPrint("notarization", "%s: cannot retrieve output transaction for finalization\n", __func__);
|
|
return std::vector<CNotaryEvidence>();
|
|
}
|
|
|
|
CPBaaSNotarization notarization;
|
|
std::vector<CNotaryEvidence> evidenceVec;
|
|
|
|
COptCCParams p;
|
|
if (!(finalizedTx.vout[output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(notarization = CPBaaSNotarization(p.vData[0])).IsValid()))
|
|
{
|
|
state.Error(std::string(__func__) + ": invalid notarization output for finalization");
|
|
LogPrint("notarization", "%s: Invalid notarization output for finalization\n", __func__);
|
|
return std::vector<CNotaryEvidence>();
|
|
}
|
|
|
|
// collect all evidence, outputs first
|
|
if (evidenceOutputs.size() || evidenceInputs.size())
|
|
{
|
|
CTransaction evidenceTx;
|
|
|
|
// add evidence outs as spends to close this entry as well
|
|
int afterMultiPart = evidenceOutputs.size() ? evidenceOutputs[0] : 0;
|
|
for (int i = 0; i < evidenceOutputs.size(); i++)
|
|
{
|
|
auto oneEvidenceOutN = evidenceOutputs[i];
|
|
if (thisTx.vout.size() <= oneEvidenceOutN)
|
|
{
|
|
state.Error(std::string(__func__) + ": indexing error for finalization evidence");
|
|
LogPrint("notarization", "%s: indexing error for finalization evidence\n", __func__);
|
|
return std::vector<CNotaryEvidence>();
|
|
}
|
|
|
|
if (oneEvidenceOutN >= afterMultiPart)
|
|
{
|
|
CNotaryEvidence oneEvidenceObj(thisTx, oneEvidenceOutN, afterMultiPart);
|
|
if (oneEvidenceObj.IsValid())
|
|
{
|
|
evidenceVec.push_back(oneEvidenceObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
CTransaction priorOutputTx;
|
|
CNotaryEvidence oneEvidenceObj;
|
|
std::vector<CNotaryEvidence> evidenceInVec;
|
|
CObjectFinalization priorFinalization;
|
|
for (int i = 0; i < evidenceInputs.size(); i++)
|
|
{
|
|
auto oneIn = evidenceInputs[i];
|
|
uint256 hashBlock;
|
|
if (priorOutputTx.GetHash() != thisTx.vin[oneIn].prevout.hash &&
|
|
!myGetTransaction(thisTx.vin[oneIn].prevout.hash, priorOutputTx, hashBlock))
|
|
{
|
|
state.Error(std::string(__func__) + ": cannot access transaction " + thisTx.vin[oneIn].prevout.hash.GetHex() + " for notarization evidence");
|
|
LogPrint("notarization", "%s: cannot access transaction %s for notarization evidence\n", __func__, thisTx.vin[oneIn].prevout.hash.GetHex().c_str());
|
|
return std::vector<CNotaryEvidence>();
|
|
}
|
|
|
|
COptCCParams inP;
|
|
if (priorOutputTx.vout[thisTx.vin[oneIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(inP) &&
|
|
inP.IsValid() &&
|
|
inP.evalCode == EVAL_NOTARY_EVIDENCE &&
|
|
inP.vData.size() &&
|
|
(oneEvidenceObj = CNotaryEvidence(inP.vData[0])).IsValid() &&
|
|
oneEvidenceObj.evidence.chainObjects.size())
|
|
{
|
|
// if we are starting a new object, finish the old one
|
|
if (!oneEvidenceObj.IsMultipartProof() ||
|
|
((CChainObject<CEvidenceData> *)oneEvidenceObj.evidence.chainObjects[0])->object.md.index == 0)
|
|
{
|
|
// if we have a composite evidence object, store it and clear the vector
|
|
if (evidenceInVec.size())
|
|
{
|
|
CNotaryEvidence multiPartEvidence(evidenceInVec);
|
|
if (multiPartEvidence.IsValid())
|
|
{
|
|
evidenceVec.push_back(multiPartEvidence);
|
|
}
|
|
evidenceInVec.clear();
|
|
}
|
|
}
|
|
|
|
if (!oneEvidenceObj.IsMultipartProof())
|
|
{
|
|
evidenceVec.push_back(oneEvidenceObj);
|
|
}
|
|
else
|
|
{
|
|
evidenceInVec.push_back(oneEvidenceObj);
|
|
}
|
|
}
|
|
else if (inP.IsValid() &&
|
|
inP.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
inP.vData.size() &&
|
|
(priorFinalization = CObjectFinalization(inP.vData[0])).IsValid())
|
|
{
|
|
// cleanup any pending multipart
|
|
if (evidenceInVec.size())
|
|
{
|
|
CNotaryEvidence multiPartEvidence(evidenceInVec);
|
|
if (multiPartEvidence.IsValid())
|
|
{
|
|
evidenceVec.push_back(multiPartEvidence);
|
|
}
|
|
evidenceInVec.clear();
|
|
}
|
|
|
|
CTransaction priorFinalizationTx;
|
|
auto priorFinalizationVec = priorFinalization.GetFinalizationEvidence(priorOutputTx, state, &priorFinalizationTx);
|
|
|
|
// TODO: HARDENING - this assumes that evidence inputs for a finalization are consecutive to calculate how to get
|
|
// to the end of the inputs as a result, we MUST enforce that inputs from a prior finalization are consecutive.
|
|
// they are made that way now, but not yet enforced as of this note.
|
|
if (priorFinalizationVec.size())
|
|
{
|
|
evidenceVec.insert(evidenceVec.end(), priorFinalizationVec.begin(), priorFinalizationVec.end());
|
|
i = oneIn + priorFinalization.evidenceOutputs.size() + 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have a composite evidence object, store it and clear the vector
|
|
if (evidenceInVec.size())
|
|
{
|
|
CNotaryEvidence multiPartEvidence(evidenceInVec);
|
|
if (multiPartEvidence.IsValid())
|
|
{
|
|
evidenceVec.push_back(multiPartEvidence);
|
|
}
|
|
else
|
|
{
|
|
state.Error(std::string(__func__) + ": invalid multipart evidence on input");
|
|
LogPrint("notarization", "%s: invalid multipart evidence on input\n", __func__);
|
|
return std::vector<CNotaryEvidence>();
|
|
}
|
|
}
|
|
}
|
|
return evidenceVec;
|
|
}
|
|
|
|
bool PreCheckFinalizeNotarization(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height)
|
|
{
|
|
// ensure that if we are finalizing a notarization, we have followed all rules to do so
|
|
// after we precheck and confirm a notarization finalization for a notary chain of this chain, we record it
|
|
// as the last notarized checkpoint and prevent any unwind of the blockchain from that point
|
|
|
|
uint32_t chainHeight = chainActive.Height();
|
|
bool haveFullChain = height <= chainHeight + 1;
|
|
|
|
auto upgradeVersion = CConstVerusSolutionVector::GetVersionByHeight(height);
|
|
if (upgradeVersion < CActivationHeight::ACTIVATE_VERUSVAULT)
|
|
{
|
|
return true;
|
|
}
|
|
else if (upgradeVersion < CActivationHeight::ACTIVATE_PBAAS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ensure that we never accept an invalid proofroot for this chain in a notarization
|
|
COptCCParams p;
|
|
CObjectFinalization currentFinalization;
|
|
if (!(tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
p.vData.size() &&
|
|
(currentFinalization = CObjectFinalization(p.vData[0])).IsValid()))
|
|
{
|
|
return state.Error("Invalid finalization for notarization output");
|
|
}
|
|
|
|
CTransaction finalizedTx;
|
|
uint256 txBlockHash;
|
|
CPBaaSNotarization notarization;
|
|
std::vector<CNotaryEvidence> evidenceVec;
|
|
|
|
if (!(currentFinalization.GetOutputTransaction(tx, finalizedTx, txBlockHash) &&
|
|
finalizedTx.vout[currentFinalization.output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(notarization = CPBaaSNotarization(p.vData[0])).IsValid()))
|
|
{
|
|
if (!haveFullChain)
|
|
{
|
|
return true;
|
|
}
|
|
return state.Error("Invalid notarization output for finalization");
|
|
}
|
|
|
|
if (p.evalCode == EVAL_EARNEDNOTARIZATION &&
|
|
!ConnectedChains.notarySystems.count(notarization.currencyID))
|
|
{
|
|
if (!haveFullChain)
|
|
{
|
|
return true;
|
|
}
|
|
return state.Error("Earned notarizations are only valid for notary systems");
|
|
}
|
|
|
|
CCurrencyDefinition notaryCurrencyDef = ConnectedChains.GetCachedCurrency(notarization.currencyID);
|
|
if (!notaryCurrencyDef.IsValid())
|
|
{
|
|
return state.Error("Cannot get currency definition for notarization");
|
|
}
|
|
|
|
const CCurrencyDefinition *pNotaryCurrency;
|
|
if (IsVerusActive() || !ConnectedChains.notarySystems.count(notaryCurrencyDef.GetID()))
|
|
{
|
|
pNotaryCurrency = ¬aryCurrencyDef;
|
|
}
|
|
else
|
|
{
|
|
pNotaryCurrency = &ConnectedChains.thisChain;
|
|
}
|
|
|
|
CNativeHashWriter hw(
|
|
(pNotaryCurrency->IsGateway() &&
|
|
pNotaryCurrency->launchSystemID == ASSETCHAINS_CHAINID &&
|
|
pNotaryCurrency->proofProtocol == pNotaryCurrency->PROOF_ETHNOTARIZATION) ?
|
|
notaryCurrencyDef.PROOF_ETHNOTARIZATION :
|
|
pNotaryCurrency->PROOF_PBAASMMR);
|
|
|
|
if (!notarization.SetMirror(false))
|
|
{
|
|
return state.Error("Cannot prepare notarization to validate finalization");
|
|
}
|
|
|
|
hw << notarization;
|
|
uint256 objHash = hw.GetHash();
|
|
|
|
CTransaction notarizationTx;
|
|
|
|
if (currentFinalization.IsConfirmed())
|
|
{
|
|
// if confirmed, combine and verify all evidence
|
|
// ensure the finalization adheres to the following on-chain rules:
|
|
|
|
evidenceVec = currentFinalization.GetFinalizationEvidence(tx, state, ¬arizationTx);
|
|
if (state.IsError())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// combine and verify signatures, accepting only evidence from this chain
|
|
std::map<uint160, CNotaryEvidence> evidenceMap;
|
|
auto notarySet = pNotaryCurrency->GetNotarySet();
|
|
|
|
// merge accumulated evidence, keeping system separate
|
|
for (auto &e : evidenceVec)
|
|
{
|
|
if (evidenceMap.count(e.systemID))
|
|
{
|
|
evidenceMap[e.systemID].MergeEvidence(e, notarySet);
|
|
}
|
|
else
|
|
{
|
|
evidenceMap[e.systemID] = e;
|
|
}
|
|
}
|
|
|
|
if (p.evalCode == EVAL_EARNEDNOTARIZATION)
|
|
{
|
|
if (!ConnectedChains.notarySystems.count(notarization.currencyID))
|
|
{
|
|
return state.Error("insufficient foundation for proof of notary chain");
|
|
}
|
|
|
|
// 1) for earned notarizations:
|
|
// a) Notarization being confirmed must be agreed to by 2 subsequent consecutive notarizations for auto-notarization and
|
|
// 1 for centralized notarization and in all cases, have no interceding disagreements
|
|
// b) Block height must be at least 1 after last required agreed notarization
|
|
// c) evidence up to this point on chain must all be spent or output by this transaction and must contain necessary
|
|
// signatures from valid, unrevoked notaries to confirm, as well as none of the following counter evidence:
|
|
// i) disagreeing earned notarization between the notarization and the second agreed notarization
|
|
// ii) other forms of proof that render the to-be-confirmed proof root invalid, such as rejecting signatures
|
|
// by the confirming IDs that cancel enough confirming, or proof of a more powerful, provably mined and
|
|
// staked chain since the last notarization
|
|
// 2) for accepted notarizations (see below after else):
|
|
|
|
if (!evidenceMap.count(ASSETCHAINS_CHAINID) ||
|
|
evidenceMap[ASSETCHAINS_CHAINID].CheckSignatureConfirmation(objHash,
|
|
notarySet,
|
|
pNotaryCurrency->minNotariesConfirm,
|
|
height) != CNotaryEvidence::STATE_CONFIRMED)
|
|
{
|
|
return state.Error("insufficient evidence for finalization");
|
|
}
|
|
|
|
// get simulated notarization data assuming the notarization confirmed, starting from that notarization
|
|
// to now and ensure that we have sufficient earned notarization confirmation
|
|
|
|
// ensure that we meet all counter-evidence requirements
|
|
|
|
// if we get here, store the verified proof root of this chain as notarized
|
|
ConnectedChains.notarySystems[notarization.currencyID].lastConfirmedNotarization = notarization;
|
|
}
|
|
else
|
|
{
|
|
// accepted notarization
|
|
// 2) for accepted notarizations:
|
|
// a) Confirmed finalizations must accompany a valid, notary signed, earned notarization with proof and notary
|
|
// agreement from the other chain.
|
|
// b) All on-chain evidence will be retrieved to ensure there is none of the following counter evidence:
|
|
// i) on-chain rejection signatures in blocks prior to this one
|
|
// ii) IDs revoked that result in less than majority for the confirmation
|
|
// iii) proof of a more powerful, provably mined/staked chain since the last notarization that is different than
|
|
// the accepted notarization.
|
|
|
|
if (notarization.currencyID != ASSETCHAINS_CHAINID &&
|
|
(notaryCurrencyDef.IsPBaaSChain() || notaryCurrencyDef.IsGateway()) &&
|
|
!notarization.IsPreLaunch() &&
|
|
(notaryCurrencyDef.launchSystemID != ASSETCHAINS_CHAINID ||
|
|
!evidenceMap.count(notarization.currencyID) ||
|
|
evidenceMap[notarization.currencyID].CheckSignatureConfirmation(objHash,
|
|
notarySet,
|
|
pNotaryCurrency->minNotariesConfirm,
|
|
height) != CNotaryEvidence::STATE_CONFIRMED))
|
|
{
|
|
return state.Error("insufficient evidence from notary system to accept finalization");
|
|
}
|
|
}
|
|
}
|
|
else if (currentFinalization.IsRejected())
|
|
{
|
|
evidenceVec = currentFinalization.GetFinalizationEvidence(tx, state, ¬arizationTx);
|
|
if (state.IsError())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// this is asserting rejection, so we must confirm that it can be rejected and that it only spends
|
|
// inputs that are now invalidated due to the rejection
|
|
if (p.evalCode == EVAL_EARNEDNOTARIZATION)
|
|
{
|
|
// 1) for earned notarizations:
|
|
// a) disagreeing earned notarization between the notarization and the second agreed notarization
|
|
// b) other forms of proof that render the to-be-confirmed proof root invalid, such as rejecting signatures
|
|
// by the confirming IDs that cancel enough confirming, or proof of a more powerful, provably mined and
|
|
// staked chain since the last notarization
|
|
}
|
|
else
|
|
{
|
|
// 2) for accepted notarizations:
|
|
// a) Majority on-chain rejection signatures in blocks prior to this one
|
|
// b) IDs revoked that result in less than majority for the confirmation
|
|
// c) proof of a more powerful, provably mined/staked chain since the last notarization that is different than
|
|
// the accepted notarization.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: HARDENING - finalization is pending, ensure that it makes sense as a pending finalization, generally,
|
|
// most often, such a finalization is on the same transaction as the notarization it sets as pending
|
|
// then evidence for that notarization is posted, which could be aggregated by a pending finalization,
|
|
// then, when there is enough evidence to pass, it is finalized.
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsAcceptedNotarizationInput(const CScript &scriptSig)
|
|
{
|
|
uint32_t ecode;
|
|
return scriptSig.IsPayToCryptoCondition(&ecode) && ecode == EVAL_ACCEPTEDNOTARIZATION;
|
|
}
|
|
|
|
|
|
// earned notarizations can be spent when finalized as either confirmed or invalidated due to a disagreeing,
|
|
// confirmed notarization
|
|
bool ValidateEarnedNotarization(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// TODO: HARDENING ensure that earned notarization UTXOs are spent appropriately
|
|
// the spending transaction must be a finalization that either confirms or invalidates this notarization
|
|
return true;
|
|
}
|
|
|
|
bool IsEarnedNotarizationInput(const CScript &scriptSig)
|
|
{
|
|
// this is an output check, and is incorrect. need to change to input
|
|
uint32_t ecode;
|
|
return scriptSig.IsPayToCryptoCondition(&ecode) && ecode == EVAL_EARNEDNOTARIZATION;
|
|
}
|
|
|
|
CObjectFinalization GetOldFinalization(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx=nullptr, uint32_t *pHeight=nullptr);
|
|
CObjectFinalization GetOldFinalization(const CTransaction &spendingTx, uint32_t nIn, CTransaction *pSourceTx, uint32_t *pHeight)
|
|
{
|
|
CTransaction _sourceTx;
|
|
CTransaction &sourceTx(pSourceTx ? *pSourceTx : _sourceTx);
|
|
|
|
CObjectFinalization oldFinalization;
|
|
uint256 blkHash;
|
|
if (myGetTransaction(spendingTx.vin[nIn].prevout.hash, sourceTx, blkHash))
|
|
{
|
|
if (pHeight)
|
|
{
|
|
auto bIt = mapBlockIndex.find(blkHash);
|
|
if (bIt == mapBlockIndex.end() || !bIt->second)
|
|
{
|
|
*pHeight = chainActive.Height();
|
|
}
|
|
else
|
|
{
|
|
*pHeight = bIt->second->GetHeight();
|
|
}
|
|
}
|
|
COptCCParams p;
|
|
if (sourceTx.vout[spendingTx.vin[nIn].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_FINALIZE_NOTARIZATION || p.evalCode == EVAL_FINALIZE_EXPORT) &&
|
|
p.version >= COptCCParams::VERSION_V3 &&
|
|
p.vData.size() > 1)
|
|
{
|
|
oldFinalization = CObjectFinalization(p.vData[0]);
|
|
}
|
|
}
|
|
return oldFinalization;
|
|
}
|
|
|
|
|
|
/*
|
|
* Ensures that the finalization, either asserted to be confirmed or rejected, has all signatures and evidence
|
|
* necessary for confirmation.
|
|
*/
|
|
bool ValidateFinalizeNotarization(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn, bool fulfilled)
|
|
{
|
|
// to validate a finalization spend, we need to validate the spender's assertion of confirmation or rejection as proven
|
|
|
|
// first, determine our notarization finalization protocol
|
|
CTransaction sourceTx;
|
|
uint32_t oldHeight;
|
|
CObjectFinalization oldFinalization = GetOldFinalization(tx, nIn, &sourceTx, &oldHeight);
|
|
if (!oldFinalization.IsValid())
|
|
{
|
|
return eval->Error("Invalid finalization output");
|
|
}
|
|
|
|
// if we are spending a confirmed notarization, then as long as we are being spent by
|
|
// an accepted notarization and have the same confirmation on an output just after a pending output,
|
|
// it is ok
|
|
if (oldFinalization.IsConfirmed())
|
|
{
|
|
int priorPendingFinalization = -1, notarizationOut = -1;
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
auto &oneOut = tx.vout[i];
|
|
|
|
COptCCParams p;
|
|
CObjectFinalization oneOF;
|
|
|
|
if (oneOut.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
p.vData.size() &&
|
|
(oneOF = CObjectFinalization(p.vData[0])).IsValid() &&
|
|
oneOF.IsNotarizationFinalization() &&
|
|
oneOF.currencyID == oldFinalization.currencyID &&
|
|
oneOF.IsConfirmed())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (LogAcceptCategory("notarization") && LogAcceptCategory("verbose"))
|
|
{
|
|
UniValue jsonNTx(UniValue::VOBJ);
|
|
TxToUniv(tx, uint256(), jsonNTx);
|
|
LogPrintf("%s: transaction removed from mempool due to conflicts:\n%s\n", __func__, jsonNTx.write(1,2).c_str());
|
|
}
|
|
return eval->Error("Invalid spend of confirmed finalization to transaction with no confirmed output");
|
|
}
|
|
|
|
// get currency to determine system and notarization method
|
|
CCurrencyDefinition curDef = ConnectedChains.GetCachedCurrency(oldFinalization.currencyID);
|
|
if (!curDef.IsValid())
|
|
{
|
|
return eval->Error("Invalid currency ID in finalization output");
|
|
}
|
|
uint160 SystemID = curDef.GetID();
|
|
|
|
// for now, auto notarization uses defined notaries. it can be updated later, while leaving those that
|
|
// select notary confirm explicitly to remain on that protocol
|
|
if (curDef.notarizationProtocol == curDef.NOTARIZATION_NOTARY_CONFIRM || curDef.notarizationProtocol == curDef.NOTARIZATION_AUTO)
|
|
{
|
|
// get the notarization this finalizes and its index output
|
|
int32_t notaryOutNum;
|
|
CTransaction notarizationTx;
|
|
|
|
if (oldFinalization.output.IsOnSameTransaction())
|
|
{
|
|
notarizationTx = sourceTx;
|
|
// output needs non-null hash below
|
|
oldFinalization.output.hash = notarizationTx.GetHash();
|
|
}
|
|
else
|
|
{
|
|
uint256 blkHash;
|
|
if (!oldFinalization.GetOutputTransaction(sourceTx, notarizationTx, blkHash))
|
|
{
|
|
return eval->Error("notarization-transaction-not-found");
|
|
}
|
|
}
|
|
if (notarizationTx.vout.size() <= oldFinalization.output.n)
|
|
{
|
|
return eval->Error("invalid-finalization");
|
|
}
|
|
|
|
CPBaaSNotarization pbn(notarizationTx.vout[oldFinalization.output.n].scriptPubKey);
|
|
if (!pbn.IsValid())
|
|
{
|
|
return eval->Error("invalid-notarization");
|
|
}
|
|
|
|
// now, we have an unconfirmed, non-rejected finalization being spent by a transaction
|
|
// confirm that the spender contains one finalization output either correctly confirming or invalidating
|
|
// the finalization. rejection may be implicit by confirming another, later notarization.
|
|
|
|
// First. make sure the oldFinalization is not referring to an earlier notarization than the
|
|
// one most recently confirmed. If so. then it can be spent by anyone.
|
|
CChainNotarizationData cnd;
|
|
if (!GetNotarizationData(SystemID, cnd) || !cnd.IsConfirmed())
|
|
{
|
|
return eval->Error("invalid-notarization");
|
|
}
|
|
|
|
CObjectFinalization newFinalization;
|
|
int finalizationOutNum = -1;
|
|
bool foundFinalization = false;
|
|
for (int i = 0; i < tx.vout.size(); i++)
|
|
{
|
|
auto &oneOut = tx.vout[i];
|
|
COptCCParams p;
|
|
// we can accept only one finalization of this notarization as an output, find it and reject more than one
|
|
|
|
// TODO: HARDENING - ensure that the output of the finalization we are spending is either a confirmed, earlier
|
|
// output or invalidated alternate to the one we are finalizing
|
|
if (oneOut.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
p.vData.size() &&
|
|
(newFinalization = CObjectFinalization(p.vData[0])).IsValid() &&
|
|
newFinalization.currencyID == oldFinalization.currencyID &&
|
|
(newFinalization.IsConfirmed() || newFinalization.output == oldFinalization.output))
|
|
{
|
|
if (foundFinalization)
|
|
{
|
|
return eval->Error("duplicate-finalization");
|
|
}
|
|
foundFinalization = true;
|
|
finalizationOutNum = i;
|
|
}
|
|
}
|
|
|
|
if (!foundFinalization)
|
|
{
|
|
return eval->Error("invalid-finalization-spend");
|
|
}
|
|
|
|
// TODO: HARDENING - complete
|
|
// validate both rejection and confirmation
|
|
// in order to finalize confirmation and not just rejection, we need to spend the last
|
|
// confirmed transaction. that means that if this finalization asserts it is confirmed, we must prove it
|
|
if (newFinalization.IsConfirmed())
|
|
{
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsFinalizeNotarizationInput(const CScript &scriptSig)
|
|
{
|
|
// this is an output check, and is incorrect. need to change to input
|
|
uint32_t ecode;
|
|
return scriptSig.IsPayToCryptoCondition(&ecode) && ecode == EVAL_FINALIZE_NOTARIZATION;
|
|
}
|
|
|
|
bool CObjectFinalization::GetOutputTransaction(const CTransaction &initialTx, CTransaction &tx, uint256 &blockHash) const
|
|
{
|
|
if (output.hash.IsNull())
|
|
{
|
|
tx = initialTx;
|
|
return true;
|
|
}
|
|
else if (myGetTransaction(output.hash, tx, blockHash) && tx.vout.size() > output.n)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Sign the output object with an ID or signing authority of the ID from the wallet.
|
|
CNotaryEvidence CObjectFinalization::SignConfirmed(const std::set<uint160> ¬arySet, int minConfirming, const CWallet *pWallet, const CTransaction &initialTx, const CIdentityID &signatureID, uint32_t signingHeight, CCurrencyDefinition::EProofProtocol hashType) const
|
|
{
|
|
CNotaryEvidence retVal = CNotaryEvidence(ASSETCHAINS_CHAINID, output, CNotaryEvidence::STATE_CONFIRMING);
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
CTransaction tx;
|
|
uint256 blockHash;
|
|
if (GetOutputTransaction(initialTx, tx, blockHash))
|
|
{
|
|
retVal.SignConfirmed(notarySet, minConfirming, *pWallet, tx, signatureID, signingHeight, hashType);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
CNotaryEvidence CObjectFinalization::SignRejected(const std::set<uint160> ¬arySet, int minConfirming, const CWallet *pWallet, const CTransaction &initialTx, const CIdentityID &signatureID, uint32_t signingHeight, CCurrencyDefinition::EProofProtocol hashType) const
|
|
{
|
|
CNotaryEvidence retVal = CNotaryEvidence(ASSETCHAINS_CHAINID, output, CNotaryEvidence::STATE_REJECTING);
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
CTransaction tx;
|
|
uint256 blockHash;
|
|
if (GetOutputTransaction(initialTx, tx, blockHash))
|
|
{
|
|
retVal.SignRejected(notarySet, minConfirming, *pWallet, tx, signatureID, signingHeight, hashType);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// Verify that the output object of "p" is signed appropriately with the indicated signature
|
|
// and that the signature is fully authorized to sign
|
|
// TODO: THIS SHOULD BE UPDATED TO REFLECT USAGE, WHICH IT DOESN'T YET HAVE
|
|
// SPECIFICALLY, THE currencyID, WHICH IS USED, SHOULD BE SEPARATED FROM minimum signatures required
|
|
CIdentitySignature::ESignatureVerification CObjectFinalization::VerifyOutputSignature(const CTransaction &initialTx, const std::vector<CNotarySignature> &signatureVec, const COptCCParams &p, uint32_t height) const
|
|
{
|
|
CCurrencyDefinition curDef;
|
|
int32_t currencyDefHeight;
|
|
|
|
CCurrencyDefinition::EProofProtocol hashType = CCurrencyDefinition::EProofProtocol::PROOF_INVALID;
|
|
for (auto oneSig : signatureVec)
|
|
{
|
|
hashType = oneSig.signatures.size() ? (CCurrencyDefinition::EProofProtocol)oneSig.signatures.begin()->second.hashType : CCurrencyDefinition::EProofProtocol::PROOF_INVALID;
|
|
if (CNativeHashWriter::IsValidHashType(hashType))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (hashType == CCurrencyDefinition::EProofProtocol::PROOF_INVALID)
|
|
{
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
std::map<CIdentityID, CIdentitySignature> confirmedSigs;
|
|
std::map<CIdentityID, CIdentitySignature> rejectedSigs;
|
|
|
|
std::set<CIdentityID> completeConfirmedIDs;
|
|
std::set<CIdentityID> partialConfirmedIDs;
|
|
std::set<CIdentityID> completeRejectedIDs;
|
|
std::set<CIdentityID> partialRejectedIDs;
|
|
|
|
if (p.IsValid() &&
|
|
p.version >= p.VERSION_V3 &&
|
|
p.vData.size() &&
|
|
GetCurrencyDefinition(currencyID, curDef, ¤cyDefHeight) &&
|
|
curDef.IsValid())
|
|
{
|
|
CCurrencyDefinition *pNotaryCurrency;
|
|
if (IsVerusActive() || !ConnectedChains.notarySystems.count(currencyID))
|
|
{
|
|
pNotaryCurrency = &curDef;
|
|
}
|
|
else
|
|
{
|
|
pNotaryCurrency = &ConnectedChains.ThisChain();
|
|
}
|
|
|
|
auto notarySet = pNotaryCurrency->GetNotarySet();
|
|
int minNotariesConfirm = pNotaryCurrency->MinimumNotariesConfirm();
|
|
|
|
for (auto &oneSigBlock : signatureVec)
|
|
{
|
|
if (oneSigBlock.IsConfirmed())
|
|
{
|
|
for (auto &oneIDSig : oneSigBlock.signatures)
|
|
{
|
|
if (oneIDSig.second.blockHeight == height)
|
|
{
|
|
// merge signatures
|
|
if (!confirmedSigs.count(oneIDSig.first))
|
|
{
|
|
confirmedSigs[oneIDSig.first] = oneIDSig.second;
|
|
}
|
|
else
|
|
{
|
|
for (auto oneKeySig : oneIDSig.second.signatures)
|
|
{
|
|
confirmedSigs[oneIDSig.first].signatures.insert(oneKeySig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneIDSig : oneSigBlock.signatures)
|
|
{
|
|
if (oneIDSig.second.blockHeight == height)
|
|
{
|
|
// merge signatures
|
|
if (!rejectedSigs.count(oneIDSig.first))
|
|
{
|
|
rejectedSigs[oneIDSig.first] = oneIDSig.second;
|
|
}
|
|
else
|
|
{
|
|
for (auto oneKeySig : oneIDSig.second.signatures)
|
|
{
|
|
rejectedSigs[oneIDSig.first].signatures.insert(oneKeySig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint256 txId = output.hash.IsNull() ? initialTx.GetHash() : output.hash;
|
|
std::vector<uint160> vdxfCodes = {CCrossChainRPCData::GetConditionID(CNotaryEvidence::NotarySignatureKey(), txId, output.n)};
|
|
std::vector<uint256> statements;
|
|
|
|
// check that signature is of the hashed vData[0] data
|
|
CNativeHashWriter hw(hashType);
|
|
hw.write((const char *)&(p.vData[0][0]), p.vData[0].size());
|
|
uint256 msgHash = hw.GetHash();
|
|
|
|
for (auto &authorizedSignature : confirmedSigs)
|
|
{
|
|
if (notarySet.count(authorizedSignature.first))
|
|
{
|
|
// we might have a partial or complete signature by one notary here
|
|
const CIdentitySignature &oneIDSig = authorizedSignature.second;
|
|
|
|
uint256 sigHash = oneIDSig.IdentitySignatureHash(vdxfCodes, statements, currencyID, height, authorizedSignature.first, "", msgHash);
|
|
|
|
// get identity used to sign
|
|
CIdentity signer = CIdentity::LookupIdentity(authorizedSignature.first, height);
|
|
if (signer.IsValid())
|
|
{
|
|
std::set<uint160> idAddresses;
|
|
std::set<uint160> verifiedSignatures;
|
|
|
|
for (const CTxDestination &oneAddress : signer.primaryAddresses)
|
|
{
|
|
if (oneAddress.which() != COptCCParams::ADDRTYPE_PK || oneAddress.which() != COptCCParams::ADDRTYPE_PKH)
|
|
{
|
|
// currently, can only check secp256k1 signatures
|
|
//return state.Error("Unsupported signature type");
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
idAddresses.insert(GetDestinationID(oneAddress));
|
|
}
|
|
|
|
for (auto &oneSig : authorizedSignature.second.signatures)
|
|
{
|
|
CPubKey pubKey;
|
|
pubKey.RecoverCompact(sigHash, oneSig);
|
|
if (!idAddresses.count(pubKey.GetID()))
|
|
{
|
|
// invalid signature or ID
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
verifiedSignatures.insert(pubKey.GetID());
|
|
}
|
|
if (verifiedSignatures.size() >= signer.minSigs)
|
|
{
|
|
completeConfirmedIDs.insert(authorizedSignature.first);
|
|
}
|
|
else
|
|
{
|
|
partialConfirmedIDs.insert(authorizedSignature.first);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// invalid signing identity in signature
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (completeConfirmedIDs.size() >= curDef.minNotariesConfirm)
|
|
{
|
|
return CIdentitySignature::SIGNATURE_COMPLETE;
|
|
}
|
|
else if (completeConfirmedIDs.size() || partialConfirmedIDs.size())
|
|
{
|
|
return CIdentitySignature::SIGNATURE_PARTIAL;
|
|
}
|
|
}
|
|
// missing or invalid
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
|
|
// Verify that the output object is signed with an authorized signing authority
|
|
CIdentitySignature::ESignatureVerification CObjectFinalization::VerifyOutputSignature(const CTransaction &initialTx, const std::vector<CNotarySignature> &signatureVec, uint32_t height) const
|
|
{
|
|
// now, get the output to check and check to ensure the signature is good
|
|
CTransaction tx;
|
|
uint256 blkHash;
|
|
COptCCParams p;
|
|
if (GetOutputTransaction(initialTx, tx, blkHash) &&
|
|
tx.vout.size() > output.n &&
|
|
tx.vout[output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.vData.size())
|
|
{
|
|
return VerifyOutputSignature(initialTx, signatureVec, p, height);
|
|
}
|
|
else
|
|
{
|
|
return CIdentitySignature::SIGNATURE_INVALID;
|
|
}
|
|
}
|
|
|
|
// this ensures that the signature is, in fact, both authorized to sign, and also a
|
|
// valid signature of the specified output object. if so, this is accepted and
|
|
// results in a valid index entry as a confirmation of the notary signature
|
|
// all signatures must be from a valid notary, or this returns false and should be
|
|
// considered invalid.
|
|
// returns the number of valid, unique notary signatures, enabling a single output
|
|
// to be sufficient to authorize.
|
|
bool ValidateNotarizationEvidence(const CTransaction &tx, int32_t outNum, CValidationState &state, uint32_t height, int &confirmedCount, bool &provenFalse)
|
|
{
|
|
// we MUST know that the cs_main lock is held. since it can be held on the validation thread while smart transactions
|
|
// execute, we cannot take it or assert here
|
|
return true;
|
|
/*
|
|
CNotaryEvidence notarySig;
|
|
COptCCParams p;
|
|
CCurrencyDefinition curDef;
|
|
|
|
confirmedCount = 0; // if a unit of evidence, whether signature or otherwise, is validated as confirming
|
|
provenFalse = false; // if the notarization is proven false
|
|
|
|
if (tx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.version >= p.VERSION_V3 &&
|
|
p.vData.size() &&
|
|
(notarySig = CNotaryEvidence(p.vData[0])).IsValid() &&
|
|
(curDef = ConnectedChains.GetCachedCurrency(notarySig.systemID)).IsValid())
|
|
{
|
|
// now, get the output to check and ensure the signature is good
|
|
CObjectFinalization of;
|
|
CPBaaSNotarization notarization;
|
|
uint256 notarizationTxId;
|
|
CTransaction nTx;
|
|
uint256 blkHash;
|
|
if (notarySig.output.hash.IsNull() ? (nTx = tx), true : myGetTransaction(notarySig.output.hash, nTx, blkHash) &&
|
|
nTx.vout.size() > notarySig.output.n &&
|
|
nTx.vout[notarySig.output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_FINALIZE_NOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(of = CObjectFinalization(p.vData[0])).IsValid() &&
|
|
of.IsNotarizationFinalization() &&
|
|
of.output.hash.IsNull() ? (nTx = tx), true : myGetTransaction(of.output.hash, nTx, blkHash) &&
|
|
!(notarizationTxId = nTx.GetHash()).IsNull() &&
|
|
nTx.vout.size() > of.output.n &&
|
|
nTx.vout[of.output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_EARNEDNOTARIZATION || p.evalCode == EVAL_ACCEPTEDNOTARIZATION) &&
|
|
p.vData.size() &&
|
|
(notarization = CPBaaSNotarization(p.vData[0])).IsValid() &&
|
|
notarization.proofRoots.count(notarySig.systemID))
|
|
{
|
|
// signature is relative only to the notarization, not the finalization
|
|
// that way, the information we put into the vdxfCodes have some meaning beyond
|
|
// the blockchain on which it was signed, and we do not have to carry the
|
|
// finalization mechanism cross-chain.
|
|
std::vector<uint160> vdxfCodes = {CCrossChainRPCData::GetConditionID(notarySig.systemID,
|
|
CNotaryEvidence::NotarySignatureKey(),
|
|
notarizationTxId,
|
|
of.output.n)};
|
|
std::vector<uint256> statements;
|
|
|
|
// check that signature is of the hashed vData[0] data
|
|
CNativeHashWriter hw;
|
|
hw.write((const char *)&(p.vData[0][0]), p.vData[0].size());
|
|
uint256 msgHash = hw.GetHash();
|
|
|
|
for (auto &authorizedNotary : curDef.notaries)
|
|
{
|
|
std::map<CIdentityID, CIdentitySignature>::iterator sigIt = notarySig.signatures.find(authorizedNotary);
|
|
if (sigIt != notarySig.signatures.end())
|
|
{
|
|
// get identity used to sign
|
|
CIdentity signer = CIdentity::LookupIdentity(authorizedNotary, height);
|
|
uint256 sigHash = sigIt->second.IdentitySignatureHash(vdxfCodes, statements, of.currencyID, height, authorizedNotary, "", msgHash);
|
|
|
|
if (signer.IsValid())
|
|
{
|
|
std::set<uint160> idAddresses;
|
|
std::set<uint160> verifiedSignatures;
|
|
|
|
for (const CTxDestination &oneAddress : signer.primaryAddresses)
|
|
{
|
|
if (oneAddress.which() != COptCCParams::ADDRTYPE_PK || oneAddress.which() != COptCCParams::ADDRTYPE_PKH)
|
|
{
|
|
// currently, can only check secp256k1 signatures
|
|
return state.Error("Unsupported signature type");
|
|
}
|
|
idAddresses.insert(GetDestinationID(oneAddress));
|
|
}
|
|
|
|
for (auto &oneSig : notarySig.signatures[authorizedNotary].signatures)
|
|
{
|
|
CPubKey pubKey;
|
|
pubKey.RecoverCompact(sigHash, oneSig);
|
|
uint160 pkID = pubKey.GetID();
|
|
if (!idAddresses.count(pkID))
|
|
{
|
|
return state.Error("Mismatched pubkey and ID in signature");
|
|
}
|
|
if (verifiedSignatures.count(pkID))
|
|
{
|
|
return state.Error("Duplicate key use in ID signature");
|
|
}
|
|
verifiedSignatures.insert(pkID);
|
|
}
|
|
if (verifiedSignatures.size() >= signer.minSigs)
|
|
{
|
|
confirmedCount++;
|
|
}
|
|
else
|
|
{
|
|
return state.Error("Insufficient signatures on behalf of ID: " + signer.name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return state.Error("Invalid notary identity or corrupt local state");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return state.Error("Unauthorized notary");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return state.Error("Invalid notarization reference");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return state.Error("Invalid or non-evidence output");
|
|
}
|
|
|
|
if (!confirmedCount)
|
|
{
|
|
return state.Error("No evidence present");
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|