From 052b865c480ddf73729257d066331c15395e3fb2 Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 11 Oct 2024 15:32:16 +0200 Subject: [PATCH 01/27] use bulletproofs_plus --- src/Makefile.am | 6 +- src/blsct/pos/proof.cpp | 8 +-- src/blsct/pos/proof.h | 6 +- .../amount_recovery_request.cpp | 14 ++-- .../amount_recovery_request.h | 11 ++- .../bulletproofs_plus/range_proof.cpp | 19 ++++++ .../bulletproofs_plus/range_proof.h | 35 +++++++++- .../bulletproofs_plus/range_proof_logic.cpp | 68 ++++++++++++------- .../bulletproofs_plus/range_proof_logic.h | 15 ++-- .../range_proof_with_transcript.cpp | 5 +- .../range_proof_with_transcript.h | 9 ++- src/blsct/range_proof/common.cpp | 5 +- src/blsct/wallet/keyman.cpp | 12 ++-- src/blsct/wallet/keyman.h | 8 +-- src/blsct/wallet/txfactory_global.cpp | 3 +- src/blsct/wallet/txfactory_global.h | 2 +- src/blsct/wallet/verification.cpp | 14 ++-- src/core_io.h | 4 +- src/core_write.cpp | 15 ++-- src/primitives/transaction.h | 8 +-- ...letproofs_plus_range_proof_logic_tests.cpp | 19 +++--- .../set_mem_proof_prover_tests.cpp | 30 ++++---- 22 files changed, 195 insertions(+), 121 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 907a83aea8c92..5cd18777ab870 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -867,13 +867,15 @@ libbitcoin_consensus_a_SOURCES = \ blsct/range_proof/bulletproofs/range_proof.cpp \ blsct/range_proof/bulletproofs/range_proof_logic.cpp \ blsct/range_proof/bulletproofs/range_proof_with_transcript.cpp \ + blsct/range_proof/bulletproofs_plus/amount_recovery_result.cpp \ + blsct/range_proof/bulletproofs_plus/amount_recovery_request.cpp \ blsct/range_proof/bulletproofs_plus/range_proof.cpp \ + blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp \ + blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.cpp \ blsct/range_proof/common.cpp \ blsct/range_proof/generators.cpp \ blsct/range_proof/proof_base.cpp \ blsct/range_proof/msg_amt_cipher.cpp \ - blsct/range_proof/bulletproofs/range_proof.cpp \ - blsct/range_proof/bulletproofs_plus/range_proof.cpp \ blsct/range_proof/bulletproofs_plus/util.cpp \ blsct/set_mem_proof/set_mem_proof.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ diff --git a/src/blsct/pos/proof.cpp b/src/blsct/pos/proof.cpp index 3449ec4f6b990..e2bb3e2c04aa8 100644 --- a/src/blsct/pos/proof.cpp +++ b/src/blsct/pos/proof.cpp @@ -11,8 +11,8 @@ using Point = Arith::Point; using Scalar = Arith::Scalar; using Points = Elements; using Scalars = Elements; -using RangeProof = bulletproofs::RangeProof; -using RangeProver = bulletproofs::RangeProofLogic; +using RangeProof = bulletproofs_plus::RangeProof; +using RangeProver = bulletproofs_plus::RangeProofLogic; using SetProof = SetMemProof; using SetProver = SetMemProofProver; @@ -106,8 +106,8 @@ bool ProofOfStake::VerifyKernelHash(const RangeProof& range_proof, const uint256 range_proof_with_value.Vs.Add(phi); RangeProver rp; - std::vector> proofs; - bulletproofs::RangeProofWithSeed proof{range_proof_with_value, eta_phi, (CAmount)min_value.GetUint64(0)}; + std::vector> proofs; + bulletproofs_plus::RangeProofWithSeed proof{range_proof_with_value, eta_phi, (CAmount)min_value.GetUint64(0)}; proofs.emplace_back(proof); diff --git a/src/blsct/pos/proof.h b/src/blsct/pos/proof.h index d6dcaeccf8f59..577b2729f2ec5 100644 --- a/src/blsct/pos/proof.h +++ b/src/blsct/pos/proof.h @@ -8,8 +8,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -19,7 +19,7 @@ using Point = Arith::Point; using Scalar = Arith::Scalar; using Points = Elements; using SetProof = SetMemProof; -using RangeProof = bulletproofs::RangeProof; +using RangeProof = bulletproofs_plus::RangeProof; namespace blsct { class ProofOfStake diff --git a/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.cpp b/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.cpp index 053fde9de6e57..75df597446a5f 100644 --- a/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.cpp @@ -9,13 +9,13 @@ namespace bulletproofs_plus { template -AmountRecoveryRequest AmountRecoveryRequest::of(RangeProof& proof, typename T::Point& nonce) +AmountRecoveryRequest AmountRecoveryRequest::of(const RangeProofWithSeed& proof, const range_proof::GammaSeed& nonce, const size_t& id) { auto proof_with_transcript = RangeProofWithTranscript::Build(proof); - AmountRecoveryRequest req { - 1, - proof.token_id, + AmountRecoveryRequest req{ + id, + proof.seed, proof_with_transcript.y, proof_with_transcript.z, proof.alpha_hat, @@ -26,10 +26,10 @@ AmountRecoveryRequest AmountRecoveryRequest::of(RangeProof& proof, type proof_with_transcript.m, proof_with_transcript.n, proof_with_transcript.mn, - nonce - }; + nonce, + 0}; return req; } -template AmountRecoveryRequest AmountRecoveryRequest::of(RangeProof&, Mcl::Point&); +template AmountRecoveryRequest AmountRecoveryRequest::of(const RangeProofWithSeed&, const range_proof::GammaSeed&, const size_t&); } // namespace bulletproofs_plus diff --git a/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.h b/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.h index dbcab238aba8d..0f1d4afcd1d08 100644 --- a/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.h +++ b/src/blsct/range_proof/bulletproofs_plus/amount_recovery_request.h @@ -7,8 +7,10 @@ #include #include +#include #include + namespace bulletproofs_plus { template @@ -19,7 +21,7 @@ struct AmountRecoveryRequest using Points = Elements; size_t id; - TokenId token_id; + typename GeneratorDeriver::Seed seed; Scalar y; Scalar z; Scalar alpha_hat; @@ -30,9 +32,12 @@ struct AmountRecoveryRequest size_t m; size_t n; size_t mn; - Point nonce; + typename range_proof::GammaSeed nonce; + Scalar min_value; - static AmountRecoveryRequest of(RangeProof& proof, Point& nonce); + static AmountRecoveryRequest of(const RangeProofWithSeed& proof, + const range_proof::GammaSeed& nonce, + const size_t& id = 0); }; } // namespace bulletproofs_plus diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.cpp b/src/blsct/range_proof/bulletproofs_plus/range_proof.cpp index 715535eae5f72..1ab3085a4873d 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.cpp @@ -32,4 +32,23 @@ bool RangeProof::operator!=(const RangeProof& other) const template bool RangeProof::operator!=(const RangeProof& other) const; +template +bool RangeProofWithSeed::operator==(const RangeProofWithSeed& other) const +{ + using P = RangeProof; + auto this_parent = static_cast(*this); + auto other_parent = static_cast(other); + + return this_parent == other_parent && + seed == other.seed; +} +template bool RangeProofWithSeed::operator==(const RangeProofWithSeed& other) const; + +template +bool RangeProofWithSeed::operator!=(const RangeProofWithSeed& other) const +{ + return !operator==(other); +} +template bool RangeProofWithSeed::operator!=(const RangeProofWithSeed& other) const; + } // namespace bulletproofs_plus diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.h b/src/blsct/range_proof/bulletproofs_plus/range_proof.h index 39ff8221b46e3..da7a6dec3c0dc 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,6 @@ struct RangeProof: public range_proof::ProofBase { void Serialize(Stream& s) const { range_proof::ProofBase::Serialize(s); - ::Serialize(s, token_id); ::Serialize(s, A); ::Serialize(s, A_wip); ::Serialize(s, B); @@ -58,7 +58,6 @@ struct RangeProof: public range_proof::ProofBase { void Unserialize(Stream& s) { range_proof::ProofBase::Unserialize(s); - ::Unserialize(s, token_id); ::Unserialize(s, A); ::Unserialize(s, A_wip); ::Unserialize(s, B); @@ -70,6 +69,38 @@ struct RangeProof: public range_proof::ProofBase { } }; +template +struct RangeProofWithSeed : public RangeProof { + RangeProofWithSeed(const RangeProof& proof, const typename GeneratorDeriver::Seed& seed, const typename T::Scalar& min_value) : RangeProof(proof), seed(seed), min_value(min_value){}; + + RangeProofWithSeed(const RangeProof& proof, const typename GeneratorDeriver::Seed& seed) : RangeProof(proof), seed(seed), min_value(0){}; + + RangeProofWithSeed(const RangeProof& proof) : RangeProof(proof), seed(TokenId()), min_value(0){}; + + RangeProofWithSeed(){}; + + bool operator==(const RangeProofWithSeed& other) const; + bool operator!=(const RangeProofWithSeed& other) const; + + template + void Serialize(Stream& s) const + { + RangeProof::Serialize(s); + } + + template + void Unserialize(Stream& s) + { + RangeProof::Unserialize(s); + } + + // seed to derive generators + typename GeneratorDeriver::Seed seed; + + // min value for proof verification + typename T::Scalar min_value; +}; + } // namespace bulletproofs_plus #endif // NAVIO_BLSCT_RANGE_PROOF_BULLETPROOFS_PLUS_RANGE_PROOF_H diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp index 91b37c52cfe17..c741f19d1b654 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp @@ -204,11 +204,12 @@ std::tuple< template RangeProof RangeProofLogic::Prove( - Elements& vs, - typename T::Point& nonce, + Elements vs, + const range_proof::GammaSeed& nonce, const std::vector& message, - const TokenId& token_id -) { + const Seed& seed, + const typename T::Scalar& minValue) +{ using Scalar = typename T::Scalar; using Scalars = Elements; @@ -218,26 +219,45 @@ RangeProof RangeProofLogic::Prove( blsct::Common::GetFirstPowerOf2GreaterOrEqTo(vs.Size()); RangeProof proof; - proof.token_id = token_id; const size_t m = num_input_values_power_of_2; const size_t n = range_proof::Setup::num_input_value_bits; const size_t mn = m * n; + auto vsOriginal = vs; + + // apply minValue + if (!minValue.IsZero()) { + for (size_t i = 0; i < vs.Size(); ++i) { + vs[i] = vs[i] - minValue; + } + } + // generate gammas Scalars gammas; - for (size_t i = 0; i < num_input_values_power_of_2; ++i) { - auto hash = nonce.GetHashWithSalt(100 + i); - gammas.Add(hash); + if (std::holds_alternative(nonce.seed)) { + for (size_t i = 0; i < num_input_values_power_of_2; ++i) { + auto hash = nonce.GetHashWithSalt(100 + i); + gammas.Add(hash); + } + } else if (std::holds_alternative(nonce.seed)) { + auto vec = std::get(nonce.seed); + if (vs.Size() != vec.Size()) { + throw std::runtime_error(strprintf("%s: size of vs does not match size of gammas", __func__)); + } + for (size_t i = 0; i < vec.Size(); ++i) { + gammas.Add(vec[i]); + } } // make the number of input values a power of 2 w/ 0s if needed while (vs.Size() < num_input_values_power_of_2) { vs.Add(Scalar(0)); + vsOriginal.Add(Scalar(0)); } // get generators for the token_id - range_proof::Generators gens = m_common.Gf().GetInstance(token_id); + range_proof::Generators gens = m_common.Gf().GetInstance(seed); auto gs = gens.GetGiSubset(mn); auto hs = gens.GetHiSubset(mn); auto h = gens.H; @@ -250,7 +270,7 @@ RangeProof RangeProofLogic::Prove( // Calculate value commitments directly form the input values for (size_t i = 0; i < vs.Size(); ++i) { - auto V = (g * vs[i]) + (h * gammas[i]); + auto V = (g * vsOriginal[i]) + (h * gammas[i]); proof.Vs.Add(V); fiat_shamir << V; } @@ -260,7 +280,7 @@ RangeProof RangeProofLogic::Prove( // Commitment to aL and aR (obfuscated with alpha) Scalar nonce_alpha = nonce.GetHashWithSalt(1); - Scalar alpha = range_proof::MsgAmtCipher::ComputeAlpha(message, vs[0], nonce_alpha); + Scalar alpha = range_proof::MsgAmtCipher::ComputeAlpha(message, vsOriginal[0], nonce_alpha); Scalar tau1 = nonce.GetHashWithSalt(2); Scalar tau2 = nonce.GetHashWithSalt(3); @@ -371,11 +391,11 @@ RangeProof RangeProofLogic::Prove( return proof; } template RangeProof RangeProofLogic::Prove( - Elements&, - Mcl::Point&, + Elements, + const range_proof::GammaSeed&, const std::vector&, - const TokenId& -); + const Seed&, + const Mcl::Scalar&); template bool RangeProofLogic::VerifyProofs( @@ -388,7 +408,7 @@ bool RangeProofLogic::VerifyProofs( for (const RangeProofWithTranscript& pt : proof_transcripts) { if (pt.proof.Ls.Size() != pt.proof.Rs.Size()) return false; - range_proof::Generators gens = m_common.Gf().GetInstance(pt.proof.token_id); + range_proof::Generators gens = m_common.Gf().GetInstance(pt.proof.seed); auto gs = gens.GetGiSubset(pt.mn); auto hs = gens.GetHiSubset(pt.mn); @@ -480,7 +500,10 @@ bool RangeProofLogic::VerifyProofs( lp.Add(pt.proof.Rs, e_inv_squares); lp.Add(gs, gs_exp); lp.Add(hs, hs_exp); - lp.Add(pt.proof.Vs, vs_exp); + + for (size_t i = 0; i < pt.proof.Vs.Size(); ++i) { + lp.Add(LazyPoint(pt.proof.Vs[i] - (gens.G * pt.proof.min_value), vs_exp[i])); + } if (!lp.Sum().IsZero()) return false; } @@ -494,14 +517,14 @@ template bool RangeProofLogic::VerifyProofs( template bool RangeProofLogic::Verify( - const std::vector>& proofs -) { + const std::vector>& proofs) +{ range_proof::Common::ValidateProofsBySizes(proofs); std::vector> proof_transcripts; size_t max_num_rounds = 0; - for (const RangeProof& proof: proofs) { + for (const RangeProofWithSeed& proof : proofs) { // update max # of rounds and sum of all V bits max_num_rounds = std::max(max_num_rounds, proof.Ls.Size()); @@ -518,8 +541,7 @@ bool RangeProofLogic::Verify( ); } template bool RangeProofLogic::Verify( - const std::vector>& -); + const std::vector>&); template AmountRecoveryResult RangeProofLogic::RecoverAmounts( @@ -532,7 +554,7 @@ AmountRecoveryResult RangeProofLogic::RecoverAmounts( std::vector> xs; for (const AmountRecoveryRequest& req: reqs) { - range_proof::Generators gens = m_common.Gf().GetInstance(req.token_id); + range_proof::Generators gens = m_common.Gf().GetInstance(req.seed); Point g = gens.G; Point h = gens.H; diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h index a45dddb68b23c..5086e4d0be3f7 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h @@ -10,10 +10,11 @@ #include #include -#include +#include #include #include #include +#include #include #include #include @@ -29,19 +30,19 @@ class RangeProofLogic public: using Scalar = typename T::Scalar; using Point = typename T::Point; + using Seed = typename GeneratorDeriver::Seed; using Scalars = Elements; using Points = Elements; RangeProof Prove( - Scalars& vs, - Point& nonce, + Scalars vs, + const range_proof::GammaSeed& nonce, const std::vector& message, - const TokenId& token_id - ); + const Seed& seed, + const typename T::Scalar& minValue = 0); bool Verify( - const std::vector>& proofs - ); + const std::vector>& proofs); AmountRecoveryResult RecoverAmounts( const std::vector>& reqs diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.cpp b/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.cpp index 308256a2f32b8..95eadea7a7d08 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.cpp @@ -18,7 +18,8 @@ namespace bulletproofs_plus { template -RangeProofWithTranscript RangeProofWithTranscript::Build(const RangeProof& proof) { +RangeProofWithTranscript RangeProofWithTranscript::Build(const RangeProofWithSeed& proof) +{ using Scalar = typename T::Scalar; // build transcript in the same way the prove function builds it @@ -75,6 +76,6 @@ RangeProofWithTranscript RangeProofWithTranscript::Build(const RangeProof< ++i; } } -template RangeProofWithTranscript RangeProofWithTranscript::Build(const RangeProof&); +template RangeProofWithTranscript RangeProofWithTranscript::Build(const RangeProofWithSeed&); } // namespace bulletproofs_plus diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.h b/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.h index 5fde8f7d32ba8..cf5ed02522e7e 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_with_transcript.h @@ -20,19 +20,18 @@ class RangeProofWithTranscript public: RangeProofWithTranscript( - const RangeProof& proof, + const RangeProofWithSeed& proof, const Scalar& y, const Scalar& z, const Scalar& e_last_round, const Scalars& es, const size_t& m, const size_t& n, - const size_t& mn - ): proof{proof}, y{y}, z{z}, e_last_round(e_last_round), es{es}, m{m}, n{n}, mn{mn} {} + const size_t& mn) : proof{proof}, y{y}, z{z}, e_last_round(e_last_round), es{es}, m{m}, n{n}, mn{mn} {} - static RangeProofWithTranscript Build(const RangeProof& proof); + static RangeProofWithTranscript Build(const RangeProofWithSeed& proof); - const RangeProof proof; + const RangeProofWithSeed proof; const Scalar y; const Scalar z; const Scalar e_last_round; diff --git a/src/blsct/range_proof/common.cpp b/src/blsct/range_proof/common.cpp index 575c018ad7730..d68489c9f76ba 100644 --- a/src/blsct/range_proof/common.cpp +++ b/src/blsct/range_proof/common.cpp @@ -194,9 +194,6 @@ void Common::ValidateProofsBySizes( template void Common::ValidateProofsBySizes( const std::vector>&); template void Common::ValidateProofsBySizes( - const std::vector>& -); - - + const std::vector>&); } diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp index 759fc7e949b6d..b33b604a59e73 100644 --- a/src/blsct/wallet/keyman.cpp +++ b/src/blsct/wallet/keyman.cpp @@ -541,13 +541,13 @@ blsct::PrivateKey KeyMan::GetSpendingKeyForOutput(const CTxOut& out, const SubAd using Arith = Mcl; -bulletproofs::AmountRecoveryResult KeyMan::RecoverOutputs(const std::vector& outs) +bulletproofs_plus::AmountRecoveryResult KeyMan::RecoverOutputs(const std::vector& outs) { if (!fViewKeyDefined || !viewKey.IsValid()) - return bulletproofs::AmountRecoveryResult::failure(); + return bulletproofs_plus::AmountRecoveryResult::failure(); - bulletproofs::RangeProofLogic rp; - std::vector> reqs; + bulletproofs_plus::RangeProofLogic rp; + std::vector> reqs; reqs.reserve(outs.size()); for (size_t i = 0; i < outs.size(); i++) { @@ -555,8 +555,8 @@ bulletproofs::AmountRecoveryResult KeyMan::RecoverOutputs(const std::vect if (out.blsctData.viewTag != CalculateViewTag(out.blsctData.blindingKey, viewKey.GetScalar())) continue; auto nonce = CalculateNonce(out.blsctData.blindingKey, viewKey.GetScalar()); - bulletproofs::RangeProofWithSeed proof = {out.blsctData.rangeProof, out.tokenId}; - reqs.push_back(bulletproofs::AmountRecoveryRequest::of(proof, nonce, i)); + bulletproofs_plus::RangeProofWithSeed proof = {out.blsctData.rangeProof, out.tokenId}; + reqs.push_back(bulletproofs_plus::AmountRecoveryRequest::of(proof, nonce, i)); } return rp.RecoverAmounts(reqs); diff --git a/src/blsct/wallet/keyman.h b/src/blsct/wallet/keyman.h index 4f04288d40721..45e2e8692be6d 100644 --- a/src/blsct/wallet/keyman.h +++ b/src/blsct/wallet/keyman.h @@ -8,9 +8,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -142,7 +142,7 @@ class KeyMan : public Manager, public KeyRing blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out) const; blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const CKeyID& id) const; blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const SubAddressIdentifier& id) const; - bulletproofs::AmountRecoveryResult RecoverOutputs(const std::vector& outs); + bulletproofs_plus::AmountRecoveryResult RecoverOutputs(const std::vector& outs); /** SubAddress keypool */ void LoadSubAddress(const CKeyID& hashId, const SubAddressIdentifier& index); diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index bcd341b154046..74f04a0d1427a 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include #include using T = Mcl; @@ -45,7 +44,7 @@ Signature UnsignedOutput::GetSignature() const UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const Scalar& blindingKey, const CreateTransactionType& type, const CAmount& minStake) { - bulletproofs::RangeProofLogic rp; + bulletproofs_plus::RangeProofLogic rp; auto ret = UnsignedOutput(); ret.out.nValue = 0; diff --git a/src/blsct/wallet/txfactory_global.h b/src/blsct/wallet/txfactory_global.h index 0e8656869751d..052269373cf74 100644 --- a/src/blsct/wallet/txfactory_global.h +++ b/src/blsct/wallet/txfactory_global.h @@ -6,7 +6,7 @@ #define TXFACTORY_GLOBAL_H #include -#include +#include #include using T = Mcl; diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp index 9befa4867176f..9d3af7e1c6f5e 100644 --- a/src/blsct/wallet/verification.cpp +++ b/src/blsct/wallet/verification.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -19,8 +19,8 @@ bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationS } range_proof::GeneratorsFactory gf; - bulletproofs::RangeProofLogic rp; - std::vector> vProofs; + bulletproofs_plus::RangeProofLogic rp; + std::vector> vProofs; std::vector vMessages; std::vector vPubKeys; MclG1Point balanceKey; @@ -46,11 +46,11 @@ bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationS } CAmount nFee = 0; - bulletproofs::RangeProofWithSeed stakedCommitmentRangeProof; + bulletproofs_plus::RangeProofWithSeed stakedCommitmentRangeProof; for (auto& out : tx.vout) { if (out.IsBLSCT()) { - bulletproofs::RangeProofWithSeed proof{out.blsctData.rangeProof, out.tokenId}; + bulletproofs_plus::RangeProofWithSeed proof{out.blsctData.rangeProof, out.tokenId}; auto out_hash = out.GetHash(); vPubKeys.emplace_back(out.blsctData.ephemeralKey); @@ -63,7 +63,7 @@ bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationS stakedCommitmentRangeProof.Vs.Clear(); stakedCommitmentRangeProof.Vs.Add(out.blsctData.rangeProof.Vs[0]); - proof = bulletproofs::RangeProofWithSeed{stakedCommitmentRangeProof, TokenId(), minStake}; + proof = bulletproofs_plus::RangeProofWithSeed{stakedCommitmentRangeProof, TokenId(), minStake}; vProofs.push_back(proof); } diff --git a/src/core_io.h b/src/core_io.h index 5ede47cc069b3..282f88187bb2b 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -6,7 +6,7 @@ #define BITCOIN_CORE_IO_H #include -#include +#include #include #include @@ -55,7 +55,7 @@ UniValue ValueFromAmount(const CAmount amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx); std::string SighashToStr(unsigned char sighash_type); -void RangeProofToUniv(const bulletproofs::RangeProof& rp, UniValue& entry, const bool& extended = false); +void RangeProofToUniv(const bulletproofs_plus::RangeProof& rp, UniValue& entry, const bool& extended = false); void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex = true, bool include_address = false, const SigningProvider* provider = nullptr); void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS, bool extendedRangeProof = false); diff --git a/src/core_write.cpp b/src/core_write.cpp index dc5cba81d242e..bb81cd31a7a0d 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -168,7 +168,7 @@ void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool i out.pushKV("type", GetTxnOutputType(type)); } -void RangeProofToUniv(const bulletproofs::RangeProof& rp, UniValue& entry, const bool& extended) +void RangeProofToUniv(const bulletproofs_plus::RangeProof& rp, UniValue& entry, const bool& extended) { UniValue Vs{UniValue::VARR}; for (size_t i = 0; i < rp.Vs.Size(); i++) { @@ -189,14 +189,13 @@ void RangeProofToUniv(const bulletproofs::RangeProof& rp, UniValue& entry, } entry.pushKV("Rs", Rs); entry.pushKV("A", HexStr(rp.A.GetVch())); - entry.pushKV("S", HexStr(rp.S.GetVch())); - entry.pushKV("T1", HexStr(rp.T1.GetVch())); - entry.pushKV("T2", HexStr(rp.T2.GetVch())); + entry.pushKV("A_wip", HexStr(rp.A_wip.GetVch())); + entry.pushKV("B", HexStr(rp.B.GetVch())); + entry.pushKV("r_prime", HexStr(rp.r_prime.GetVch())); + entry.pushKV("s_prime", HexStr(rp.s_prime.GetVch())); + entry.pushKV("delta_prime", HexStr(rp.delta_prime.GetVch())); + entry.pushKV("alpha_hat", HexStr(rp.alpha_hat.GetVch())); entry.pushKV("tau_x", HexStr(rp.tau_x.GetVch())); - entry.pushKV("mu", HexStr(rp.mu.GetVch())); - entry.pushKV("a", HexStr(rp.a.GetVch())); - entry.pushKV("b", HexStr(rp.b.GetVch())); - entry.pushKV("t_hat", HexStr(rp.t_hat.GetVch())); } } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 6ff2fdf7a0f88..5ab023b507db4 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -162,7 +162,7 @@ class CTxOutBLSCTData MclG1Point spendingKey; MclG1Point ephemeralKey; MclG1Point blindingKey; - bulletproofs::RangeProof rangeProof; + bulletproofs_plus::RangeProof rangeProof; uint16_t viewTag; CTxOutBLSCTData() @@ -293,12 +293,12 @@ class CTxOut bool IsStakedCommitment() const { - bulletproofs::RangeProofWithSeed dummy; + bulletproofs_plus::RangeProofWithSeed dummy; return GetStakedCommitmentRangeProof(dummy); } - bool GetStakedCommitmentRangeProof(bulletproofs::RangeProofWithSeed& rangeProof) const + bool GetStakedCommitmentRangeProof(bulletproofs_plus::RangeProofWithSeed& rangeProof) const { if (!IsBLSCT()) return false; diff --git a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp index 83fc6149e7906..9121df410bf40 100644 --- a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp +++ b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp @@ -74,8 +74,7 @@ BOOST_AUTO_TEST_CASE(test_range_proof_prove_verify_one_value) auto p = rpl.Prove(vs, nonce, msg.second, token_id); auto is_valid = rpl.Verify( - std::vector> { p } - ); + std::vector>{p}); BOOST_CHECK(is_valid); } @@ -273,7 +272,7 @@ static void RunTestCase( auto token_id = GenTokenId(); auto nonce = GenNonce(); - std::vector> proofs; + std::vector> proofs; RangeProofLogic rpl; // calculate proofs @@ -385,7 +384,7 @@ BOOST_AUTO_TEST_CASE(test_range_proof_number_of_input_values) BOOST_AUTO_TEST_CASE(test_range_proof_validate_proofs_by_sizes) { auto gen_valid_proof_wo_value_commitments = [](size_t num_inputs) { - bulletproofs_plus::RangeProof p; + bulletproofs_plus::RangeProofWithSeed p; auto n = blsct::Common::GetFirstPowerOf2GreaterOrEqTo(num_inputs); for (size_t i=0; i> proofs; + std::vector> proofs; BOOST_CHECK_NO_THROW(range_proof::Common::ValidateProofsBySizes(proofs)); } { // no value commitment - bulletproofs_plus::RangeProof p; - std::vector> proofs { p }; + bulletproofs_plus::RangeProofWithSeed p; + std::vector> proofs{p}; BOOST_CHECK_THROW(range_proof::Common::ValidateProofsBySizes(proofs), std::runtime_error); } { // minimum number of value commitments auto p = gen_valid_proof_wo_value_commitments(1); - std::vector> proofs { p }; + std::vector> proofs{p}; BOOST_CHECK_NO_THROW(range_proof::Common::ValidateProofsBySizes(proofs)); } { // maximum number of value commitments auto p = gen_valid_proof_wo_value_commitments(range_proof::Setup::max_input_values); - std::vector> proofs { p }; + std::vector> proofs{p}; BOOST_CHECK_NO_THROW(range_proof::Common::ValidateProofsBySizes(proofs)); } { // number of value commitments exceeding maximum auto p = gen_valid_proof_wo_value_commitments(range_proof::Setup::max_input_values + 1); - std::vector> proofs { p }; + std::vector> proofs{p}; BOOST_CHECK_THROW(range_proof::Common::ValidateProofsBySizes(proofs), std::runtime_error); } } diff --git a/src/test/blsct/set_mem_proof/set_mem_proof_prover_tests.cpp b/src/test/blsct/set_mem_proof/set_mem_proof_prover_tests.cpp index 26ef817f0d475..4660ebcc74edc 100644 --- a/src/test/blsct/set_mem_proof/set_mem_proof_prover_tests.cpp +++ b/src/test/blsct/set_mem_proof/set_mem_proof_prover_tests.cpp @@ -4,18 +4,18 @@ #define BOOST_UNIT_TEST -#include -#include -#include -#include -#include #include +#include #include -#include -#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include using Scalar = Mcl::Scalar; using Scalars = Elements; @@ -387,16 +387,16 @@ static MsgPair GenMsgPair(std::string s) return std::pair(s, message); } -static bulletproofs::RangeProof CreateTokenIdRangeProof( +static bulletproofs_plus::RangeProof CreateTokenIdRangeProof( Point nonce, - Scalar value -) { + Scalar value) +{ auto msg = GenMsgPair("test"); Scalars vs; vs.Add(value); - bulletproofs::RangeProofLogic rp; + bulletproofs_plus::RangeProofLogic rp; auto proof = rp.Prove(vs, nonce, msg.second, TokenId()); return proof; @@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(test_pos_scenario) BOOST_CHECK_EQUAL(res, true); - bulletproofs::RangeProofLogic rp; + bulletproofs_plus::RangeProofLogic rp; Scalars vs; vs.Add(value); @@ -458,8 +458,8 @@ BOOST_AUTO_TEST_CASE(test_pos_scenario) BOOST_CHECK(rproof.Vs[0] == proof.phi); - std::vector> rproofs; - bulletproofs::RangeProofWithSeed p{rproof, eta_phi, value - Scalar(1)}; + std::vector> rproofs; + bulletproofs_plus::RangeProofWithSeed p{rproof, eta_phi, value - Scalar(1)}; rproofs.emplace_back(p); res = rp.Verify(rproofs); From 22e06c9612ed3003d30cb8e85121a3e22c772529 Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 11 Oct 2024 16:18:08 +0200 Subject: [PATCH 02/27] include block txs in generator seed --- src/blsct/pos/helpers.cpp | 4 ++-- src/blsct/pos/helpers.h | 2 +- src/blsct/pos/pos.cpp | 6 +++--- src/blsct/pos/pos.h | 2 +- src/blsct/pos/proof.cpp | 4 ++-- src/blsct/pos/proof_logic.cpp | 4 ++-- src/navio-staker.cpp | 2 +- src/rpc/mining.cpp | 2 +- .../bulletproofs_plus_range_proof_logic_tests.cpp | 5 +++-- 9 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/blsct/pos/helpers.cpp b/src/blsct/pos/helpers.cpp index a9635fc4a5e8d..13f1b3b88c74c 100644 --- a/src/blsct/pos/helpers.cpp +++ b/src/blsct/pos/helpers.cpp @@ -9,11 +9,11 @@ namespace blsct { uint256 -CalculateKernelHash(const uint32_t& prevTime, const uint64_t& stakeModifier, const MclG1Point& phi, const uint32_t& time) +CalculateKernelHash(const uint32_t& prevTime, const uint64_t& stakeModifier, const uint32_t& time) { HashWriter ss{}; - ss << prevTime << stakeModifier << phi << time; + ss << prevTime << stakeModifier << time; return ss.GetHash(); } diff --git a/src/blsct/pos/helpers.h b/src/blsct/pos/helpers.h index 92ee4146ca1ff..e248e83c09788 100644 --- a/src/blsct/pos/helpers.h +++ b/src/blsct/pos/helpers.h @@ -11,7 +11,7 @@ #define MODIFIER_INTERVAL_RATIO 3 namespace blsct { -uint256 CalculateKernelHash(const uint32_t& prevTime, const uint64_t& stakeModifier, const MclG1Point& phi, const uint32_t& time); +uint256 CalculateKernelHash(const uint32_t& prevTime, const uint64_t& stakeModifier, const uint32_t& time); } // namespace blsct #endif // BLSCT_POS_H \ No newline at end of file diff --git a/src/blsct/pos/pos.cpp b/src/blsct/pos/pos.cpp index e61ab76a6545c..91103f08b593a 100644 --- a/src/blsct/pos/pos.cpp +++ b/src/blsct/pos/pos.cpp @@ -119,11 +119,11 @@ std::vector CalculateSetMemProofRandomness(const CBlockIndex* pin blsct::Message -CalculateSetMemProofGeneratorSeed(const CBlockIndex* pindexPrev) +CalculateSetMemProofGeneratorSeed(const CBlockIndex* pindexPrev, const CBlock& block) { HashWriter ss{}; - ss << pindexPrev->nHeight << pindexPrev->nStakeModifier; + ss << pindexPrev->nHeight << pindexPrev->nStakeModifier << TX_NO_WITNESS(block.vtx); auto hash = ss.GetHash(); @@ -132,6 +132,6 @@ CalculateSetMemProofGeneratorSeed(const CBlockIndex* pindexPrev) uint256 CalculateKernelHash(const CBlockIndex* pindexPrev, const CBlock& block) { - return CalculateKernelHash(pindexPrev->nTime, pindexPrev->nStakeModifier, block.posProof.setMemProof.phi, block.nTime); + return CalculateKernelHash(pindexPrev->nTime, pindexPrev->nStakeModifier, block.nTime); } } // namespace blsct \ No newline at end of file diff --git a/src/blsct/pos/pos.h b/src/blsct/pos/pos.h index cb692a451371a..7cf90d06a73e7 100644 --- a/src/blsct/pos/pos.h +++ b/src/blsct/pos/pos.h @@ -20,7 +20,7 @@ bool GetLastStakeModifier(const CBlockIndex* pindex, uint64_t& nStakeModifier, i int64_t GetStakeModifierSelectionIntervalSection(int nSection, const Consensus::Params& params); int64_t GetStakeModifierSelectionInterval(const Consensus::Params& params); std::vector CalculateSetMemProofRandomness(const CBlockIndex* pindexPrev); -blsct::Message CalculateSetMemProofGeneratorSeed(const CBlockIndex* pindexPrev); +blsct::Message CalculateSetMemProofGeneratorSeed(const CBlockIndex* pindexPrev, const CBlock& block); uint256 CalculateKernelHash(const CBlockIndex* pindexPrev, const CBlock& block); } // namespace blsct diff --git a/src/blsct/pos/proof.cpp b/src/blsct/pos/proof.cpp index e2bb3e2c04aa8..5e541c9aa976e 100644 --- a/src/blsct/pos/proof.cpp +++ b/src/blsct/pos/proof.cpp @@ -35,7 +35,7 @@ ProofOfStake::ProofOfStake(const Points& staked_commitments, const Scalar& eta_f setMemProof = SetProver::Prove(setup, staked_commitments, sigma, m, f, eta_fiat_shamir, eta_phi); - auto kernel_hash = CalculateKernelHash(prev_time, stake_modifier, setMemProof.phi, time); + auto kernel_hash = CalculateKernelHash(prev_time, stake_modifier, time); uint256 min_value = CalculateMinValue(kernel_hash, next_target); range_proof::GammaSeed gamma_seed(Scalars({f})); @@ -55,7 +55,7 @@ ProofOfStake::ProofOfStake(const Points& staked_commitments, const Scalar& eta_f ProofOfStake::VerificationResult ProofOfStake::Verify(const Points& staked_commitments, const Scalar& eta_fiat_shamir, const blsct::Message& eta_phi, const uint32_t& prev_time, const uint64_t& stake_modifier, const uint32_t& time, const unsigned int& next_target) const { - return Verify(staked_commitments, eta_fiat_shamir, eta_phi, CalculateKernelHash(prev_time, stake_modifier, setMemProof.phi, time), next_target); + return Verify(staked_commitments, eta_fiat_shamir, eta_phi, CalculateKernelHash(prev_time, stake_modifier, time), next_target); } ProofOfStake::VerificationResult ProofOfStake::Verify(const Points& staked_commitments, const Scalar& eta_fiat_shamir, const blsct::Message& eta_phi, const uint256& kernel_hash, const unsigned int& next_target) const diff --git a/src/blsct/pos/proof_logic.cpp b/src/blsct/pos/proof_logic.cpp index 6bf41fe5116b9..4c506621719f8 100644 --- a/src/blsct/pos/proof_logic.cpp +++ b/src/blsct/pos/proof_logic.cpp @@ -18,7 +18,7 @@ ProofOfStake ProofOfStakeLogic::Create(const CCoinsViewCache& cache, const Scala { auto staked_commitments = cache.GetStakedCommitments().GetElements(); auto eta_fiat_shamir = blsct::CalculateSetMemProofRandomness(pindexPrev); - auto eta_phi = blsct::CalculateSetMemProofGeneratorSeed(pindexPrev); + auto eta_phi = blsct::CalculateSetMemProofGeneratorSeed(pindexPrev, block); auto next_target = blsct::GetNextTargetRequired(pindexPrev, &block, params); @@ -37,7 +37,7 @@ bool ProofOfStakeLogic::Verify(const CCoinsViewCache& cache, const CBlockIndex* } auto eta_fiat_shamir = blsct::CalculateSetMemProofRandomness(pindexPrev); - auto eta_phi = blsct::CalculateSetMemProofGeneratorSeed(pindexPrev); + auto eta_phi = blsct::CalculateSetMemProofGeneratorSeed(pindexPrev, block); auto kernel_hash = blsct::CalculateKernelHash(pindexPrev, block); auto next_target = blsct::GetNextTargetRequired(pindexPrev, &block, params); diff --git a/src/navio-staker.cpp b/src/navio-staker.cpp index 45b49032001bf..0ce25a0a1ba9f 100644 --- a/src/navio-staker.cpp +++ b/src/navio-staker.cpp @@ -784,7 +784,7 @@ std::optional GetBlockProposal(const std::unique_ptr proposal.posProof = blsct::ProofOfStake(staked_elements, eta_fiat_shamir, eta_phi, m, f, prev_time, modifier, proposal.nTime, next_target); proposal.hashMerkleRoot = BlockMerkleRoot(proposal); - auto valid = blsct::ProofOfStake(proposal.posProof).Verify(staked_elements, eta_fiat_shamir, eta_phi, blsct::CalculateKernelHash(prev_time, modifier, proposal.posProof.setMemProof.phi, proposal.nTime), next_target); + auto valid = blsct::ProofOfStake(proposal.posProof).Verify(staked_elements, eta_fiat_shamir, eta_phi, blsct::CalculateKernelHash(prev_time, modifier, proposal.nTime), next_target); if (valid == blsct::ProofOfStake::VALID) return proposal; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 64c9b29955f97..0b1b401a755b1 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1026,7 +1026,7 @@ static RPCHelpMan getblocktemplate() result.pushKV("staked_commitments", stakedCommitments); result.pushKV("eta_fiat_shamir", HexStr(blsct::CalculateSetMemProofRandomness(pindexPrev))); - result.pushKV("eta_phi", HexStr(blsct::CalculateSetMemProofGeneratorSeed(pindexPrev))); + result.pushKV("eta_phi", HexStr(blsct::CalculateSetMemProofGeneratorSeed(pindexPrev, *pblock))); result.pushKV("prev_time", pindexPrev->nTime); result.pushKV("modifier", pindexPrev->nStakeModifier); } diff --git a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp index 9121df410bf40..dd5f2d24e74db 100644 --- a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp +++ b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(test_range_proof_prove_verify_one_value) auto p = rpl.Prove(vs, nonce, msg.second, token_id); auto is_valid = rpl.Verify( - std::vector>{p}); + std::vector>{bulletproofs_plus::RangeProofWithSeed(p, token_id)}); BOOST_CHECK(is_valid); } @@ -93,8 +93,9 @@ BOOST_AUTO_TEST_CASE(test_range_proof_recovery_one_value) RangeProofLogic rpl; auto p = rpl.Prove(vs, nonce, msg.second, token_id); + bulletproofs_plus::RangeProofWithSeed proofWithSeed = {p, token_id}; - auto req = bulletproofs_plus::AmountRecoveryRequest::of(p, nonce); + auto req = bulletproofs_plus::AmountRecoveryRequest::of(proofWithSeed, nonce); auto reqs = std::vector> { req }; auto result = rpl.RecoverAmounts(reqs); From d80912263032e55936b4d3762a293c9574b96c0f Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 11 Oct 2024 17:13:01 +0200 Subject: [PATCH 03/27] fix test --- ...letproofs_plus_range_proof_logic_tests.cpp | 300 ++++++++++-------- src/test/blsct/wallet/txfactory_tests.cpp | 10 +- src/wallet/wallet.cpp | 2 +- 3 files changed, 165 insertions(+), 147 deletions(-) diff --git a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp index dd5f2d24e74db..d8ba73a472351 100644 --- a/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp +++ b/src/test/blsct/range_proof/bulletproofs_plus/bulletproofs_plus_range_proof_logic_tests.cpp @@ -33,6 +33,7 @@ struct TestCase size_t num_amounts; bool verify_result; MsgPair msg; + Scalar min_value; }; static MclG1Point GenNonce() @@ -109,208 +110,224 @@ BOOST_AUTO_TEST_CASE(test_range_proof_recovery_one_value) static std::vector BuildTestCases() { - Scalar one(1); - Scalar two(2); - Scalar lower_bound(0); - Scalar upper_bound = (one << 64) - one; // int64_t max - // [LB, LB+1, UB-1, UB] - Scalars valid_inputs; - valid_inputs.Add(lower_bound); - valid_inputs.Add(lower_bound + one); - valid_inputs.Add(upper_bound - one); - valid_inputs.Add(upper_bound); - - // [-1, UB+1, UB+2, UB*2] - Scalars invalid_inputs; - invalid_inputs.Add(one.Negate()); - invalid_inputs.Add(upper_bound + one); - invalid_inputs.Add(upper_bound + one + one); - invalid_inputs.Add(upper_bound << 1); - + bulletproofs_plus::RangeProofLogic rp; std::vector test_cases; - // test single valid value - for (auto value: valid_inputs.m_vec) { - Scalars values; - values.Add(value); - - TestCase x; - x.name = strprintf("valid input value %s", value.GetString()).c_str(); - x.values = values; - x.is_batched = false; - x.should_complete_recovery = true; - x.num_amounts = 1; - x.msg = GenMsgPair(); - x.verify_result = true; - test_cases.push_back(x); - } - - // test single invalid value - for (auto value: invalid_inputs.m_vec) { - Scalars values; - values.Add(value); - - TestCase x; - x.name = strprintf("invalid input value %s", value.GetString()).c_str(); - x.values = values; - x.is_batched = false; - x.should_complete_recovery = true; - x.num_amounts = 0; - x.msg = GenMsgPair(); - x.verify_result = false; - test_cases.push_back(x); - } - - // test batched valid values - { - TestCase x; - x.name = "batched valid values"; - x.values = valid_inputs; - x.is_batched = true; - x.should_complete_recovery = true; - x.num_amounts = 0; - x.msg = GenMsgPair(); - x.verify_result = true; - test_cases.push_back(x); - } - - // test batched invalid values - { - TestCase x; - x.name = "batched invalid values"; - x.values = invalid_inputs; - x.is_batched = true; - x.should_complete_recovery = true; - x.num_amounts = 0; - x.msg = GenMsgPair(); - x.verify_result = false; - test_cases.push_back(x); - } - - // test with messages of various length - { - Scalars values; - values.Add(Scalar(1)); + for (auto& lower_bound : {Scalar(0), Scalar(100)}) { + Scalar one(1); + Scalar two(2); + Scalar upper_bound = (one << 64) - one; // int64_t max + // [LB, LB+1, UB-1, UB] + Scalars valid_inputs; + valid_inputs.Add(lower_bound); + valid_inputs.Add(lower_bound + one); + valid_inputs.Add(upper_bound - one); + valid_inputs.Add(upper_bound); + + // [-1, UB+1, UB+2, UB*2] + Scalars invalid_inputs; + invalid_inputs.Add(one.Negate()); + invalid_inputs.Add(upper_bound + one); + invalid_inputs.Add(upper_bound + one + one); + invalid_inputs.Add(upper_bound << 1); + + // test single valid value + for (auto value : valid_inputs.m_vec) { + Scalars values; + values.Add(value); - std::vector msg_sizes { 1ul, 23ul, 24ul, range_proof::Setup::max_message_size }; - for (auto msg_size: msg_sizes) { TestCase x; - x.name = strprintf("with message of length %d", msg_size).c_str(); + x.name = strprintf("valid input value %s min_value=%s", value.GetString(), lower_bound.GetString()).c_str(); x.values = values; - x.is_batched = true; + x.is_batched = false; x.should_complete_recovery = true; x.num_amounts = 1; - x.msg = GenMsgPair(std::string(msg_size, 'x')); - x.verify_result = true; + x.msg = GenMsgPair(); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; test_cases.push_back(x); } - } - // test # of input values from 1 to max - { - for (size_t n=1; n<=range_proof::Setup::max_input_values; ++n) { + // test single invalid value + for (auto value : invalid_inputs.m_vec) { Scalars values; - for (size_t i=0; i= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; + test_cases.push_back(x); + } + + // test batched valid values + { + TestCase x; + x.name = "batched valid values"; + x.values = valid_inputs; x.is_batched = true; x.should_complete_recovery = true; - x.num_amounts = n == 1 ? 1 : 0; // recovery should be performed only when n=1 + x.num_amounts = 0; x.msg = GenMsgPair(); - x.verify_result = true; + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; test_cases.push_back(x); } - } - // test valid and invalid values mixed - { - Scalars values; - for (auto& s: valid_inputs.m_vec) values.Add(s); - for (auto& s: invalid_inputs.m_vec) values.Add(s); - - TestCase x; - x.name = "mix of valid and invalid values"; - x.values = values; - x.is_batched = true; - x.should_complete_recovery = true; - x.num_amounts = 0; - x.msg = GenMsgPair(); - x.verify_result = false; - test_cases.push_back(x); - } + // test batched invalid values + { + TestCase x; + x.name = "batched invalid values"; + x.values = invalid_inputs; + x.is_batched = true; + x.should_complete_recovery = true; + x.num_amounts = 0; + x.msg = GenMsgPair(); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; + test_cases.push_back(x); + } - { - // string of maximum message size 54 - const std::string s("Pneumonoultramicroscopicsilicovolcanoconiosis123456789"); - assert(s.size() == range_proof::Setup::max_message_size); - Scalars values; - values.Add(one); + // test with messages of various length + { + Scalars values; + values.Add(Scalar(1)); - for (size_t i=0; i<=s.size(); ++i) { // try message of size 0 to 54 - auto msg = s.substr(0, i); + std::vector msg_sizes{1ul, 23ul, 24ul, range_proof::Setup::max_message_size}; + for (auto msg_size : msg_sizes) { + TestCase x; + x.name = strprintf("with message of length %d min_value=%s", msg_size, lower_bound.GetString()).c_str(); + x.values = values; + x.is_batched = true; + x.should_complete_recovery = true; + x.num_amounts = 1; + x.msg = GenMsgPair(std::string(msg_size, 'x')); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; + test_cases.push_back(x); + } + } + + // test # of input values from 1 to max + { + for (size_t n = 1; n <= range_proof::Setup::max_input_values; ++n) { + Scalars values; + for (size_t i = 0; i < n; ++i) { + values.Add(Scalar(i + 1)); + } + TestCase x; + x.name = strprintf("%d valid input values min_value=%s", n, lower_bound.GetString()).c_str(); + x.values = values; + x.is_batched = true; + x.should_complete_recovery = true; + x.num_amounts = n == 1 ? 1 : 0; // recovery should be performed only when n=1 + x.msg = GenMsgPair(); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; + test_cases.push_back(x); + } + } + + // test valid and invalid values mixed + { + Scalars values; + for (auto& s : valid_inputs.m_vec) + values.Add(s); + for (auto& s : invalid_inputs.m_vec) + values.Add(s); TestCase x; - x.name = strprintf("message size %ld", i).c_str(); + x.name = "mix of valid and invalid values"; x.values = values; - x.is_batched = false; + x.is_batched = true; x.should_complete_recovery = true; - x.num_amounts = 1; - x.msg = GenMsgPair(msg); - x.verify_result = true; + x.num_amounts = 0; + x.msg = GenMsgPair(); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + x.min_value = lower_bound; test_cases.push_back(x); } + + { + // string of maximum message size 54 + const std::string s("Pneumonoultramicroscopicsilicovolcanoconiosis123456789"); + assert(s.size() == range_proof::Setup::max_message_size); + Scalars values; + values.Add(one); + + for (size_t i = 0; i <= s.size(); ++i) { // try message of size 0 to 54 + auto msg = s.substr(0, i); + + TestCase x; + x.name = strprintf("message size %ld min_value=%s", i, lower_bound.GetString()).c_str(); + x.values = values; + x.is_batched = false; + x.should_complete_recovery = true; + x.num_amounts = 1; + x.msg = GenMsgPair(msg); + x.verify_result = x.values >= lower_bound && x.values <= (upper_bound + lower_bound); + + x.min_value = lower_bound; + test_cases.push_back(x); + } + } } return test_cases; } static void RunTestCase( - TestCase& test_case -) { + bulletproofs_plus::RangeProofLogic& rp, + TestCase& test_case) +{ auto token_id = GenTokenId(); auto nonce = GenNonce(); std::vector> proofs; - RangeProofLogic rpl; // calculate proofs if (test_case.is_batched) { - auto proof = rpl.Prove(test_case.values, nonce, test_case.msg.second, token_id); - proofs.push_back(proof); + auto proof = rp.Prove(test_case.values, nonce, test_case.msg.second, token_id, test_case.min_value); + bulletproofs_plus::RangeProofWithSeed p{proof, token_id, test_case.min_value}; + proofs.emplace_back(p); } else { - for (auto value: test_case.values.m_vec) { + for (auto value : test_case.values.m_vec) { Scalars single_value_vec; single_value_vec.Add(value); - auto proof = rpl.Prove(single_value_vec, nonce, test_case.msg.second, token_id); - proofs.push_back(proof); + + auto proof = rp.Prove(single_value_vec, nonce, test_case.msg.second, token_id, test_case.min_value); + bulletproofs_plus::RangeProofWithSeed p{proof, token_id, test_case.min_value}; + proofs.emplace_back(p); } } // verify proofs - auto verify_result = rpl.Verify(proofs); + auto verify_result = rp.Verify(proofs); BOOST_CHECK(verify_result == test_case.verify_result); // recover value, gamma and message std::vector> reqs; - for (size_t i=0; i::of(proofs[i], nonce)); } - auto recovery_result = rpl.RecoverAmounts(reqs); + auto recovery_result = rp.RecoverAmounts(reqs); BOOST_CHECK(recovery_result.is_completed == test_case.should_complete_recovery); if (recovery_result.is_completed) { auto amounts = recovery_result.amounts; + BOOST_CHECK(amounts.size() == test_case.num_amounts); - for (size_t i=0; i x_msg(x.message.begin(), x.message.end()); @@ -322,8 +339,9 @@ static void RunTestCase( BOOST_AUTO_TEST_CASE(test_range_proof_prove_verify_recovery) { auto test_cases = BuildTestCases(); + bulletproofs_plus::RangeProofLogic rp; for (auto test_case: test_cases) { - RunTestCase(test_case); + RunTestCase(rp, test_case); } } diff --git a/src/test/blsct/wallet/txfactory_tests.cpp b/src/test/blsct/wallet/txfactory_tests.cpp index 867189a9c7132..db26679f11129 100644 --- a/src/test/blsct/wallet/txfactory_tests.cpp +++ b/src/test/blsct/wallet/txfactory_tests.cpp @@ -93,7 +93,7 @@ BOOST_FIXTURE_TEST_CASE(createtransaction_test, TestingSetup) auto result = blsct_km->RecoverOutputs(finalTx.value().vout); for (auto& res : result.amounts) { - if (res.message == "Change" && res.amount == (1000 - 900 - 0.00304125) * COIN) fFoundChange = true; + if (res.message == "Change" && res.amount == (1000 - 900 - 0.00292125) * COIN) fFoundChange = true; } BOOST_CHECK(fFoundChange); @@ -151,7 +151,7 @@ BOOST_FIXTURE_TEST_CASE(addinput_test, TestingSetup) auto result = blsct_km->RecoverOutputs(finalTx.value().vout); for (auto& res : result.amounts) { - if (res.message == "Change" && res.amount == (1000 - 900 - 0.00304125) * COIN) fFoundChange = true; + if (res.message == "Change" && res.amount == (1000 - 900 - 0.00292125) * COIN) fFoundChange = true; } BOOST_CHECK(fFoundChange); @@ -165,7 +165,7 @@ BOOST_FIXTURE_TEST_CASE(addinput_test, TestingSetup) uint32_t nChangePosition = 0; for (auto& res : wtx->blsctRecoveryData) { - if (res.second.message == "Change" && res.second.amount == (1000 - 900 - 0.00304125) * COIN) { + if (res.second.message == "Change" && res.second.amount == (1000 - 900 - 0.00292125) * COIN) { nChangePosition = res.second.id; fFoundChange = true; break; @@ -189,8 +189,8 @@ BOOST_FIXTURE_TEST_CASE(addinput_test, TestingSetup) auto finalTx2 = tx2.BuildTx(); wallet->transactionAddedToMempool(MakeTransactionRef(finalTx2.value())); - BOOST_CHECK(wallet->GetDebit(CTransaction(finalTx2.value()), wallet::ISMINE_SPENDABLE_BLSCT) == (1000 - 900 - 0.00304125) * COIN); - BOOST_CHECK(TxGetCredit(*wallet, CTransaction(finalTx2.value()), wallet::ISMINE_SPENDABLE_BLSCT) == (1000 - 900 - 0.00304125 - 50 - 0.00304125) * COIN); + BOOST_CHECK(wallet->GetDebit(CTransaction(finalTx2.value()), wallet::ISMINE_SPENDABLE_BLSCT) == (1000 - 900 - 0.00292125) * COIN); + BOOST_CHECK(TxGetCredit(*wallet, CTransaction(finalTx2.value()), wallet::ISMINE_SPENDABLE_BLSCT) == (1000 - 900 - 0.00292125 - 50 - 0.00292125) * COIN); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ba04e0d2c3560..4c9a4d963ecee 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3013,7 +3013,7 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri } // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys - const bool fFirstRun = ((walletInstance->m_spk_managers.empty() && !walletInstance->IsWalletFlagSet(WALLET_FLAG_BLSCT)) || !walletInstance->GetOrCreateBLSCTKeyMan()->CanGenerateKeys()) && + const bool fFirstRun = ((walletInstance->m_spk_managers.empty() && !walletInstance->IsWalletFlagSet(WALLET_FLAG_BLSCT)) || (walletInstance->IsWalletFlagSet(WALLET_FLAG_BLSCT) && !walletInstance->GetOrCreateBLSCTKeyMan()->CanGenerateKeys())) && !walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); if (fFirstRun) { From 5e7909ce1adb6c3678b4ec6c3d4073f0e772a85a Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 11 Oct 2024 17:48:34 +0200 Subject: [PATCH 04/27] MaybeUpdateBirthTime --- src/blsct/wallet/keyman.cpp | 6 ++++++ src/blsct/wallet/keyman.h | 2 ++ src/wallet/wallet.cpp | 1 + 3 files changed, 9 insertions(+) diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp index b33b604a59e73..2746e58bf160f 100644 --- a/src/blsct/wallet/keyman.cpp +++ b/src/blsct/wallet/keyman.cpp @@ -879,4 +879,10 @@ bool KeyMan::OutputIsChange(const CTxOut& out) const return false; } + +int64_t KeyMan::GetTimeFirstKey() const +{ + LOCK(cs_KeyStore); + return nTimeFirstKey; +} } // namespace blsct diff --git a/src/blsct/wallet/keyman.h b/src/blsct/wallet/keyman.h index 45e2e8692be6d..fd480729a3868 100644 --- a/src/blsct/wallet/keyman.h +++ b/src/blsct/wallet/keyman.h @@ -165,6 +165,8 @@ class KeyMan : public Manager, public KeyRing bool OutputIsChange(const CTxOut& out) const; + int64_t GetTimeFirstKey() const; + /** Keypool has new keys */ boost::signals2::signal NotifyCanGetAddressesChanged; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4c9a4d963ecee..8668cce0ba073 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3642,6 +3642,7 @@ void CWallet::SetupBLSCTKeyMan() } auto mblsctkm = std::unique_ptr(new blsct::KeyMan(*this, m_keypool_size)); m_blsct_key_manager = std::move(mblsctkm); + MaybeUpdateBirthTime(m_blsct_key_manager->GetTimeFirstKey()); } const CKeyingMaterial& CWallet::GetEncryptionKey() const From 80c5c3d9c4bc355f1f6a9b3d3b645f8f92aee21b Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 11 Oct 2024 17:57:04 +0200 Subject: [PATCH 05/27] thread support for bpplus verification --- .../bulletproofs_plus/range_proof_logic.cpp | 187 +++++++++--------- 1 file changed, 99 insertions(+), 88 deletions(-) diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp index c741f19d1b654..f86e4f77237af 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp @@ -6,13 +6,14 @@ #include #include #include -#include -#include #include +#include +#include #include #include #include #include +#include #include // Bulletproofs+ implementation based on @@ -405,107 +406,117 @@ bool RangeProofLogic::VerifyProofs( using Scalar = typename T::Scalar; using Scalars = Elements; + // Vector to hold future results from async tasks + std::vector> futures; + + // Atomic flag to signal abort + std::atomic abort_flag(false); + + futures.reserve(proof_transcripts.size()); + for (const RangeProofWithTranscript& pt : proof_transcripts) { - if (pt.proof.Ls.Size() != pt.proof.Rs.Size()) return false; + futures.emplace_back(std::async(std::launch::async, [this, &pt, max_mn]() -> bool { + if (abort_flag.load()) return false; // Early exit if another task has already failed - range_proof::Generators gens = m_common.Gf().GetInstance(pt.proof.seed); + if (pt.proof.Ls.Size() != pt.proof.Rs.Size()) return false; - auto gs = gens.GetGiSubset(pt.mn); - auto hs = gens.GetHiSubset(pt.mn); - auto h = gens.H; - auto g = gens.G; + range_proof::Generators gens = m_common.Gf().GetInstance(pt.proof.seed); - auto [ - two_pows, - y_asc_pows_mn, - y_desc_pows_mn, - z_asc_by_2_pows, - y_to_mn_plus_1 - ] = RangeProofLogic::ComputePowers(pt.y, pt.z, pt.m, pt.n); + auto gs = gens.GetGiSubset(pt.mn); + auto hs = gens.GetHiSubset(pt.mn); + auto h = gens.H; + auto g = gens.G; - // Compute: z^2 * 1, ..., z^2 * 2^n-1, z^4 * 1, ..., z^4 * 2^n-1, z^6 * 1, ... - Scalars z_times_two_pows; - { - for (auto z_pow: z_asc_by_2_pows.m_vec) { - for (auto two_pow: two_pows.m_vec) { - z_times_two_pows.Add(z_pow * two_pow); + auto [two_pows, + y_asc_pows_mn, + y_desc_pows_mn, + z_asc_by_2_pows, + y_to_mn_plus_1] = RangeProofLogic::ComputePowers(pt.y, pt.z, pt.m, pt.n); + + // Compute: z^2 * 1, ..., z^2 * 2^n-1, z^4 * 1, ..., z^4 * 2^n-1, z^6 * 1, ... + Scalars z_times_two_pows; + { + for (auto z_pow : z_asc_by_2_pows.m_vec) { + for (auto two_pow : two_pows.m_vec) { + z_times_two_pows.Add(z_pow * two_pow); + } } } - } - // Compute scalars for verification - auto [ - e_squares, - e_inv_squares, - s_vec - ] = RangeProofLogic::ComputeVeriScalars(pt.es, pt.mn); - - Scalars s_prime_vec = s_vec.Reverse(); - Scalar inv_final_e = pt.e_last_round.Invert(); - Scalar final_e_sq = pt.e_last_round.Square(); - Scalar inv_final_e_sq = final_e_sq.Invert(); - Scalar r_prime_inv_final_e_y = pt.proof.r_prime * inv_final_e * pt.y; - Scalar s_prime_inv_final_e = pt.proof.s_prime * inv_final_e; - Scalars inv_y_asc_pows_mn = Scalars::FirstNPow(pt.y.Invert(), pt.mn, 1); // skip first 1 - - // Compute generator exponents - Scalars gs_exp; - { - Scalar minus_z = pt.z.Negate(); - for (size_t i=0; i::ComputeVeriScalars(pt.es, pt.mn); + + Scalars s_prime_vec = s_vec.Reverse(); + Scalar inv_final_e = pt.e_last_round.Invert(); + Scalar final_e_sq = pt.e_last_round.Square(); + Scalar inv_final_e_sq = final_e_sq.Invert(); + Scalar r_prime_inv_final_e_y = pt.proof.r_prime * inv_final_e * pt.y; + Scalar s_prime_inv_final_e = pt.proof.s_prime * inv_final_e; + Scalars inv_y_asc_pows_mn = Scalars::FirstNPow(pt.y.Invert(), pt.mn, 1); // skip first 1 + + // Compute generator exponents + Scalars gs_exp; + { + Scalar minus_z = pt.z.Negate(); + for (size_t i = 0; i < pt.mn; ++i) { + Scalar s = s_vec[i]; + Scalar inv_y_pow = inv_y_asc_pows_mn[i]; + gs_exp.Add(minus_z + s.Negate() * inv_y_pow * r_prime_inv_final_e_y); + } } - } - Scalars hs_exp; - { - Scalar neg_s_prime_inv_final_e = s_prime_inv_final_e.Negate(); - for (size_t i=0; i lp; - lp.Add(pt.proof.A); - lp.Add(pt.proof.A_wip, pt.e_last_round.Invert()); - lp.Add(pt.proof.B, pt.e_last_round.Square().Invert()); - lp.Add(g, g_exp); - lp.Add(h, h_exp); - lp.Add(pt.proof.Ls, static_cast(e_squares)); - lp.Add(pt.proof.Rs, e_inv_squares); - lp.Add(gs, gs_exp); - lp.Add(hs, hs_exp); - - for (size_t i = 0; i < pt.proof.Vs.Size(); ++i) { - lp.Add(LazyPoint(pt.proof.Vs[i] - (gens.G * pt.proof.min_value), vs_exp[i])); - } + LazyPoints lp; + lp.Add(pt.proof.A); + lp.Add(pt.proof.A_wip, pt.e_last_round.Invert()); + lp.Add(pt.proof.B, pt.e_last_round.Square().Invert()); + lp.Add(g, g_exp); + lp.Add(h, h_exp); + lp.Add(pt.proof.Ls, static_cast(e_squares)); + lp.Add(pt.proof.Rs, e_inv_squares); + lp.Add(gs, gs_exp); + lp.Add(hs, hs_exp); + + for (size_t i = 0; i < pt.proof.Vs.Size(); ++i) { + lp.Add(LazyPoint(pt.proof.Vs[i] - (gens.G * pt.proof.min_value), vs_exp[i])); + } + + if (!lp.Sum().IsZero()) { + abort_flag.store(true); // Signal abort if verification fails + return false; + } + + return true; + })); + } - if (!lp.Sum().IsZero()) return false; + // Wait for all threads to finish and collect results + for (auto& fut : futures) { + if (!fut.get()) return false; } return true; From 847e55c9d58af5485df9e41df672e55fe414e607 Mon Sep 17 00:00:00 2001 From: alex v Date: Thu, 17 Oct 2024 11:59:07 +0200 Subject: [PATCH 06/27] expose abort_flag in async fn --- .../bulletproofs_plus/range_proof_logic.cpp | 15 +++++---------- .../bulletproofs_plus/range_proof_logic.h | 4 +--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp index f86e4f77237af..2c2c3c364d505 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.cpp @@ -400,9 +400,8 @@ template RangeProof RangeProofLogic::Prove( template bool RangeProofLogic::VerifyProofs( - const std::vector>& proof_transcripts, - const size_t& max_mn -) { + const std::vector>& proof_transcripts) +{ using Scalar = typename T::Scalar; using Scalars = Elements; @@ -415,7 +414,7 @@ bool RangeProofLogic::VerifyProofs( futures.reserve(proof_transcripts.size()); for (const RangeProofWithTranscript& pt : proof_transcripts) { - futures.emplace_back(std::async(std::launch::async, [this, &pt, max_mn]() -> bool { + futures.emplace_back(std::async(std::launch::async, [this, &pt, &abort_flag]() -> bool { if (abort_flag.load()) return false; // Early exit if another task has already failed if (pt.proof.Ls.Size() != pt.proof.Rs.Size()) return false; @@ -522,8 +521,7 @@ bool RangeProofLogic::VerifyProofs( return true; } template bool RangeProofLogic::VerifyProofs( - const std::vector>&, - const size_t& + const std::vector>& ); template @@ -544,12 +542,9 @@ bool RangeProofLogic::Verify( proof_transcripts.push_back(proof_transcript); } - const size_t max_mn = 1ull << max_num_rounds; return VerifyProofs( - proof_transcripts, - max_mn - ); + proof_transcripts); } template bool RangeProofLogic::Verify( const std::vector>&); diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h index 5086e4d0be3f7..5b90388a6b4f3 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof_logic.h @@ -102,9 +102,7 @@ class RangeProofLogic static size_t GetNumLeadingZeros(const uint32_t& n); bool VerifyProofs( - const std::vector>& proof_transcripts, - const size_t& max_mn - ); + const std::vector>& proof_transcripts); range_proof::Common m_common; }; From dce6216fc8034641b1252df7ec601dec723d6ca0 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 10 Nov 2024 22:09:28 +0100 Subject: [PATCH 07/27] tokens internal --- src/Makefile.am | 8 + src/blsct/common.h | 9 + src/blsct/eip_2333/bls12_381_keygen.cpp | 16 ++ src/blsct/eip_2333/bls12_381_keygen.h | 13 +- src/blsct/private_key.cpp | 5 + src/blsct/private_key.h | 1 + src/blsct/public_keys.cpp | 2 +- src/blsct/tokens/info.h | 88 ++++++++++ src/blsct/tokens/predicate.h | 12 ++ src/blsct/tokens/predicate_parser.cpp | 95 +++++++++++ src/blsct/tokens/predicate_parser.h | 194 ++++++++++++++++++++++ src/blsct/tokens/rpc.cpp | 119 +++++++++++++ src/blsct/tokens/rpc.h | 16 ++ src/blsct/wallet/keyman.cpp | 23 +++ src/blsct/wallet/keyman.h | 4 + src/blsct/wallet/rpc.cpp | 145 ++++++++++++++++ src/blsct/wallet/rpc.h | 23 +++ src/blsct/wallet/txfactory.cpp | 122 +++++++++++--- src/blsct/wallet/txfactory.h | 70 +++++++- src/blsct/wallet/txfactory_global.cpp | 90 +++++++--- src/blsct/wallet/txfactory_global.h | 25 ++- src/blsct/wallet/verification.cpp | 31 +++- src/blsct/wallet/verification.h | 3 +- src/coins.cpp | 118 ++++++++++++- src/coins.h | 73 +++++++- src/ctokens/tokenid.h | 5 + src/node/transaction.cpp | 1 - src/policy/policy.cpp | 14 +- src/primitives/transaction.cpp | 4 +- src/primitives/transaction.h | 10 ++ src/rpc/client.cpp | 5 + src/rpc/register.h | 2 + src/test/blsct/wallet/chain_tests.cpp | 2 +- src/test/blsct/wallet/txfactory_tests.cpp | 4 +- src/test/coins_tests.cpp | 5 +- src/test/fuzz/coins_view.cpp | 3 +- src/test/fuzz/coinscache_sim.cpp | 3 +- src/test/transaction_tests.cpp | 16 -- src/txdb.cpp | 134 ++++++++++++++- src/txdb.h | 10 +- src/validation.cpp | 7 +- src/wallet/interfaces.cpp | 25 ++- src/wallet/rpc/spend.cpp | 93 ++--------- src/wallet/rpc/wallet.cpp | 43 +++-- 44 files changed, 1471 insertions(+), 220 deletions(-) create mode 100644 src/blsct/tokens/info.h create mode 100644 src/blsct/tokens/predicate.h create mode 100644 src/blsct/tokens/predicate_parser.cpp create mode 100644 src/blsct/tokens/predicate_parser.h create mode 100644 src/blsct/tokens/rpc.cpp create mode 100644 src/blsct/tokens/rpc.h create mode 100644 src/blsct/wallet/rpc.cpp create mode 100644 src/blsct/wallet/rpc.h diff --git a/src/Makefile.am b/src/Makefile.am index 5cd18777ab870..d0dff830c370d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -197,12 +197,17 @@ BLSCT_H = \ blsct/set_mem_proof/set_mem_proof_prover.h \ blsct/set_mem_proof/set_mem_proof_setup.h \ blsct/signature.h \ + blsct/tokens/info.h \ + blsct/tokens/predicate.h \ + blsct/tokens/predicate_parser.h \ + blsct/tokens/rpc.h \ blsct/wallet/address.h \ blsct/wallet/hdchain.h \ blsct/wallet/helpers.h \ blsct/wallet/import_wallet_type.h \ blsct/wallet/keyman.h \ blsct/wallet/keyring.h \ + blsct/wallet/rpc.h \ blsct/wallet/txfactory.h \ blsct/wallet/txfactory_global.h \ blsct/wallet/verification.h @@ -247,6 +252,7 @@ BLSCT_CPP = \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/signature.cpp \ + blsct/tokens/predicate_parser.cpp \ blsct/wallet/verification.cpp @@ -559,6 +565,7 @@ libbitcoin_node_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ + blsct/tokens/rpc.cpp \ blsct/wallet/verification.cpp \ blsct/signature.cpp \ chain.cpp \ @@ -716,6 +723,7 @@ libbitcoin_wallet_a_SOURCES = \ blsct/wallet/helpers.cpp \ blsct/wallet/keyman.cpp \ blsct/wallet/keyring.cpp \ + blsct/wallet/rpc.cpp \ blsct/wallet/txfactory.cpp \ blsct/wallet/txfactory_global.cpp \ blsct/wallet/verification.cpp \ diff --git a/src/blsct/common.h b/src/blsct/common.h index b688b481b8187..91d45e2c6ddc4 100644 --- a/src/blsct/common.h +++ b/src/blsct/common.h @@ -25,6 +25,15 @@ class Common '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'}; + inline static const std::vector BLSCTFEE = { + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + 'B', 'L', 'S', 'C', 'T', 'F', 'E', 'E', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0'}; + static std::vector DataStreamToVector(const DataStream& st); /** diff --git a/src/blsct/eip_2333/bls12_381_keygen.cpp b/src/blsct/eip_2333/bls12_381_keygen.cpp index dc0220ceab594..9a78148415b2c 100644 --- a/src/blsct/eip_2333/bls12_381_keygen.cpp +++ b/src/blsct/eip_2333/bls12_381_keygen.cpp @@ -183,3 +183,19 @@ MclScalar BLS12_381_KeyGen::derive_child_SK(const MclScalar& parent_SK, const ui auto SK = HKDF_mod_r(std::vector(comp_PK.cbegin(), comp_PK.cend())); return SK; } + +MclScalar BLS12_381_KeyGen::derive_child_SK_hash(const MclScalar& parent_SK, const uint256& hash) +{ + auto ret = parent_SK; + for (auto i = 0; i < 8; i++) { + const uint8_t* pos = hash.begin() + i * 4; + uint32_t index = (uint8_t)(pos[0]) << 24 | + (uint8_t)(pos[1]) << 16 | + (uint8_t)(pos[2]) << 8 | + (uint8_t)(pos[3]); + + auto comp_PK = parent_SK_to_lamport_PK(ret, index); + ret = HKDF_mod_r(std::vector(comp_PK.cbegin(), comp_PK.cend())); + } + return ret; +} diff --git a/src/blsct/eip_2333/bls12_381_keygen.h b/src/blsct/eip_2333/bls12_381_keygen.h index 4b592511f9420..8e583a9a9f22f 100644 --- a/src/blsct/eip_2333/bls12_381_keygen.h +++ b/src/blsct/eip_2333/bls12_381_keygen.h @@ -20,6 +20,7 @@ class BLS12_381_KeyGen public: static MclScalar derive_master_SK(const std::vector& seed); static MclScalar derive_child_SK(const MclScalar& parent_SK, const uint32_t& index); + static MclScalar derive_child_SK_hash(const MclScalar& parent_SK, const uint256& hash); #ifndef BOOST_UNIT_TEST private: @@ -27,20 +28,20 @@ class BLS12_381_KeyGen inline static const uint32_t DigestSize = CSHA256::OUTPUT_SIZE; inline static const uint32_t NumLamportChunks = 255; - using LamportChunks = std::array,NumLamportChunks>; + using LamportChunks = std::array, NumLamportChunks>; - static std::array HKDF_Extract(const std::vector& salt, const std::vector& IKM); + static std::array HKDF_Extract(const std::vector& salt, const std::vector& IKM); template - static std::array HKDF_Expand(const std::array& PRK, const std::vector& info); + static std::array HKDF_Expand(const std::array& PRK, const std::vector& info); static std::vector I2OSP(const MclScalar& x, const size_t& xLen); - static MclScalar OS2IP(const std::array& X); + static MclScalar OS2IP(const std::array& X); static std::vector flip_bits(const std::vector& vec); - static LamportChunks bytes_split(const std::array& octet_string); + static LamportChunks bytes_split(const std::array& octet_string); static MclScalar HKDF_mod_r(const std::vector& IKM); static LamportChunks IKM_to_lamport_SK(const std::vector& IKM, const std::vector& salt); - static std::array parent_SK_to_lamport_PK(const MclScalar& parent_SK, const uint32_t& index); + static std::array parent_SK_to_lamport_PK(const MclScalar& parent_SK, const uint32_t& index); }; #endif // NAVIO_BLSCT_EIP_2333_BLS12_381_KEYGEN_H diff --git a/src/blsct/private_key.cpp b/src/blsct/private_key.cpp index 23bef42b4a160..3c2cfc9d1f916 100644 --- a/src/blsct/private_key.cpp +++ b/src/blsct/private_key.cpp @@ -67,6 +67,11 @@ Signature PrivateKey::SignBalance() const return CoreSign(Common::BLSCTBALANCE); } +Signature PrivateKey::SignFee() const +{ + return CoreSign(Common::BLSCTFEE); +} + Signature PrivateKey::Sign(const uint256& msg) const { return Sign(Message(msg.begin(), msg.end())); diff --git a/src/blsct/private_key.h b/src/blsct/private_key.h index 7c93b4ea1bb72..06263d933c2a2 100644 --- a/src/blsct/private_key.h +++ b/src/blsct/private_key.h @@ -50,6 +50,7 @@ class PrivateKey // Basic scheme Signature SignBalance() const; + Signature SignFee() const; // Message augmentation scheme Signature Sign(const uint256& msg) const; diff --git a/src/blsct/public_keys.cpp b/src/blsct/public_keys.cpp index af0d2c238956b..5be62af6405a3 100644 --- a/src/blsct/public_keys.cpp +++ b/src/blsct/public_keys.cpp @@ -76,7 +76,7 @@ bool PublicKeys::VerifyBatch(const std::vector& msgs, const std::vector> aug_msgs; auto msg = msgs.begin(); for (auto pk = m_pks.begin(), end = m_pks.end(); pk != end; ++pk, ++msg) { - if (*msg == blsct::Common::BLSCTBALANCE && fVerifyTx) { + if ((*msg == blsct::Common::BLSCTBALANCE || *msg == blsct::Common::BLSCTFEE) && fVerifyTx) { aug_msgs.push_back(*msg); } else { aug_msgs.push_back(pk->AugmentMessage(*msg)); diff --git a/src/blsct/tokens/info.h b/src/blsct/tokens/info.h new file mode 100644 index 0000000000000..a01c4341ad8f5 --- /dev/null +++ b/src/blsct/tokens/info.h @@ -0,0 +1,88 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef NAVIO_BLSCT_TOKENS_INFO_H +#define NAVIO_BLSCT_TOKENS_INFO_H + +#include +#include +#include +#include + +namespace blsct { +enum TokenType : unsigned char { + TOKEN = 0, + NFT = 1 +}; +std::string TokenTypeToString(const TokenType& type) +{ + switch (type) { + case TOKEN: { + return "token"; + } + case NFT: { + return "nft"; + } + default: + return "unknown"; + } +} +class TokenInfo +{ +public: + TokenType type; + blsct::PublicKey publicKey; + std::map mapMetadata; + CAmount nTotalSupply; + + TokenInfo(const TokenType& type, const blsct::PublicKey& publicKey, const std::map& mapMetadata, + const CAmount& nTotalSupply) : type(type), publicKey(publicKey), mapMetadata(mapMetadata), nTotalSupply(nTotalSupply){}; + TokenInfo(){}; + + SERIALIZE_METHODS(TokenInfo, obj) { READWRITE(static_cast(obj.type), obj.publicKey, obj.mapMetadata, obj.nTotalSupply); }; + + std::string ToString() const + { + std::string ret = strprintf("type=%s publicKey=%s", TokenTypeToString(type), publicKey.ToString()); + for (auto& it : mapMetadata) { + ret += strprintf(" %s=%s", it.first, it.second); + } + ret += strprintf(" nTotalSupply=%s", FormatMoney(nTotalSupply)); + return ret; + } +}; + +class TokenEntry +{ +public: + TokenInfo info; + CAmount nSupply; + std::map> mapMintedNft; + + TokenEntry(){}; + TokenEntry(const TokenInfo& info, + const CAmount& nSupply = 0) : info(info), nSupply(nSupply){}; + TokenEntry(const TokenInfo& info, + const std::map>& mapMintedNft) : info(info), mapMintedNft(mapMintedNft){}; + + bool Mint(const CAmount& amount) + { + if (amount + nSupply > info.nTotalSupply) + return false; + nSupply += nSupply; + return true; + }; + + SERIALIZE_METHODS(TokenEntry, obj) + { + READWRITE(obj.info); + if (obj.info.type == TOKEN) + READWRITE(obj.nSupply); + else if (obj.info.type == NFT) + READWRITE(obj.mapMintedNft); + }; +}; +} // namespace blsct + +#endif // NAVIO_BLSCT_TOKENS_INFO_H \ No newline at end of file diff --git a/src/blsct/tokens/predicate.h b/src/blsct/tokens/predicate.h new file mode 100644 index 0000000000000..406f9e0f49ac9 --- /dev/null +++ b/src/blsct/tokens/predicate.h @@ -0,0 +1,12 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef NAVIO_BLSCT_TOKENS_PREDICATE_H +#define NAVIO_BLSCT_TOKENS_PREDICATE_H + +namespace blsct { +typedef std::vector VectorPredicate; +} + +#endif // NAVIO_BLSCT_TOKENS_PREDICATE_H \ No newline at end of file diff --git a/src/blsct/tokens/predicate_parser.cpp b/src/blsct/tokens/predicate_parser.cpp new file mode 100644 index 0000000000000..67b7c0cab7504 --- /dev/null +++ b/src/blsct/tokens/predicate_parser.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +namespace blsct { +ParsedPredicate ParsePredicate(const VectorPredicate& vch) +{ + DataStream ss{vch}; + PredicateOperation op; + ss >> Using>(op); + + if (op == CREATE_TOKEN) { + CreateTokenPredicate p; + ss >> p; + return p; + } else if (op == MINT) { + MintTokenPredicate p; + ss >> p; + return p; + } else if (op == NFT_MINT) { + MintNftPredicate p; + ss >> p; + return p; + } else if (op == PAY_FEE) { + PayFeePredicate p; + ss >> p; + return p; + } else { + throw std::ios_base::failure("unknown predicate operation"); + } +} + +bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect) +{ + if (predicate.IsCreateTokenPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + if (!fDisconnect && view.HaveToken(hash)) return false; + + if (fDisconnect) + view.EraseToken(hash); + else + view.AddToken(hash, std::move(predicate.GetTokenInfo())); + + return true; + } else if (predicate.IsMintTokenPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + if (!view.HaveToken(hash)) + return false; + + blsct::TokenEntry token; + + if (!view.GetToken(hash, token)) + return false; + + if (!token.Mint(predicate.GetAmount() * (1 - 2 * fDisconnect))) + return false; + + view.AddToken(hash, std::move(token)); + + return true; + } else if (predicate.IsMintNftPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + if (!view.HaveToken(hash)) + return false; + + blsct::TokenEntry token; + + if (!view.GetToken(hash, token)) + return false; + + if (token.mapMintedNft.contains(predicate.GetNftId()) == !fDisconnect) + return false; + + token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); + + view.AddToken(hash, std::move(token)); + + return true; + } else if (predicate.IsPayFeePredicate()) { + return true; + } + + return false; +} + +bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect) +{ + return ExecutePredicate(ParsePredicate(vch), view, fDisconnect); +} +} // namespace blsct \ No newline at end of file diff --git a/src/blsct/tokens/predicate_parser.h b/src/blsct/tokens/predicate_parser.h new file mode 100644 index 0000000000000..338baa6915f73 --- /dev/null +++ b/src/blsct/tokens/predicate_parser.h @@ -0,0 +1,194 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef NAVIO_BLSCT_TOKENS_PREDICATE_PARSER_H +#define NAVIO_BLSCT_TOKENS_PREDICATE_PARSER_H + +#include +#include +#include +#include + +namespace blsct { + +enum PredicateOperation : uint8_t { + CREATE_TOKEN, + MINT, + NFT_MINT, + PAY_FEE +}; + +struct CreateTokenPredicate { + blsct::TokenInfo tokenInfo; + + CreateTokenPredicate(){}; + CreateTokenPredicate(const blsct::TokenInfo& tokenInfo) : tokenInfo(tokenInfo){}; + + SERIALIZE_METHODS(CreateTokenPredicate, obj) + { + READWRITE(obj.tokenInfo); + } + + VectorPredicate GetVch() + { + DataStream ss; + ss << CREATE_TOKEN; + ss << tokenInfo; + return VectorPredicate(ss.data(), ss.data() + ss.size()); + } +}; + +struct MintTokenPredicate { + blsct::PublicKey publicKey; + CAmount amount; + + MintTokenPredicate(){}; + MintTokenPredicate(const blsct::PublicKey& publicKey, const CAmount& amount) : publicKey(publicKey), amount(amount){}; + + SERIALIZE_METHODS(MintTokenPredicate, obj) + { + READWRITE(obj.publicKey, obj.amount); + } + + VectorPredicate GetVch() + { + DataStream ss; + ss << MINT; + ss << publicKey; + ss << amount; + return VectorPredicate(ss.data(), ss.data() + ss.size()); + } +}; + +struct MintNftPredicate { + blsct::PublicKey publicKey; + CAmount nftId; + std::map nftMetadata; + + MintNftPredicate(){}; + MintNftPredicate(const blsct::PublicKey& publicKey, const CAmount& nftId, const std::map& nftMetadata) : publicKey(publicKey), nftId(nftId), nftMetadata(nftMetadata){}; + + SERIALIZE_METHODS(MintNftPredicate, obj) + { + READWRITE(obj.publicKey, obj.nftId, obj.nftMetadata); + } + + VectorPredicate GetVch() + { + DataStream ss; + ss << NFT_MINT; + ss << publicKey; + ss << nftId; + ss << nftMetadata; + return VectorPredicate(ss.data(), ss.data() + ss.size()); + } +}; + +struct PayFeePredicate { + blsct::PublicKey publicKey; + + PayFeePredicate(){}; + PayFeePredicate(const blsct::PublicKey& publicKey) : publicKey(publicKey){}; + + SERIALIZE_METHODS(PayFeePredicate, obj) + { + READWRITE(obj.publicKey); + } + + VectorPredicate GetVch() + { + DataStream ss; + ss << PAY_FEE; + ss << publicKey; + return VectorPredicate(ss.data(), ss.data() + ss.size()); + } +}; + +class ParsedPredicate +{ +public: + ParsedPredicate(CreateTokenPredicate& predicate) : predicate_(predicate) {} + ParsedPredicate(MintTokenPredicate& predicate) : predicate_(predicate) {} + ParsedPredicate(MintNftPredicate& predicate) : predicate_(predicate) {} + ParsedPredicate(PayFeePredicate& predicate) : predicate_(predicate) {} + + bool IsCreateTokenPredicate() const + { + return std::holds_alternative(predicate_); + } + + bool IsMintTokenPredicate() const + { + return std::holds_alternative(predicate_); + } + + bool IsMintNftPredicate() const + { + return std::holds_alternative(predicate_); + } + + bool IsPayFeePredicate() const + { + return std::holds_alternative(predicate_); + } + + blsct::PublicKey GetPublicKey() const + { + if (IsCreateTokenPredicate()) + return std::get(predicate_).tokenInfo.publicKey; + else if (IsMintTokenPredicate()) + return std::get(predicate_).publicKey; + else if (IsMintNftPredicate()) + return std::get(predicate_).publicKey; + else if (IsPayFeePredicate()) + return std::get(predicate_).publicKey; + else + throw std::ios_base::failure("wrong predicate type"); + } + + blsct::TokenInfo GetTokenInfo() const + { + if (IsCreateTokenPredicate()) + return std::get(predicate_).tokenInfo; + else + throw std::ios_base::failure("wrong predicate type"); + } + + CAmount GetAmount() const + { + if (IsMintTokenPredicate()) + return std::get(predicate_).amount; + else + throw std::ios_base::failure("wrong predicate type"); + } + + CAmount GetNftId() const + { + if (IsMintNftPredicate()) + return std::get(predicate_).nftId; + else + throw std::ios_base::failure("wrong predicate type"); + } + + std::map GetNftMetaData() const + { + if (IsMintNftPredicate()) + return std::get(predicate_).nftMetadata; + else + throw std::ios_base::failure("wrong predicate type"); + } + +private: + std::variant + predicate_; +}; + +ParsedPredicate ParsePredicate(const VectorPredicate& vch); +bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect = false); +bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect = false); + + +} // namespace blsct + +#endif // NAVIO_BLSCT_TOKENS_PREDICATE_PARSER_H \ No newline at end of file diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp new file mode 100644 index 0000000000000..519b38260a87b --- /dev/null +++ b/src/blsct/tokens/rpc.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2024 The Navio Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include + +std::vector tokenInfoResult = { + RPCResult{RPCResult::Type::STR_HEX, "tokenId", "the token id"}, + RPCResult{RPCResult::Type::STR_HEX, "publicKey", "the token public key"}, + RPCResult{RPCResult::Type::NUM, "type", "the token type"}, + RPCResult{RPCResult::Type::ANY, "metadata", "the token metadata"}, + RPCResult{RPCResult::Type::NUM, "maxSupply", "the token max supply"}, +}; + +void TokenToUniValue(const blsct::TokenEntry& token, UniValue& obj) { + obj.pushKV("publicKey", token.info.publicKey.ToString()); + obj.pushKV("type", blsct::TokenTypeToString(token.info.type)); + UniValue metadata{UniValue::VOBJ}; + for (auto& it : token.info.mapMetadata) { + metadata.pushKV(it.first, it.second); + } + obj.pushKV("metadata", metadata); + obj.pushKV("maxSupply", token.info.nTotalSupply); +} + +RPCHelpMan +gettoken() +{ + return RPCHelpMan{ + "gettoken", + "Returns an object containing information about a token.\n", + { + { + "token_id", + RPCArg::Type::STR_HEX, + RPCArg::Optional::NO, + "The token id", + }, + }, + RPCResult{RPCResult::Type::OBJ, "", "", tokenInfoResult}, + RPCExamples{HelpExampleCli("gettoken", "ba12afc43322f204fe6236b11a0f85b5d9edcb09f446176c73fe4abe99a17edd")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + LOCK(cs_main); + Chainstate& active_chainstate = chainman.ActiveChainstate(); + + CCoinsViewCache* coins_view; + coins_view = &active_chainstate.CoinsTip(); + + uint256 tokenId(ParseHashV(request.params[0], "txid")); + blsct::TokenEntry token; + if (!coins_view->GetToken(tokenId, token)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + UniValue obj{UniValue::VOBJ}; + obj.pushKV("tokenId", tokenId.ToString()); + TokenToUniValue(obj, token); + return obj; + }, + }; +}; + +RPCHelpMan +listtokens() +{ + return RPCHelpMan{ + "listtokens", + "Returns an array containing the tokens list.\n", + {}, + RPCResult{RPCResult::Type::ARR, "", "", { + RPCResult{RPCResult::Type::OBJ, "", "", tokenInfoResult}, + }}, + RPCExamples{HelpExampleCli("listtokens", "")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); + + LOCK(cs_main); + Chainstate& active_chainstate = chainman.ActiveChainstate(); + + CCoinsViewCache* coins_view; + coins_view = &active_chainstate.CoinsTip(); + + TokensMap tokens; + coins_view->GetAllTokens(tokens); + + UniValue ret{UniValue::VARR}; + + for (auto& it : tokens) { + uint256 key = it.first; + blsct::TokenEntry token = it.second.token; + UniValue obj{UniValue::VOBJ}; + obj.pushKV("tokenId", key.ToString()); + TokenToUniValue(obj, token); + ret.push_back(obj); + } + + return ret; + }, + }; +}; + +void RegisterTokenRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"blsct", &listtokens}, + {"blsct", &gettoken}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} \ No newline at end of file diff --git a/src/blsct/tokens/rpc.h b/src/blsct/tokens/rpc.h new file mode 100644 index 0000000000000..3da2f5defd7eb --- /dev/null +++ b/src/blsct/tokens/rpc.h @@ -0,0 +1,16 @@ +// Copyright (c) 2024 The Navio Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_BLSCT_TOKENS_RPC_H +#define BITCOIN_BLSCT_TOKENS_RPC_H + +#include + +class CRPCCommand; + +namespace blsct { +Span GetTokenRPCCommands(); +} // namespace blsct + +#endif // BITCOIN_BLSCT_TOKENS_RPC_H \ No newline at end of file diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp index 2746e58bf160f..eccd41703de46 100644 --- a/src/blsct/wallet/keyman.cpp +++ b/src/blsct/wallet/keyman.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include namespace blsct { @@ -484,6 +485,21 @@ blsct::PrivateKey KeyMan::GetMasterSeedKey() const return ret; } +blsct::PrivateKey KeyMan::GetMasterTokenKey() const +{ + if (!IsHDEnabled()) + throw std::runtime_error(strprintf("%s: the wallet has no HD enabled")); + + auto tokenKeyId = m_hd_chain.token_id; + + PrivateKey ret; + + if (!GetKey(tokenKeyId, ret)) + throw std::runtime_error(strprintf("%s: could not access the master token key", __func__)); + + return ret; +} + blsct::PrivateKey KeyMan::GetPrivateViewKey() const { if (!fViewKeyDefined) @@ -539,6 +555,13 @@ blsct::PrivateKey KeyMan::GetSpendingKeyForOutput(const CTxOut& out, const SubAd return CalculatePrivateSpendingKey(out.blsctData.blindingKey, viewKey.GetScalar(), sk.GetScalar(), id.account, id.address); } +blsct::PrivateKey KeyMan::GetTokenKey(const uint256& tokenId) const +{ + auto masterTokenKey = GetMasterTokenKey(); + + return BLS12_381_KeyGen::derive_child_SK_hash(masterTokenKey.GetScalar(), tokenId); +} + using Arith = Mcl; bulletproofs_plus::AmountRecoveryResult KeyMan::RecoverOutputs(const std::vector& outs) diff --git a/src/blsct/wallet/keyman.h b/src/blsct/wallet/keyman.h index fd480729a3868..22f0a6e417516 100644 --- a/src/blsct/wallet/keyman.h +++ b/src/blsct/wallet/keyman.h @@ -138,12 +138,16 @@ class KeyMan : public Manager, public KeyRing blsct::PrivateKey GetMasterSeedKey() const; blsct::PrivateKey GetPrivateViewKey() const; blsct::PublicKey GetPublicSpendingKey() const; + blsct::PrivateKey GetMasterTokenKey() const; blsct::PrivateKey GetSpendingKey() const; blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out) const; blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const CKeyID& id) const; blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const SubAddressIdentifier& id) const; bulletproofs_plus::AmountRecoveryResult RecoverOutputs(const std::vector& outs); + blsct::PrivateKey GetTokenKey(const uint256& tokenId) const; + blsct::PrivateKey GetTokenKey(const blsct::PublicKey& tokenPublicKey) const { return GetTokenKey(tokenPublicKey.GetHash()); }; + /** SubAddress keypool */ void LoadSubAddress(const CKeyID& hashId, const SubAddressIdentifier& index); bool AddSubAddress(const CKeyID& hashId, const SubAddressIdentifier& index); diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp new file mode 100644 index 0000000000000..5a862dc6fae3b --- /dev/null +++ b/src/blsct/wallet/rpc.cpp @@ -0,0 +1,145 @@ +// Copyright (c) 2024 The Navio Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blsct { +UniValue SendTransaction(wallet::CWallet& wallet, const blsct::CreateTransactionData& transactionData, const bool& verbose) +{ + // This should always try to sign, if we don't have private keys, don't try to do anything here. + if (wallet.IsWalletFlagSet(wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); + } + + // Send + auto res = blsct::TxFactory::CreateTransaction(&wallet, wallet.GetBLSCTKeyMan(), transactionData); + + if (!res) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Not enough funds available"); + } + + const CTransactionRef& tx = MakeTransactionRef(res.value()); + wallet::mapValue_t map_value; + wallet.CommitTransaction(tx, std::move(map_value), /*orderForm=*/{}); + if (verbose) { + UniValue entry(UniValue::VOBJ); + entry.pushKV("txid", tx->GetHash().GetHex()); + return entry; + } + return tx->GetHash().GetHex(); +} +} // namespace blsct + +UniValue CreateTokenOrNft(const RPCHelpMan& self, const JSONRPCRequest& request, const blsct::TokenType& type) +{ + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + auto blsct_km = pwallet->GetOrCreateBLSCTKeyMan(); + + std::map metadata; + if (!request.params[0].isNull() && !request.params[0].get_obj().empty()) + request.params[0].get_obj().getObjMap(metadata); + + std::map mapMetadata; + + for (auto& it : metadata) { + if (it.second.isNull() || !it.second.isStr() || it.second.get_str().empty()) + continue; + mapMetadata[it.first] = it.second.get_str(); + } + + CAmount max_supply = AmountFromValue(request.params[1]); + + blsct::TokenInfo tokenInfo; + tokenInfo.nTotalSupply = max_supply; + tokenInfo.mapMetadata = mapMetadata; + tokenInfo.type = type; + tokenInfo.publicKey = blsct_km->GetTokenKey((HashWriter{} << tokenInfo.mapMetadata << tokenInfo.nTotalSupply).GetHash()).GetPublicKey(); + + blsct::CreateTransactionData + transactionData(tokenInfo); + + EnsureWalletIsUnlocked(*pwallet); + + auto hash = blsct::SendTransaction(*pwallet, transactionData, false); + + UniValue ret{UniValue::VOBJ}; + ret.pushKV("hash", hash); + ret.pushKV("tokenId", tokenInfo.publicKey.GetHash().ToString()); + + return ret; +} + +RPCHelpMan createnft() +{ + return RPCHelpMan{ + "createnft", + "Submits a transaction creating a NFT\n", + {{ + "metadata", + RPCArg::Type::OBJ_USER_KEYS, + RPCArg::Optional::NO, + "The NFT metadata", + { + {"key", RPCArg::Type::STR, RPCArg::Optional::NO, "value"}, + }, + }, + {"max_supply", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The NFT max supply."}}, + RPCResult{ + RPCResult::Type::STR_HEX, "tokenId", "the token id"}, + RPCExamples{HelpExampleRpc("createnft", "{'name':'My NFT Collection'} 1000")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + return CreateTokenOrNft(self, request, blsct::NFT); + }, + }; +} + +RPCHelpMan createtoken() +{ + return RPCHelpMan{ + "createtoken", + "Submits a transaction creating a token.\n", + {{ + "metadata", + RPCArg::Type::OBJ_USER_KEYS, + RPCArg::Optional::NO, + "The token metadata", + { + {"key", RPCArg::Type::STR, RPCArg::Optional::NO, "value"}, + }, + }, + {"max_supply", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The token max supply."}}, + RPCResult{ + RPCResult::Type::STR_HEX, "tokenId", "the token id"}, + RPCExamples{HelpExampleRpc("createtoken", "{'name':'Token'} 1000")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + return CreateTokenOrNft(self, request, blsct::TOKEN); + }, + }; +} + +Span GetBLSCTWalletRPCCommands() +{ + static const CRPCCommand commands[]{ + {"blsct", &createnft}, + {"blsct", &createtoken}, + }; + return commands; +} \ No newline at end of file diff --git a/src/blsct/wallet/rpc.h b/src/blsct/wallet/rpc.h new file mode 100644 index 0000000000000..47c6571f516b6 --- /dev/null +++ b/src/blsct/wallet/rpc.h @@ -0,0 +1,23 @@ +// Copyright (c) 2024 The Navio Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_BLSCT_WALLET_RPC_H +#define BITCOIN_BLSCT_WALLET_RPC_H + +#include +#include + +namespace wallet { +class CWallet; +} + +namespace blsct { +UniValue SendTransaction(wallet::CWallet& wallet, const blsct::CreateTransactionData& transactionData, const bool& verbose); +} + +class CRPCCommand; + +Span GetBLSCTWalletRPCCommands(); + +#endif // BITCOIN_BLSCT_WALLET_RPC_H \ No newline at end of file diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp index 6ba9aef30e444..925ec9ef1a13c 100644 --- a/src/blsct/wallet/txfactory.cpp +++ b/src/blsct/wallet/txfactory.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include @@ -37,6 +38,53 @@ void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmo vOutputs[token_id].push_back(out); } +// Create token +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo) +{ + UnsignedOutput out; + + out = CreateOutput(tokenKey, tokenInfo); + + TokenId token_id{tokenInfo.publicKey.GetHash()}; + + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); + + vOutputs[token_id].push_back(out); +} + +// Mint Token + +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount) +{ + UnsignedOutput out; + + out = CreateOutput(destination.GetKeys(), mintAmount, Scalar::Rand(), tokenKey, tokenPublicKey); + + TokenId token_id{tokenPublicKey.GetHash()}; + + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); + + vOutputs[token_id].push_back(out); +} + +// Mint NFT + +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) +{ + UnsignedOutput out; + + out = CreateOutput(destination.GetKeys(), Scalar::Rand(), tokenKey, tokenPublicKey, nftId, nftMetadata); + + TokenId token_id{tokenPublicKey.GetHash(), nftId}; + + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); + + vOutputs[token_id].push_back(out); +} + bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) { if (vInputs.count(token_id) == 0) @@ -65,8 +113,16 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA for (auto& out_ : vOutputs) { for (auto& out : out_.second) { this->tx.vout.push_back(out.out); - outputGammas = outputGammas - out.gamma; - outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(out.out.GetHash())); + auto outHash = out.out.GetHash(); + + if (out.out.IsBLSCT()) { + outputGammas = outputGammas - out.gamma; + outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(outHash)); + } + + if (out.type == TX_CREATE_TOKEN || out.type == TX_MINT_TOKEN) { + outputSignatures.push_back(PrivateKey(out.tokenKey).Sign(outHash)); + } } } @@ -109,7 +165,7 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA mapInputs[in_.first] += in.value.GetUint64(); - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nFee) break; + if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + in_.first.IsNull() ? nFee : 0) break; } } @@ -137,8 +193,13 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA if (nFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { CTxOut fee_out{nFee, CScript(OP_RETURN)}; + auto feeKey = blsct::PrivateKey(MclScalar::Rand()); + fee_out.predicate = blsct::PayFeePredicate(feeKey.GetPublicKey()).GetVch(); + tx.vout.push_back(fee_out); txSigs.push_back(PrivateKey(gammaAcc).SignBalance()); + txSigs.push_back(PrivateKey(feeKey).SignFee()); + tx.txSig = Signature::Aggregate(txSigs); return tx; @@ -150,11 +211,11 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA return std::nullopt; } -std::optional TxFactoryBase::CreateTransaction(const std::vector& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake) +std::optional TxFactoryBase::CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData) { auto tx = blsct::TxFactoryBase(); - if (type == STAKED_COMMITMENT) { + if (transactionData.type == STAKED_COMMITMENT) { CAmount inputFromStakedCommitments = 0; for (const auto& output : inputCandidates) { @@ -164,19 +225,19 @@ std::optional TxFactoryBase::CreateTransaction(const std::v tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); } - if (nAmount + inputFromStakedCommitments < minStake) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(minStake))); + if (transactionData.nAmount + inputFromStakedCommitments < transactionData.minStake) { + throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); } bool fSubtractFeeFromAmount = false; // nAmount == inAmount + inputFromStakedCommitments; - tx.AddOutput(destination, nAmount + inputFromStakedCommitments, sMemo, token_id, type, minStake, fSubtractFeeFromAmount); + tx.AddOutput(transactionData.destination, transactionData.nAmount + inputFromStakedCommitments, transactionData.sMemo, transactionData.token_id, transactionData.type, transactionData.minStake, fSubtractFeeFromAmount); } else { CAmount inputFromStakedCommitments = 0; for (const auto& output : inputCandidates) { if (output.is_staked_commitment) { - if (!(type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE || type == CreateTransactionType::STAKED_COMMITMENT)) + if (!(transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE || transactionData.type == CreateTransactionType::STAKED_COMMITMENT)) continue; inputFromStakedCommitments += output.amount; } @@ -184,25 +245,32 @@ std::optional TxFactoryBase::CreateTransaction(const std::v tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); } - if (type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { - if (inputFromStakedCommitments - nAmount < 0) { + if (transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { + if (inputFromStakedCommitments - transactionData.nAmount < 0) { throw std::runtime_error(strprintf("Not enough staked coins")); - } else if (inputFromStakedCommitments - nAmount < minStake && inputFromStakedCommitments - nAmount > 0) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(minStake))); + } else if (inputFromStakedCommitments - transactionData.nAmount < transactionData.minStake && inputFromStakedCommitments - transactionData.nAmount > 0) { + throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); } - if (inputFromStakedCommitments - nAmount > 0) { + if (inputFromStakedCommitments - transactionData.nAmount > 0) { // CHANGE - tx.AddOutput(destination, inputFromStakedCommitments - nAmount, sMemo, token_id, CreateTransactionType::STAKED_COMMITMENT, minStake, false); + tx.AddOutput(transactionData.destination, inputFromStakedCommitments - transactionData.nAmount, transactionData.sMemo, transactionData.token_id, CreateTransactionType::STAKED_COMMITMENT, transactionData.minStake, false); } } bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; - tx.AddOutput(destination, nAmount, sMemo, token_id, type, minStake, fSubtractFeeFromAmount); + if (transactionData.type == TX_CREATE_TOKEN) { + tx.AddOutput(transactionData.tokenKey, transactionData.tokenInfo); + } else if (transactionData.type == TX_MINT_TOKEN) { + if (!transactionData.token_id.IsNFT()) + tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.nAmount); + else + tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.token_id.subid, transactionData.nftMetadata); + } } - return tx.BuildTx(changeDestination, minStake, type); + return tx.BuildTx(transactionData.changeDestination, transactionData.minStake, transactionData.type); } bool TxFactory::AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) @@ -293,20 +361,30 @@ void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* bl coins_params.include_staked_commitment = true; AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); } + + if (type == CreateTransactionType::NORMAL && !token_id.IsNull()) { + coins_params.token_id.SetNull(); + AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); + } } -std::optional TxFactory::CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake) +std::optional TxFactory::CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, CreateTransactionData transactionData) { LOCK(wallet->cs_wallet); std::vector inputCandidates; - TxFactoryBase::AddAvailableCoins(wallet, blsct_km, token_id, type, inputCandidates); + TxFactoryBase::AddAvailableCoins(wallet, blsct_km, transactionData.token_id, transactionData.type, inputCandidates); - auto changeType = type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE ? STAKING_ACCOUNT : CHANGE_ACCOUNT; - auto changeAddress = std::get(blsct_km->GetNewDestination(changeType).value()); + auto changeType = transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE ? STAKING_ACCOUNT : CHANGE_ACCOUNT; + + transactionData.changeDestination = std::get(blsct_km->GetNewDestination(changeType).value()); + + if (transactionData.type == TX_CREATE_TOKEN || transactionData.type == TX_MINT_TOKEN) { + transactionData.tokenKey = blsct_km->GetTokenKey((HashWriter{} << transactionData.tokenInfo.mapMetadata << transactionData.tokenInfo.nTotalSupply).GetHash()).GetScalar(); + } - return TxFactoryBase::CreateTransaction(inputCandidates, changeAddress, destination, nAmount, sMemo, token_id, type, minStake); + return TxFactoryBase::CreateTransaction(inputCandidates, transactionData); } } // namespace blsct \ No newline at end of file diff --git a/src/blsct/wallet/txfactory.h b/src/blsct/wallet/txfactory.h index b179124212ceb..c6ba1733425ca 100644 --- a/src/blsct/wallet/txfactory.h +++ b/src/blsct/wallet/txfactory.h @@ -9,12 +9,71 @@ #include #include #include +#include +#include #include #include #include +#include namespace blsct { +struct CreateTransactionData { + CreateTransactionType type; + blsct::TokenInfo tokenInfo; + blsct::DoublePublicKey changeDestination; + SubAddress destination; + CAmount nAmount; + std::string sMemo; + TokenId token_id; + CAmount minStake; + + Scalar tokenKey; + std::map nftMetadata; + + CreateTransactionData(const blsct::DoublePublicKey& changeDestination, + const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo, + const TokenId& token_id, + const CreateTransactionType& type, + const CAmount& minStake) : type(type), + changeDestination(changeDestination), + destination(destination), + nAmount(nAmount), + sMemo(sMemo), + token_id(token_id), + minStake(minStake) + { + } + + CreateTransactionData(const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo, + const TokenId& token_id, + const CreateTransactionType& type, + const CAmount& minStake) : type(type), + destination(destination), + nAmount(nAmount), + sMemo(sMemo), + token_id(token_id), + minStake(minStake) {} + + + CreateTransactionData(const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo) : type(NORMAL), + destination(destination), + nAmount(nAmount), + sMemo(sMemo) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo) : type(TX_CREATE_TOKEN), tokenInfo(tokenInfo) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& mintAmount, const SubAddress& destination) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), nAmount(mintAmount), token_id(TokenId(tokenInfo.publicKey.GetHash())) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& nftId, const SubAddress& destination, const std::map& nftMetadata) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), token_id(TokenId(tokenInfo.publicKey.GetHash(), nftId)), nftMetadata(nftMetadata) {} +}; + struct InputCandidates { CAmount amount; MclScalar gamma; @@ -38,10 +97,17 @@ class TxFactoryBase public: TxFactoryBase(){}; + // Normal transfer void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0, const bool& fSubtractFeeFromAmount = false); + // Create Token + void AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); + // Mint Token + void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount); + // Mint NFT + void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false); std::optional BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false); - static std::optional CreateTransaction(const std::vector& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0); + static std::optional CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData); static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const TokenId& token_id, const CreateTransactionType& type, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); }; @@ -57,7 +123,7 @@ class TxFactory : public TxFactoryBase bool AddInput(wallet::CWallet* wallet, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); bool AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false); std::optional BuildTx(); - static std::optional CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0); + static std::optional CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, CreateTransactionData transactionData); }; } // namespace blsct diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index 74f04a0d1427a..2bcc5a52782c6 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include using T = Mcl; @@ -42,11 +43,51 @@ Signature UnsignedOutput::GetSignature() const return Signature::Aggregate(txSigs); } +UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo) +{ + auto ret = CreateOutput(blsct::DoublePublicKey(), 0, "", TokenId(), Scalar::Rand(), TX_CREATE_TOKEN); + + ret.out.predicate = CreateTokenPredicate(tokenInfo).GetVch(); + ret.tokenKey = tokenKey; + + return ret; +} + +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey) +{ + TokenId tokenId{tokenPublicKey.GetHash()}; + + auto ret = CreateOutput(destKeys, nAmount, "", tokenId, blindingKey, TX_MINT_TOKEN); + + if (!tokenId.IsNFT()) { + ret.out.predicate = MintTokenPredicate(tokenPublicKey, nAmount).GetVch(); + } + ret.tokenKey = tokenKey; + + return ret; +} + +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) +{ + TokenId tokenId{tokenPublicKey.GetHash(), nftId}; + + auto ret = CreateOutput(destKeys, 0, "", tokenId, blindingKey, TX_MINT_TOKEN); + + if (tokenId.IsNFT()) { + ret.out.predicate = MintNftPredicate(tokenPublicKey, nftId, nftMetadata).GetVch(); + } + ret.tokenKey = tokenKey; + + return ret; +} + UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const Scalar& blindingKey, const CreateTransactionType& type, const CAmount& minStake) { bulletproofs_plus::RangeProofLogic rp; auto ret = UnsignedOutput(); + ret.type = type; + ret.out.nValue = 0; ret.out.tokenId = tokenId; @@ -70,28 +111,29 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun std::vector memo{sMemo.begin(), sMemo.end()}; - ret.out.scriptPubKey = CScript(OP_TRUE); + if (nAmount > 0) { + ret.out.scriptPubKey = CScript(OP_TRUE); - if (type == STAKED_COMMITMENT && tokenId.IsNull()) { - auto stakeRp = rp.Prove(vs, nonce, {}, tokenId, minStake); + if (type == STAKED_COMMITMENT && tokenId.IsNull()) { + auto stakeRp = rp.Prove(vs, nonce, {}, tokenId, minStake); - stakeRp.Vs.Clear(); + stakeRp.Vs.Clear(); - DataStream ss{}; - ss << stakeRp; + DataStream ss{}; + ss << stakeRp; - ret.out.scriptPubKey = CScript() << OP_STAKED_COMMITMENT << blsct::Common::DataStreamToVector(ss) << OP_DROP << OP_TRUE; + ret.out.scriptPubKey = CScript() << OP_STAKED_COMMITMENT << blsct::Common::DataStreamToVector(ss) << OP_DROP << OP_TRUE; + } + auto p = rp.Prove(vs, nonce, memo, tokenId); + ret.out.blsctData.rangeProof = p; + ret.GenerateKeys(blindingKey, destKeys); + HashWriter hash{}; + hash << nonce; + ret.out.blsctData.viewTag = (hash.GetHash().GetUint64(0) & 0xFFFF); + } else { + ret.out.scriptPubKey = CScript(OP_RETURN); } - auto p = rp.Prove(vs, nonce, memo, tokenId); - ret.out.blsctData.rangeProof = p; - - HashWriter hash{}; - hash << nonce; - - ret.GenerateKeys(blindingKey, destKeys); - ret.out.blsctData.viewTag = (hash.GetHash().GetUint64(0) & 0xFFFF); - return ret; } @@ -100,6 +142,7 @@ CTransactionRef AggregateTransactions(const std::vector& txs) auto ret = CMutableTransaction(); std::vector vSigs; CAmount nFee = 0; + std::vector feePublicKeys; for (auto& tx : txs) { vSigs.push_back(tx->txSig); @@ -108,14 +151,23 @@ CTransactionRef AggregateTransactions(const std::vector& txs) } for (auto& out : tx->vout) { if (out.scriptPubKey.IsFee()) { - nFee += out.nValue; - continue; + if (out.predicate.size() > 0) { + auto parsedPredicate = blsct::ParsePredicate(out.predicate); + if (parsedPredicate.IsPayFeePredicate()) { + feePublicKeys.push_back(parsedPredicate.GetPublicKey()); + nFee += out.nValue; + continue; + } + } } ret.vout.push_back(out); } } - ret.vout.emplace_back(nFee, CScript{OP_RETURN}); + CTxOut feeOut(nFee, CScript{OP_RETURN}); + feeOut.predicate = blsct::PayFeePredicate(blsct::PublicKeys(feePublicKeys).Aggregate()).GetVch(); + + ret.vout.emplace_back(feeOut); ret.txSig = blsct::Signature::Aggregate(vSigs); ret.nVersion = CTransaction::BLSCT_MARKER; diff --git a/src/blsct/wallet/txfactory_global.h b/src/blsct/wallet/txfactory_global.h index 052269373cf74..9add20e7035a8 100644 --- a/src/blsct/wallet/txfactory_global.h +++ b/src/blsct/wallet/txfactory_global.h @@ -6,7 +6,9 @@ #define TXFACTORY_GLOBAL_H #include +#include #include +#include #include using T = Mcl; @@ -18,13 +20,24 @@ using Scalars = Elements; #define BLSCT_DEFAULT_FEE 125 namespace blsct { +enum CreateTransactionType { + NORMAL, + STAKED_COMMITMENT, + STAKED_COMMITMENT_UNSTAKE, + TX_CREATE_TOKEN, + TX_MINT_TOKEN +}; + struct UnsignedOutput { CTxOut out; Scalar blindingKey; Scalar value; Scalar gamma; + Scalar tokenKey; + CreateTransactionType type; - void GenerateKeys(Scalar blindingKey, DoublePublicKey destKeys); + void + GenerateKeys(Scalar blindingKey, DoublePublicKey destKeys); Signature GetSignature() const; @@ -60,14 +73,12 @@ struct Amounts { CAmount nFromOutputs; }; -enum CreateTransactionType { - NORMAL, - STAKED_COMMITMENT, - STAKED_COMMITMENT_UNSTAKE -}; - CTransactionRef AggregateTransactions(const std::vector& txs); +UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); +UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey); +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const Scalar& blindingKey = Scalar::Rand(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0); int32_t GetTransactionWeight(const CTransaction& tx); int32_t GetTransactioOutputWeight(const CTxOut& out); diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp index 9d3af7e1c6f5e..99525b4bc1260 100644 --- a/src/blsct/wallet/verification.cpp +++ b/src/blsct/wallet/verification.cpp @@ -12,7 +12,7 @@ #include namespace blsct { -bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationState& state, const CAmount& blockReward, const CAmount& minStake) +bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& state, const CAmount& blockReward, const CAmount& minStake) { if (!view.HaveInputs(tx)) { return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-inputs-unknown"); @@ -41,6 +41,7 @@ bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationS vPubKeys.emplace_back(coin.out.blsctData.spendingKey); auto in_hash = in.GetHash(); vMessages.emplace_back(in_hash.begin(), in_hash.end()); + balanceKey = balanceKey + coin.out.blsctData.rangeProof.Vs[0]; } } @@ -49,9 +50,35 @@ bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationS bulletproofs_plus::RangeProofWithSeed stakedCommitmentRangeProof; for (auto& out : tx.vout) { + auto out_hash = out.GetHash(); + + if (out.predicate.size() > 0) { + auto parsedPredicate = ParsePredicate(out.predicate); + + if (parsedPredicate.IsMintTokenPredicate()) { + vPubKeys.emplace_back(parsedPredicate.GetPublicKey()); + vMessages.emplace_back(out_hash.begin(), out_hash.end()); + range_proof::Generators gen = gf.GetInstance(TokenId(parsedPredicate.GetPublicKey().GetHash())); + balanceKey = balanceKey + (gen.G * MclScalar(parsedPredicate.GetAmount())); + } else if (parsedPredicate.IsCreateTokenPredicate()) { + vPubKeys.emplace_back(parsedPredicate.GetPublicKey()); + vMessages.emplace_back(out_hash.begin(), out_hash.end()); + } else if (parsedPredicate.IsMintNftPredicate()) { + vPubKeys.emplace_back(parsedPredicate.GetPublicKey()); + vMessages.emplace_back(out_hash.begin(), out_hash.end()); + range_proof::Generators gen = gf.GetInstance(TokenId(parsedPredicate.GetPublicKey().GetHash(), parsedPredicate.GetNftId())); + balanceKey = balanceKey + (gen.G * MclScalar(1)); + } else if (out.scriptPubKey.IsFee() && parsedPredicate.IsPayFeePredicate()) { + vMessages.emplace_back(blsct::Common::BLSCTFEE); + vPubKeys.emplace_back(parsedPredicate.GetPublicKey()); + } + + if (!ExecutePredicate(parsedPredicate, view)) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "failed-to-execute-predicate"); + } + if (out.IsBLSCT()) { bulletproofs_plus::RangeProofWithSeed proof{out.blsctData.rangeProof, out.tokenId}; - auto out_hash = out.GetHash(); vPubKeys.emplace_back(out.blsctData.ephemeralKey); vMessages.emplace_back(out_hash.begin(), out_hash.end()); diff --git a/src/blsct/wallet/verification.h b/src/blsct/wallet/verification.h index 78d8b8ba1c7d8..91d5ecdb1cee6 100644 --- a/src/blsct/wallet/verification.h +++ b/src/blsct/wallet/verification.h @@ -5,11 +5,12 @@ #ifndef BLSCT_VERIFICATION_H #define BLSCT_VERIFICATION_H +#include #include #include #include namespace blsct { -bool VerifyTx(const CTransaction& tx, const CCoinsViewCache& view, TxValidationState& state, const CAmount& blockReward = 0, const CAmount& minStake = 0); +bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& state, const CAmount& blockReward = 0, const CAmount& minStake = 0); } #endif // BLSCT_VERIFICATION_H diff --git a/src/coins.cpp b/src/coins.cpp index 6ff025ff09979..e747bc9961f97 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -11,11 +11,14 @@ #include bool CCoinsView::GetCoin(const COutPoint& outpoint, Coin& coin) const { return false; } +bool CCoinsView::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const { return false; } +bool CCoinsView::GetAllTokens(TokensMap& tokensMap) const { return false; }; uint256 CCoinsView::GetBestBlock() const { return uint256(); } OrderedElements CCoinsView::GetStakedCommitments() const { return OrderedElements(); }; std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } -bool CCoinsView::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase) { return false; } +bool CCoinsView::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase) { return false; } std::unique_ptr CCoinsView::Cursor() const { return nullptr; } +std::unique_ptr CCoinsView::CursorTokens() const { return nullptr; } bool CCoinsView::HaveCoin(const COutPoint& outpoint) const { @@ -23,15 +26,26 @@ bool CCoinsView::HaveCoin(const COutPoint& outpoint) const return GetCoin(outpoint, coin); } +bool CCoinsView::HaveToken(const uint256& tokenId) const +{ + blsct::TokenEntry token; + return GetToken(tokenId, token); +} + CCoinsViewBacked::CCoinsViewBacked(CCoinsView* viewIn) : base(viewIn) {} bool CCoinsViewBacked::GetCoin(const COutPoint& outpoint, Coin& coin) const { return base->GetCoin(outpoint, coin); } bool CCoinsViewBacked::HaveCoin(const COutPoint& outpoint) const { return base->HaveCoin(outpoint); } +bool CCoinsViewBacked::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const { return base->GetToken(tokenId, token); } +bool CCoinsViewBacked::GetAllTokens(TokensMap& tokensMap) const { return base->GetAllTokens(tokensMap); }; +bool CCoinsViewBacked::HaveToken(const uint256& tokenId) const { return base->HaveToken(tokenId); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } OrderedElements CCoinsViewBacked::GetStakedCommitments() const { return base->GetStakedCommitments(); }; std::vector CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } -bool CCoinsViewBacked::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase) { return base->BatchWrite(mapCoins, hashBlock, stakedCommitments, erase); } +bool CCoinsViewBacked::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase) { return base->BatchWrite(mapCoins, hashBlock, stakedCommitments, tokensMap, erase); } std::unique_ptr CCoinsViewBacked::Cursor() const { return base->Cursor(); } +std::unique_ptr CCoinsViewBacked::CursorTokens() const { return base->CursorTokens(); } + size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) : @@ -118,6 +132,60 @@ void CCoinsViewCache::AddCoin(const COutPoint& outpoint, Coin&& coin, bool possi } } +TokensMap::iterator CCoinsViewCache::FetchToken(const uint256& tokenId) const +{ + TokensMap::iterator it = cacheTokens.find(tokenId); + if (it != cacheTokens.end()) { + return it; + } + blsct::TokenEntry tmp; + if (!base->GetToken(tokenId, tmp)) + return cacheTokens.end(); + + TokensMap::iterator ret = cacheTokens.emplace(std::piecewise_construct, std::forward_as_tuple(tokenId), std::forward_as_tuple(std::move(tmp))).first; + return ret; +} + +bool CCoinsViewCache::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const +{ + TokensMap::const_iterator it = FetchToken(tokenId); + if (it != cacheTokens.end()) { + token = it->second.token; + return !it->second.IsErased(); + } + return false; +} + +bool CCoinsViewCache::GetAllTokens(TokensMap& tokensMap) const +{ + if (!base->GetAllTokens(tokensMap)) + return false; + + for (auto& it : cacheTokens) { + if (!it.second.IsErased()) + tokensMap[it.first] = it.second; + }; + + return true; +}; + +void CCoinsViewCache::AddToken(const uint256& tokenId, blsct::TokenEntry&& token) +{ + TokensMap::iterator it; + bool inserted; + std::tie(it, inserted) = cacheTokens.emplace(std::piecewise_construct, std::forward_as_tuple(tokenId), std::tuple<>()); + it->second.token = std::move(token); + it->second.flags |= TokenCacheEntry::WRITE; +} + +void CCoinsViewCache::EraseToken(const uint256& tokenId) +{ + TokensMap::iterator it; + bool inserted; + std::tie(it, inserted) = cacheTokens.emplace(std::piecewise_construct, std::forward_as_tuple(tokenId), std::tuple<>()); + it->second.flags |= TokenCacheEntry::ERASE; +} + void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) { cachedCoinsUsage += coin.DynamicMemoryUsage(); @@ -194,6 +262,18 @@ bool CCoinsViewCache::HaveCoinInCache(const COutPoint& outpoint) const return (it != cacheCoins.end() && !it->second.coin.IsSpent()); } +bool CCoinsViewCache::HaveToken(const uint256& tokenId) const +{ + TokensMap::const_iterator it = FetchToken(tokenId); + return (it != cacheTokens.end() && !it->second.IsErased()); +} + +bool CCoinsViewCache::HaveTokenInCache(const uint256& tokenId) const +{ + TokensMap::const_iterator it = cacheTokens.find(tokenId); + return (it != cacheTokens.end() && !it->second.IsErased()); +} + uint256 CCoinsViewCache::GetBestBlock() const { if (hashBlock.IsNull()) @@ -222,7 +302,7 @@ void CCoinsViewCache::SetBestBlock(const uint256& hashBlockIn) hashBlock = hashBlockIn; } -bool CCoinsViewCache::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlockIn, CStakedCommitmentsMap& cacheStakedCommitmentsIn, bool erase) +bool CCoinsViewCache::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlockIn, CStakedCommitmentsMap& cacheStakedCommitmentsIn, TokensMap& cacheTokensIn, bool erase) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); @@ -297,14 +377,19 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlockIn }; if (erase) cacheStakedCommitmentsIn.clear(); + for (auto& it : cacheTokensIn) { + cacheTokens[it.first] = it.second; + }; + if (erase) + cacheTokensIn.clear(); return true; } bool CCoinsViewCache::Flush() { - bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheStakedCommitments, /*erase=*/true); + bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheStakedCommitments, cacheTokens, /*erase=*/true); if (fOk) { - if (!cacheCoins.empty() || cacheStakedCommitments.size() != 0) { + if (!cacheCoins.empty() || cacheStakedCommitments.size() != 0 || cacheTokens.size() != 0) { /* BatchWrite must erase all cacheCoins elements when erase=true. */ throw std::logic_error("Not all cached coins were erased"); } @@ -316,7 +401,7 @@ bool CCoinsViewCache::Flush() { bool CCoinsViewCache::Sync() { - bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheStakedCommitments, /*erase=*/false); + bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheStakedCommitments, cacheTokens, /*erase=*/false); // Instead of clearing `cacheCoins` as we would in Flush(), just clear the // FRESH/DIRTY flags of any coin that isn't spent. for (auto it = cacheCoins.begin(); it != cacheCoins.end(); ) { @@ -423,10 +508,27 @@ static bool ExecuteBackedWrapper(Func func, const std::vector #include #include #include @@ -160,6 +161,43 @@ using CCoinsMap = std::unordered_map; + +/** Cursor for iterating over CoinsView tokens state */ +class CTokensViewCursor +{ +public: + CTokensViewCursor() {} + virtual ~CTokensViewCursor() {} + + virtual bool GetKey(uint256& key) const = 0; + virtual bool GetValue(blsct::TokenEntry& coin) const = 0; + + virtual bool Valid() const = 0; + virtual void Next() = 0; +}; + using CStakedCommitmentsMap = std::map; /** Cursor for iterating over CoinsView state */ @@ -195,6 +233,10 @@ class CCoinsView //! Just check whether a given outpoint is unspent. virtual bool HaveCoin(const COutPoint& outpoint) const; + virtual bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const; + virtual bool GetAllTokens(TokensMap& tokensMap) const; + virtual bool HaveToken(const uint256& tokenId) const; + //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; @@ -209,10 +251,11 @@ class CCoinsView //! Do a bulk modification (multiple Coin changes + BestBlock change). //! The passed mapCoins can be modified. - virtual bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase = true); + virtual bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase = true); //! Get a cursor to iterate over the whole state virtual std::unique_ptr Cursor() const; + virtual std::unique_ptr CursorTokens() const; //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} @@ -232,12 +275,17 @@ class CCoinsViewBacked : public CCoinsView CCoinsViewBacked(CCoinsView* viewIn); bool GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint& outpoint) const override; + bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const override; + bool GetAllTokens(TokensMap& tokensMap) const override; + bool HaveToken(const uint256& tokenId) const override; uint256 GetBestBlock() const override; OrderedElements GetStakedCommitments() const override; std::vector GetHeadBlocks() const override; void SetBackend(CCoinsView &viewIn); - bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase = true) override; + bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase = true) override; std::unique_ptr Cursor() const override; + std::unique_ptr CursorTokens() const override; + size_t EstimateSize() const override; }; @@ -257,6 +305,7 @@ class CCoinsViewCache : public CCoinsViewBacked mutable CCoinsMapMemoryResource m_cache_coins_memory_resource{}; mutable CCoinsMap cacheCoins; mutable CStakedCommitmentsMap cacheStakedCommitments; + mutable TokensMap cacheTokens; /* Cached dynamic memory usage for the inner Coin objects. */ mutable size_t cachedCoinsUsage{0}; @@ -273,12 +322,19 @@ class CCoinsViewCache : public CCoinsViewBacked bool GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint& outpoint) const override; uint256 GetBestBlock() const override; + bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const override; + bool HaveToken(const uint256& tokenId) const override; OrderedElements GetStakedCommitments() const override; void SetBestBlock(const uint256 &hashBlock); - bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase = true) override; + bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase = true) override; std::unique_ptr Cursor() const override { throw std::logic_error("CCoinsViewCache cursor iteration not supported."); } + std::unique_ptr CursorTokens() const override + { + throw std::logic_error("CCoinsViewCache token cursor iteration not supported."); + } + bool GetAllTokens(TokensMap& tokensMap) const override; void RemoveStakedCommitment(const MclG1Point& commitment); @@ -288,6 +344,8 @@ class CCoinsViewCache : public CCoinsViewBacked * the backing CCoinsView are made. */ bool HaveCoinInCache(const COutPoint& outpoint) const; + bool HaveTokenInCache(const uint256& tokenId) const; + /** * Return a reference to Coin in the cache, or coinEmpty if not found. This is @@ -301,11 +359,14 @@ class CCoinsViewCache : public CCoinsViewBacked */ const Coin& AccessCoin(const COutPoint& output) const; + /** * Add a coin. Set possible_overwrite to true if an unspent version may * already exist in the cache. */ void AddCoin(const COutPoint& outpoint, Coin&& coin, bool possible_overwrite); + void AddToken(const uint256& tokenId, blsct::TokenEntry&& token); + void EraseToken(const uint256& tokenId); /** * Emplace a coin into cacheCoins without performing any checks, marking @@ -371,6 +432,7 @@ class CCoinsViewCache : public CCoinsViewBacked * memory usage. */ CCoinsMap::iterator FetchCoin(const COutPoint& outpoint) const; + TokensMap::iterator FetchToken(const uint256& tokenId) const; }; //! Utility function to add all of a transaction's outputs to a cache. @@ -404,8 +466,11 @@ class CCoinsViewErrorCatcher final : public CCoinsViewBacked m_err_callbacks.emplace_back(std::move(f)); } - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + bool GetCoin(const COutPoint& outpoint, Coin& coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; + bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const override; + bool GetAllTokens(TokensMap& tokensMap) const override; + bool HaveToken(const uint256& tokenId) const override; private: /** A list of callbacks to execute upon leveldb read error. */ diff --git a/src/ctokens/tokenid.h b/src/ctokens/tokenid.h index 86efa6c5d547e..1730534791509 100644 --- a/src/ctokens/tokenid.h +++ b/src/ctokens/tokenid.h @@ -24,6 +24,11 @@ class TokenId subid = std::numeric_limits::max(); } + bool IsNFT() const + { + return token != uint256() && subid != std::numeric_limits::max(); + }; + bool IsNull() const { return token == uint256() && subid == std::numeric_limits::max(); } std::string ToString() const { return strprintf("%s%s", token.ToString(), subid == std::numeric_limits::max() ? "" : strprintf("#%d", subid)); } diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 956029434c55d..a5baffc9869ba 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -114,7 +114,6 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t // transaction entering the mempool. promise.get_future().wait(); } - if (relay) { node.peerman->RelayTransaction(txid, wtxid); } diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index b83247d966f92..c287b2fadfec2 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -128,7 +128,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat } } - unsigned int nDataOut = 0; TxoutType whichType; for (const CTxOut& txout : tx.vout) { if (txout.IsBLSCT()) @@ -139,9 +138,10 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat return false; } - if (whichType == TxoutType::NULL_DATA) - nDataOut++; - else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) { + // if (whichType == TxoutType::NULL_DATA) + // nDataOut++; + //else + if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) { reason = "bare-multisig"; return false; } else if (IsDust(txout, dust_relay_fee)) { @@ -150,12 +150,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat } } - // only one OP_RETURN txout is permitted - if (nDataOut > 1) { - reason = "multi-op-return"; - return false; - } - return true; } diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 5fda4995d1dbe..8a951e583be97 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -66,10 +66,10 @@ CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, TokenId tokenIdI std::string CTxOut::ToString() const { - return strprintf("CTxOut(scriptPubKey=%s%s%s%s)", HexStr(scriptPubKey).substr(0, 30), + return strprintf("CTxOut(scriptPubKey=%s%s%s%s%s)", HexStr(scriptPubKey).substr(0, 30), IsBLSCT() ? strprintf(", spendingKey=%s, blindingKey=%s, ephemeralKey=%s", HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch())) : "", tokenId.IsNull() ? "" : strprintf(", tokenId=%s", tokenId.ToString()), - IsBLSCT() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue))); + IsBLSCT() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue)), predicate.size() > 0 ? strprintf(", predicate=%s", HexStr(predicate)) : ""); } uint256 CTxOut::GetHash() const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 5ab023b507db4..ec7471ff42fec 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -221,11 +222,13 @@ class CTxOut public: static const uint32_t BLSCT_MARKER = 0x1 << 0; static const uint32_t TOKEN_MARKER = 0x1 << 1; + static const uint32_t PREDICATE_MARKER = 0x1 << 2; CAmount nValue; CScript scriptPubKey; CTxOutBLSCTData blsctData; TokenId tokenId; + blsct::VectorPredicate predicate; CTxOut() { @@ -243,6 +246,8 @@ class CTxOut nFlags |= BLSCT_MARKER; if (!tokenId.IsNull()) nFlags |= TOKEN_MARKER; + if (predicate.size() > 0) + nFlags |= PREDICATE_MARKER; if (nFlags != 0) { ::Serialize(s, std::numeric_limits::max()); ::Serialize(s, nFlags); @@ -255,6 +260,8 @@ class CTxOut } if (nFlags & TOKEN_MARKER) ::Serialize(s, tokenId); + if (nFlags & PREDICATE_MARKER) + ::Serialize(s, predicate); } template @@ -273,6 +280,9 @@ class CTxOut if (nFlags & TOKEN_MARKER) { ::Unserialize(s, tokenId); } + if (nFlags & PREDICATE_MARKER) { + ::Unserialize(s, predicate); + } } void SetNull() diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index deb9d5113c0d6..8b489ff0e299b 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -316,6 +316,11 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmsgtopeer", 0, "peer_id" }, { "stop", 0, "wait" }, { "addnode", 2, "v2transport" }, + // BLSCT + { "createtoken", 0, "metadata"}, + { "createtoken", 1, "max_supply"}, + { "createnft", 0, "metadata"}, + { "createnft", 1, "max_supply"}, }; // clang-format on diff --git a/src/rpc/register.h b/src/rpc/register.h index c88f49ecf0458..54d9b2780d7ed 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -20,6 +20,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC); void RegisterSignMessageRPCCommands(CRPCTable&); void RegisterSignerRPCCommands(CRPCTable &tableRPC); void RegisterTxoutProofRPCCommands(CRPCTable&); +void RegisterTokenRPCCommands(CRPCTable&); static inline void RegisterAllCoreRPCCommands(CRPCTable &t) { @@ -36,6 +37,7 @@ static inline void RegisterAllCoreRPCCommands(CRPCTable &t) RegisterSignerRPCCommands(t); #endif // ENABLE_EXTERNAL_SIGNER RegisterTxoutProofRPCCommands(t); + RegisterTokenRPCCommands(t); } #endif // BITCOIN_RPC_REGISTER_H diff --git a/src/test/blsct/wallet/chain_tests.cpp b/src/test/blsct/wallet/chain_tests.cpp index d515e541c2379..7b9db4b41ff62 100644 --- a/src/test/blsct/wallet/chain_tests.cpp +++ b/src/test/blsct/wallet/chain_tests.cpp @@ -44,7 +44,7 @@ BOOST_FIXTURE_TEST_CASE(SyncTest, TestBLSCTChain100Setup) BOOST_CHECK(coins.size() == 1); // Create Transaction sending to another address - auto tx = blsct::TxFactory::CreateTransaction(wallet.get(), wallet->GetOrCreateBLSCTKeyMan(), blsct::SubAddress(), 1 * COIN, "test"); + auto tx = blsct::TxFactory::CreateTransaction(wallet.get(), wallet->GetOrCreateBLSCTKeyMan(), blsct::CreateTransactionData{blsct::SubAddress(), 1 * COIN, "test"}); BOOST_CHECK(tx != std::nullopt); diff --git a/src/test/blsct/wallet/txfactory_tests.cpp b/src/test/blsct/wallet/txfactory_tests.cpp index db26679f11129..a63d73a0ed90d 100644 --- a/src/test/blsct/wallet/txfactory_tests.cpp +++ b/src/test/blsct/wallet/txfactory_tests.cpp @@ -88,7 +88,7 @@ BOOST_FIXTURE_TEST_CASE(createtransaction_test, TestingSetup) bool fFoundChange = false; // Wallet does not have the coins available yet - BOOST_CHECK(blsct::TxFactory::CreateTransaction(wallet, wallet->GetOrCreateBLSCTKeyMan(), recvAddress, 900 * COIN, "test") == std::nullopt); + BOOST_CHECK(blsct::TxFactory::CreateTransaction(wallet, wallet->GetOrCreateBLSCTKeyMan(), blsct::CreateTransactionData{recvAddress, 900 * COIN, "test"}) == std::nullopt); auto result = blsct_km->RecoverOutputs(finalTx.value().vout); @@ -101,7 +101,7 @@ BOOST_FIXTURE_TEST_CASE(createtransaction_test, TestingSetup) wallet->transactionAddedToMempool(MakeTransactionRef(finalTx.value())); // Wallet does not have the coins available yet (not confirmed in block) - BOOST_CHECK(blsct::TxFactory::CreateTransaction(wallet, wallet->GetOrCreateBLSCTKeyMan(), recvAddress, 900 * COIN, "test") == std::nullopt); + BOOST_CHECK(blsct::TxFactory::CreateTransaction(wallet, wallet->GetOrCreateBLSCTKeyMan(), blsct::CreateTransactionData{recvAddress, 900 * COIN, "test"}) == std::nullopt); } BOOST_FIXTURE_TEST_CASE(addinput_test, TestingSetup) diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 65a08c0e1b52b..0cc3a123197a7 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -56,7 +56,7 @@ class CCoinsViewTest : public CCoinsView uint256 GetBestBlock() const override { return hashBestBlock_; } - bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase = true) override + bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase = true) override { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); it = erase ? mapCoins.erase(it) : std::next(it)) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { @@ -630,7 +630,8 @@ void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags) CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource}; InsertCoinsMapEntry(map, value, flags); CStakedCommitmentsMap stakedCommitments; - BOOST_CHECK(view.BatchWrite(map, {}, stakedCommitments)); + TokensMap tokensMap; + BOOST_CHECK(view.BatchWrite(map, {}, stakedCommitments, tokensMap)); } class SingleEntryCacheTest diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index ddc16b9846ac8..c592b65b90031 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -143,7 +143,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) bool expected_code_path = false; try { CStakedCommitmentsMap stakedCommitmentsMap; - coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock(), stakedCommitmentsMap); + TokensMap tokensMap; + coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock(), stakedCommitmentsMap, tokensMap); expected_code_path = true; } catch (const std::logic_error& e) { if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) { diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index 131f839778636..7a20749034654 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -170,9 +170,10 @@ class CoinsViewBottom final : public CCoinsView uint256 GetBestBlock() const final { return {}; } std::vector GetHeadBlocks() const final { return {}; } std::unique_ptr Cursor() const final { return {}; } + std::unique_ptr CursorTokens() const final { return {}; } size_t EstimateSize() const final { return m_data.size(); } - bool BatchWrite(CCoinsMap& data, const uint256&, CStakedCommitmentsMap&, bool erase) final + bool BatchWrite(CCoinsMap& data, const uint256&, CStakedCommitmentsMap&, TokensMap&, bool erase) final { for (auto it = data.begin(); it != data.end(); it = erase ? data.erase(it) : std::next(it)) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 6fe475390f5b1..44488b596506f 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -840,22 +840,6 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].scriptPubKey = CScript() << OP_RETURN; CheckIsStandard(t); - // Only one TxoutType::NULL_DATA permitted in all cases - t.vout.resize(2); - t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); - t.vout[0].nValue = 0; - t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); - t.vout[1].nValue = 0; - CheckIsNotStandard(t, "multi-op-return"); - - t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); - t.vout[1].scriptPubKey = CScript() << OP_RETURN; - CheckIsNotStandard(t, "multi-op-return"); - - t.vout[0].scriptPubKey = CScript() << OP_RETURN; - t.vout[1].scriptPubKey = CScript() << OP_RETURN; - CheckIsNotStandard(t, "multi-op-return"); - // Check large scriptSig (non-standard if size is >1650 bytes) t.vout.resize(1); t.vout[0].nValue = MAX_MONEY; diff --git a/src/txdb.cpp b/src/txdb.cpp index 99c43514a1050..3319de47b02bf 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -23,6 +23,7 @@ static constexpr uint8_t DB_COIN{'C'}; static constexpr uint8_t DB_BEST_BLOCK{'B'}; static constexpr uint8_t DB_HEAD_BLOCKS{'H'}; static constexpr uint8_t DB_STAKED_OUTPUTS{'S'}; +static constexpr uint8_t DB_TOKEN{'T'}; // Keys used in previous version that might still be found in the DB: static constexpr uint8_t DB_COINS{'c'}; @@ -46,6 +47,13 @@ struct CoinEntry { SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); } }; +struct TokenDbEntry { + uint256* tokenId; + uint8_t key; + explicit TokenDbEntry(const uint256* ptr) : tokenId(const_cast(ptr)), key(DB_TOKEN) {} + + SERIALIZE_METHODS(TokenDbEntry, obj) { READWRITE(obj.key, *(obj.tokenId)); } +}; } // namespace CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) : m_db_params{std::move(db_params)}, @@ -76,6 +84,35 @@ bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const return m_db->Exists(CoinEntry(&outpoint)); } +bool CCoinsViewDB::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const +{ + return m_db->Read(TokenDbEntry(&tokenId), token); +}; + +bool CCoinsViewDB::GetAllTokens(TokensMap& tokensMap) const +{ + std::unique_ptr pcursor(CursorTokens()); + assert(pcursor); + + while (pcursor->Valid()) { + uint256 key; + blsct::TokenEntry token; + if (pcursor->GetKey(key) && pcursor->GetValue(token)) { + TokenCacheEntry cacheEntry; + cacheEntry.token = token; + tokensMap[key] = cacheEntry; + } + pcursor->Next(); + } + + return true; +}; + +bool CCoinsViewDB::HaveToken(const uint256& tokenId) const +{ + return m_db->Exists(TokenDbEntry(&tokenId)); +}; + uint256 CCoinsViewDB::GetBestBlock() const { uint256 hashBestChain; @@ -101,7 +138,7 @@ std::vector CCoinsViewDB::GetHeadBlocks() const return vhashHeadBlocks; } -bool CCoinsViewDB::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase) +bool CCoinsViewDB::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase) { CDBBatch batch(*m_db); size_t count = 0; @@ -153,6 +190,29 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CSt } } + for (TokensMap::iterator it = tokensMap.begin(); it != tokensMap.end();) { + if (it->second.flags > 0) { + TokenDbEntry entry(&it->first); + if (it->second.IsErased()) + batch.Erase(entry); + else + batch.Write(entry, it->second.token); + } + it = erase ? tokensMap.erase(it) : std::next(it); + if (batch.SizeEstimate() > m_options.batch_write_bytes) { + LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0)); + m_db->WriteBatch(batch); + batch.Clear(); + if (m_options.simulate_crash_ratio) { + static FastRandomContext rng; + if (rng.randrange(m_options.simulate_crash_ratio) == 0) { + LogPrintf("Simulating a crash. Goodbye.\n"); + _Exit(0); + } + } + } + } + // In the last batch, mark the database as consistent with hashBlock again. batch.Erase(DB_HEAD_BLOCKS); batch.Write(DB_BEST_BLOCK, hashBlock); @@ -254,3 +314,75 @@ void CCoinsViewDBCursor::Next() keyTmp.first = entry.key; } } + +/** Specialization of CTokensViewCursor to iterate over tokens of a CCoinsViewDB */ +class CTokensViewDBCursor : public CTokensViewCursor +{ +public: + // Prefer using CCoinsViewDB::Cursor() since we want to perform some + // cache warmup on instantiation. + CTokensViewDBCursor(CDBIterator* pcursorIn) : CTokensViewCursor(), pcursor(pcursorIn) {} + ~CTokensViewDBCursor() = default; + + bool GetKey(uint256& key) const override; + bool GetValue(blsct::TokenEntry& coin) const override; + + bool Valid() const override; + void Next() override; + +private: + std::unique_ptr pcursor; + std::pair keyTmp; + + friend class CCoinsViewDB; +}; + +std::unique_ptr CCoinsViewDB::CursorTokens() const +{ + auto i = std::make_unique( + const_cast(*m_db).NewIterator()); + /* It seems that there are no "const iterators" for LevelDB. Since we + only need read operations on it, use a const-cast to get around + that restriction. */ + i->pcursor->Seek(DB_TOKEN); + // Cache key of first record + if (i->pcursor->Valid()) { + TokenDbEntry entry(&i->keyTmp.second); + i->pcursor->GetKey(entry); + i->keyTmp.first = entry.key; + } else { + i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false + } + return i; +} + +bool CTokensViewDBCursor::GetKey(uint256& key) const +{ + // Return cached key + if (keyTmp.first == DB_TOKEN) { + key = keyTmp.second; + return true; + } + return false; +} + +bool CTokensViewDBCursor::GetValue(blsct::TokenEntry& coin) const +{ + return pcursor->GetValue(coin); +} + +bool CTokensViewDBCursor::Valid() const +{ + return keyTmp.first == DB_TOKEN; +} + +void CTokensViewDBCursor::Next() +{ + pcursor->Next(); + TokenDbEntry entry(&keyTmp.second); + if (!pcursor->Valid() || !pcursor->GetKey(entry)) { + keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false + } else { + keyTmp.first = entry.key; + } +} diff --git a/src/txdb.h b/src/txdb.h index ca06be2f53147..1fc7f7dc8e345 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -59,13 +59,17 @@ class CCoinsViewDB final : public CCoinsView public: explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options); - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - bool HaveCoin(const COutPoint &outpoint) const override; + bool GetCoin(const COutPoint& outpoint, Coin& coin) const override; + bool HaveCoin(const COutPoint& outpoint) const override; + bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const override; + bool GetAllTokens(TokensMap& tokensMap) const override; + bool HaveToken(const uint256& tokenId) const override; uint256 GetBestBlock() const override; OrderedElements GetStakedCommitments() const override; std::vector GetHeadBlocks() const override; - bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, bool erase = true) override; + bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, CStakedCommitmentsMap& stakedCommitments, TokensMap& tokensMap, bool erase = true) override; std::unique_ptr Cursor() const override; + std::unique_ptr CursorTokens() const override; //! Whether an unsupported database format is used. bool NeedsUpgrade(); diff --git a/src/validation.cpp b/src/validation.cpp index c24a4b6fbf0bc..b981bfba18f7c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1251,7 +1251,6 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef Workspace ws(ptx); const std::vector single_wtxid{ws.m_ptx->GetWitnessHash()}; - if (!PreChecks(args, ws)) { if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { // Failed for fee reasons. Provide the effective feerate and which tx was included. @@ -2070,6 +2069,12 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn if (tx.vout[o].IsStakedCommitment()) { view.RemoveStakedCommitment(tx.vout[o].blsctData.rangeProof.Vs[0]); } + if (tx.vout[o].predicate.size() > 0) { + if (!blsct::ExecutePredicate(tx.vout[o].predicate, view, true)) { + error("DisconnectBlock(): Could not revert predicate"); + return DISCONNECT_FAILED; + } + } if (!tx.vout[o].scriptPubKey.IsUnspendable()) { COutPoint out(hash, o); Coin coin; diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 37ae8ce86ddb0..b8a2257db06e6 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -22,11 +23,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -576,11 +577,23 @@ class WalletLoaderImpl : public WalletLoader void registerRpcs() override { for (const CRPCCommand& command : GetWalletRPCCommands()) { - m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) { - JSONRPCRequest wallet_request = request; - wallet_request.context = &m_context; - return command.actor(wallet_request, result, last_handler); - }, command.argNames, command.unique_id); + m_rpc_commands.emplace_back( + command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) { + JSONRPCRequest wallet_request = request; + wallet_request.context = &m_context; + return command.actor(wallet_request, result, last_handler); + }, + command.argNames, command.unique_id); + m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); + } + for (const CRPCCommand& command : GetBLSCTWalletRPCCommands()) { + m_rpc_commands.emplace_back( + command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) { + JSONRPCRequest wallet_request = request; + wallet_request.context = &m_context; + return command.actor(wallet_request, result, last_handler); + }, + command.argNames, command.unique_id); m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); } } diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index d0120bc60ca80..3240b3df04065 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -202,79 +203,6 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl& coin_control, std::vecto return tx->GetHash().GetHex(); } -UniValue SendBLSCTMoney(CWallet& wallet, std::vector& recipients, bool verbose, CAmount minStakeIn = 0) -{ - EnsureWalletIsUnlocked(wallet); - - if (recipients.size() == 0) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Error: No recipients"); - - // This should always try to sign, if we don't have private keys, don't try to do anything here. - if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - - // Shuffle recipient list - std::shuffle(recipients.begin(), recipients.end(), FastRandomContext()); - - // Send - auto outputType = recipients[0].fStakeCommitment ? blsct::CreateTransactionType::STAKED_COMMITMENT : blsct::CreateTransactionType::NORMAL; - auto minStake = recipients[0].fStakeCommitment ? minStakeIn : 0; - - auto res = blsct::TxFactory::CreateTransaction(&wallet, wallet.GetBLSCTKeyMan(), recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), outputType, minStake); - - if (!res) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Not enough funds available"); - } - - const CTransactionRef& tx = MakeTransactionRef(res.value()); - mapValue_t map_value; - wallet.CommitTransaction(tx, std::move(map_value), /*orderForm=*/{}); - if (verbose) { - UniValue entry(UniValue::VOBJ); - entry.pushKV("txid", tx->GetHash().GetHex()); - return entry; - } - return tx->GetHash().GetHex(); -} - - -UniValue UnstakeBLSCT(CWallet& wallet, std::vector& recipients, bool verbose, CAmount minStakeIn = 0) -{ - EnsureWalletIsUnlocked(wallet); - - if (recipients.size() == 0) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Error: No recipients"); - - // This should always try to sign, if we don't have private keys, don't try to do anything here. - if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - - // Shuffle recipient list - std::shuffle(recipients.begin(), recipients.end(), FastRandomContext()); - - // Send - auto outputType = blsct::CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; - auto minStake = minStakeIn; - - auto res = blsct::TxFactory::CreateTransaction(&wallet, wallet.GetBLSCTKeyMan(), recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), outputType, minStake); - - if (!res) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Not enough funds available"); - } - - const CTransactionRef& tx = MakeTransactionRef(res.value()); - mapValue_t map_value; - wallet.CommitTransaction(tx, std::move(map_value), /*orderForm=*/{}); - if (verbose) { - UniValue entry(UniValue::VOBJ); - entry.pushKV("txid", tx->GetHash().GetHex()); - return entry; - } - return tx->GetHash().GetHex(); -} - /** * Update coin control with fee estimation based on the given parameters * @@ -365,7 +293,11 @@ RPCHelpMan sendtoblsctaddress() ParseBLSCTRecipients(address_amounts, false, sMemo, recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - return SendBLSCTMoney(*pwallet, recipients, verbose); + blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::NORMAL, 0); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); }, }; } @@ -417,9 +349,11 @@ RPCHelpMan stakelock() ParseBLSCTRecipients(address_amounts, false, "", recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - recipients[0].fStakeCommitment = true; + blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT, Params().GetConsensus().nPePoSMinStakeAmount); + + EnsureWalletIsUnlocked(*pwallet); - return SendBLSCTMoney(*pwallet, recipients, verbose, Params().GetConsensus().nPePoSMinStakeAmount); + return blsct::SendTransaction(*pwallet, transactionData, verbose); }, }; } @@ -471,7 +405,12 @@ RPCHelpMan stakeunlock() ParseBLSCTRecipients(address_amounts, false, "", recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - return UnstakeBLSCT(*pwallet, recipients, verbose, Params().GetConsensus().nPePoSMinStakeAmount); + + blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT_UNSTAKE, Params().GetConsensus().nPePoSMinStakeAmount); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); }, }; } diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index b03133cb5da0a..227d887795572 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -184,32 +184,27 @@ static RPCHelpMan listwalletdir() static RPCHelpMan listwallets() { - return RPCHelpMan{"listwallets", - "Returns a list of currently loaded wallets.\n" - "For full information on the wallet, use \"getwalletinfo\"\n", - {}, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR, "walletname", "the wallet name"}, - } - }, - RPCExamples{ - HelpExampleCli("listwallets", "") - + HelpExampleRpc("listwallets", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - UniValue obj(UniValue::VARR); + return RPCHelpMan{ + "listwallets", + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n", + {}, + RPCResult{ + RPCResult::Type::ARR, "", "", { + {RPCResult::Type::STR, "walletname", "the wallet name"}, + }}, + RPCExamples{HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + UniValue obj(UniValue::VARR); - WalletContext& context = EnsureWalletContext(request.context); - for (const std::shared_ptr& wallet : GetWallets(context)) { - LOCK(wallet->cs_wallet); - obj.push_back(wallet->GetName()); - } + WalletContext& context = EnsureWalletContext(request.context); + for (const std::shared_ptr& wallet : GetWallets(context)) { + LOCK(wallet->cs_wallet); + obj.push_back(wallet->GetName()); + } - return obj; -}, + return obj; + }, }; } From 5480223c93d2c0ba7967a460c97ef8d4440550a6 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 10 Nov 2024 22:49:56 +0100 Subject: [PATCH 08/27] mint token and nft rpc --- src/blsct/tokens/rpc.cpp | 9 +- src/blsct/wallet/rpc.cpp | 188 ++++++++++++++++++++++++++++++++++++++- src/rpc/client.cpp | 3 + 3 files changed, 193 insertions(+), 7 deletions(-) diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp index 519b38260a87b..3842e5c241132 100644 --- a/src/blsct/tokens/rpc.cpp +++ b/src/blsct/tokens/rpc.cpp @@ -19,7 +19,8 @@ std::vector tokenInfoResult = { RPCResult{RPCResult::Type::NUM, "maxSupply", "the token max supply"}, }; -void TokenToUniValue(const blsct::TokenEntry& token, UniValue& obj) { +void TokenToUniValue(UniValue& obj, const blsct::TokenEntry& token) +{ obj.pushKV("publicKey", token.info.publicKey.ToString()); obj.pushKV("type", blsct::TokenTypeToString(token.info.type)); UniValue metadata{UniValue::VOBJ}; @@ -47,9 +48,8 @@ gettoken() RPCResult{RPCResult::Type::OBJ, "", "", tokenInfoResult}, RPCExamples{HelpExampleCli("gettoken", "ba12afc43322f204fe6236b11a0f85b5d9edcb09f446176c73fe4abe99a17edd")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - ChainstateManager& chainman = EnsureAnyChainman(request.context); - LOCK(cs_main); + ChainstateManager& chainman = EnsureAnyChainman(request.context); Chainstate& active_chainstate = chainman.ActiveChainstate(); CCoinsViewCache* coins_view; @@ -80,9 +80,8 @@ listtokens() }}, RPCExamples{HelpExampleCli("listtokens", "")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - ChainstateManager& chainman = EnsureAnyChainman(request.context); - LOCK(cs_main); + ChainstateManager& chainman = EnsureAnyChainman(request.context); Chainstate& active_chainstate = chainman.ActiveChainstate(); CCoinsViewCache* coins_view; diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp index 5a862dc6fae3b..83dc1aea0c60f 100644 --- a/src/blsct/wallet/rpc.cpp +++ b/src/blsct/wallet/rpc.cpp @@ -71,7 +71,22 @@ UniValue CreateTokenOrNft(const RPCHelpMan& self, const JSONRPCRequest& request, tokenInfo.nTotalSupply = max_supply; tokenInfo.mapMetadata = mapMetadata; tokenInfo.type = type; - tokenInfo.publicKey = blsct_km->GetTokenKey((HashWriter{} << tokenInfo.mapMetadata << tokenInfo.nTotalSupply).GetHash()).GetPublicKey(); + + auto tokenId = (HashWriter{} << tokenInfo.mapMetadata << tokenInfo.nTotalSupply).GetHash(); + + { + LOCK(cs_main); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + Chainstate& active_chainstate = chainman.ActiveChainstate(); + + CCoinsViewCache* coins_view; + coins_view = &active_chainstate.CoinsTip(); + + if (coins_view->HaveToken(tokenId)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Token already exists"); + } + + tokenInfo.publicKey = blsct_km->GetTokenKey(tokenId).GetPublicKey(); blsct::CreateTransactionData transactionData(tokenInfo); @@ -128,18 +143,187 @@ RPCHelpMan createtoken() {"max_supply", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The token max supply."}}, RPCResult{ RPCResult::Type::STR_HEX, "tokenId", "the token id"}, - RPCExamples{HelpExampleRpc("createtoken", "{'name':'Token'} 1000")}, + RPCExamples{HelpExampleRpc("createtoken", "{\"name\":\"Token\"} 1000")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { return CreateTokenOrNft(self, request, blsct::TOKEN); }, }; } +RPCHelpMan minttoken() +{ + return RPCHelpMan{ + "minttoken", + "Mints a certain amount of tokens to an address.\n", + {{ + "token_id", + RPCArg::Type::STR_HEX, + RPCArg::Optional::NO, + "The token id.", + }, + { + "address", + RPCArg::Type::STR, + RPCArg::Optional::NO, + "The address where the tokens will be minted.", + }, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The token amount to be minted."}}, + RPCResult{ + RPCResult::Type::STR_HEX, "hash", "The transaction hash"}, + RPCExamples{HelpExampleRpc("minttoken", "d46a375d31843d6a303dc7a8c0e0cccaa2d89f442052226fd5337b4d77afcc80 " + BLSCT_EXAMPLE_ADDRESS[0] + " 1000")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + auto blsct_km = pwallet->GetOrCreateBLSCTKeyMan(); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + const std::string address = request.params[1].get_str(); + CAmount mint_amount = AmountFromValue(request.params[2]); + blsct::TokenEntry token; + + { + LOCK(cs_main); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + Chainstate& active_chainstate = chainman.ActiveChainstate(); + CCoinsViewCache* coins_view; + coins_view = &active_chainstate.CoinsTip(); + + if (!coins_view->GetToken(token_id, token)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto publicKey = blsct_km->GetTokenKey(token_id).GetPublicKey(); + + if (publicKey != token.info.publicKey) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); + } + + blsct::CreateTransactionData + transactionData(token.info, mint_amount, address); + + EnsureWalletIsUnlocked(*pwallet); + + auto hash = blsct::SendTransaction(*pwallet, transactionData, false); + + UniValue ret{UniValue::VOBJ}; + ret.pushKV("hash", hash); + + return ret; + }, + }; +} + +RPCHelpMan mintnft() +{ + return RPCHelpMan{ + "mintnft", + "Mints a NFT to an address.\n", + {{ + "token_id", + RPCArg::Type::STR_HEX, + RPCArg::Optional::NO, + "The token id.", + }, + { + "nft_id", + RPCArg::Type::AMOUNT, + RPCArg::Optional::NO, + "The nft id.", + }, + { + "address", + RPCArg::Type::STR, + RPCArg::Optional::NO, + "The address where the tokens will be minted.", + }, + { + "metadata", + RPCArg::Type::OBJ_USER_KEYS, + RPCArg::Optional::NO, + "The token metadata", + { + {"key", RPCArg::Type::STR, RPCArg::Optional::NO, "value"}, + }, + }}, + RPCResult{ + RPCResult::Type::STR_HEX, "hash", "The transaction hash"}, + RPCExamples{HelpExampleRpc("mintnft", "d46a375d31843d6a303dc7a8c0e0cccaa2d89f442052226fd5337b4d77afcc80 1 " + BLSCT_EXAMPLE_ADDRESS[0] + " {\"desc\":\"Your first NFT\"}")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + auto blsct_km = pwallet->GetOrCreateBLSCTKeyMan(); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + CAmount nft_id = AmountFromValue(request.params[1]); + const std::string address = request.params[2].get_str(); + std::map metadata; + if (!request.params[3].isNull() && !request.params[3].get_obj().empty()) + request.params[3].get_obj().getObjMap(metadata); + + std::map mapMetadata; + + for (auto& it : metadata) { + if (it.second.isNull() || !it.second.isStr() || it.second.get_str().empty()) + continue; + mapMetadata[it.first] = it.second.get_str(); + } + + blsct::TokenEntry token; + + { + LOCK(cs_main); + ChainstateManager& chainman = EnsureAnyChainman(request.context); + Chainstate& active_chainstate = chainman.ActiveChainstate(); + CCoinsViewCache* coins_view; + coins_view = &active_chainstate.CoinsTip(); + + if (!coins_view->GetToken(token_id, token)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto publicKey = blsct_km->GetTokenKey(token_id).GetPublicKey(); + + if (publicKey != token.info.publicKey) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); + + if (token.mapMintedNft.count(nft_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The NFT is already minted"); + } + + blsct::CreateTransactionData + transactionData(token.info, nft_id, address, mapMetadata); + + EnsureWalletIsUnlocked(*pwallet); + + auto hash = blsct::SendTransaction(*pwallet, transactionData, false); + + UniValue ret{UniValue::VOBJ}; + ret.pushKV("hash", hash); + + return ret; + }, + }; +} + Span GetBLSCTWalletRPCCommands() { static const CRPCCommand commands[]{ {"blsct", &createnft}, {"blsct", &createtoken}, + {"blsct", &minttoken}, + {"blsct", &mintnft}, }; return commands; } \ No newline at end of file diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8b489ff0e299b..9e5bc5e4c4d5a 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -321,6 +321,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createtoken", 1, "max_supply"}, { "createnft", 0, "metadata"}, { "createnft", 1, "max_supply"}, + { "minttoken", 2, "amount"}, + { "mintnft", 1, "nft_id"}, + { "mintnft", 3, "metadata"}, }; // clang-format on From 112ec67f046c9a76ee783256a45e2997dbc6818d Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 17 Nov 2024 20:40:26 +0100 Subject: [PATCH 09/27] token and nft mint --- src/Makefile.am | 1 + src/blsct/tokens/info.h | 6 +-- src/blsct/tokens/predicate_parser.cpp | 36 ++++++++++---- src/blsct/tokens/predicate_parser.h | 2 + src/blsct/tokens/rpc.cpp | 17 ++++++- src/blsct/wallet/rpc.cpp | 71 ++++++++++++--------------- src/blsct/wallet/txfactory.cpp | 34 ++++++------- src/blsct/wallet/txfactory_global.cpp | 6 +-- src/blsct/wallet/txfactory_global.h | 1 + src/blsct/wallet/verification.cpp | 6 ++- src/coins.h | 5 ++ src/interfaces/chain.h | 5 ++ src/node/coin.cpp | 18 +++++++ src/node/coin.h | 6 +++ src/node/interfaces.cpp | 1 + src/node/miner.cpp | 18 ++++++- src/primitives/transaction.h | 1 - src/txdb.cpp | 3 +- src/txmempool.cpp | 5 ++ src/txmempool.h | 3 +- src/validation.cpp | 6 ++- src/wallet/walletdb.cpp | 4 +- test/functional/mempool_accept.py | 7 --- 23 files changed, 170 insertions(+), 92 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index d0dff830c370d..81a6fe7a4e3e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -566,6 +566,7 @@ libbitcoin_node_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ blsct/tokens/rpc.cpp \ + blsct/wallet/rpc.cpp \ blsct/wallet/verification.cpp \ blsct/signature.cpp \ chain.cpp \ diff --git a/src/blsct/tokens/info.h b/src/blsct/tokens/info.h index a01c4341ad8f5..6e25a6f657033 100644 --- a/src/blsct/tokens/info.h +++ b/src/blsct/tokens/info.h @@ -40,7 +40,7 @@ class TokenInfo const CAmount& nTotalSupply) : type(type), publicKey(publicKey), mapMetadata(mapMetadata), nTotalSupply(nTotalSupply){}; TokenInfo(){}; - SERIALIZE_METHODS(TokenInfo, obj) { READWRITE(static_cast(obj.type), obj.publicKey, obj.mapMetadata, obj.nTotalSupply); }; + SERIALIZE_METHODS(TokenInfo, obj) { READWRITE(Using>(obj.type), obj.publicKey, obj.mapMetadata, obj.nTotalSupply); }; std::string ToString() const { @@ -68,9 +68,9 @@ class TokenEntry bool Mint(const CAmount& amount) { - if (amount + nSupply > info.nTotalSupply) + if (amount + nSupply > info.nTotalSupply || amount + nSupply < 0) return false; - nSupply += nSupply; + nSupply += amount; return true; }; diff --git a/src/blsct/tokens/predicate_parser.cpp b/src/blsct/tokens/predicate_parser.cpp index 67b7c0cab7504..63beec5a2d129 100644 --- a/src/blsct/tokens/predicate_parser.cpp +++ b/src/blsct/tokens/predicate_parser.cpp @@ -32,12 +32,31 @@ ParsedPredicate ParsePredicate(const VectorPredicate& vch) } } +std::string PredicateToString(const VectorPredicate& vch) +{ + auto predicate = ParsePredicate(vch); + + std::string ret; + + if (predicate.IsCreateTokenPredicate()) + ret = "CREATE_TOKEN"; + else if (predicate.IsMintTokenPredicate()) + ret = "MINT_TOKEN"; + else if (predicate.IsMintNftPredicate()) + ret = "MINT_NFT"; + else if (predicate.IsPayFeePredicate()) + ret = "PAY_FEE"; + + return ret; +} + bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect) { if (predicate.IsCreateTokenPredicate()) { auto hash = predicate.GetPublicKey().GetHash(); - if (!fDisconnect && view.HaveToken(hash)) return false; + blsct::TokenEntry token; + if (view.GetToken(hash, token) == !fDisconnect) return false; if (fDisconnect) view.EraseToken(hash); @@ -48,14 +67,10 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c } else if (predicate.IsMintTokenPredicate()) { auto hash = predicate.GetPublicKey().GetHash(); - if (!view.HaveToken(hash)) - return false; - blsct::TokenEntry token; if (!view.GetToken(hash, token)) return false; - if (!token.Mint(predicate.GetAmount() * (1 - 2 * fDisconnect))) return false; @@ -65,18 +80,21 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c } else if (predicate.IsMintNftPredicate()) { auto hash = predicate.GetPublicKey().GetHash(); - if (!view.HaveToken(hash)) - return false; - blsct::TokenEntry token; if (!view.GetToken(hash, token)) return false; + if (predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) + return false; + if (token.mapMintedNft.contains(predicate.GetNftId()) == !fDisconnect) return false; - token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); + if (fDisconnect) + token.mapMintedNft.erase(predicate.GetNftId()); + else + token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); view.AddToken(hash, std::move(token)); diff --git a/src/blsct/tokens/predicate_parser.h b/src/blsct/tokens/predicate_parser.h index 338baa6915f73..6feeac627aa06 100644 --- a/src/blsct/tokens/predicate_parser.h +++ b/src/blsct/tokens/predicate_parser.h @@ -108,6 +108,7 @@ struct PayFeePredicate { class ParsedPredicate { public: + ParsedPredicate() {} ParsedPredicate(CreateTokenPredicate& predicate) : predicate_(predicate) {} ParsedPredicate(MintTokenPredicate& predicate) : predicate_(predicate) {} ParsedPredicate(MintNftPredicate& predicate) : predicate_(predicate) {} @@ -185,6 +186,7 @@ class ParsedPredicate }; ParsedPredicate ParsePredicate(const VectorPredicate& vch); +std::string PredicateToString(const VectorPredicate& vch); bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect = false); bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect = false); diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp index 3842e5c241132..ea481f8d16e33 100644 --- a/src/blsct/tokens/rpc.cpp +++ b/src/blsct/tokens/rpc.cpp @@ -17,6 +17,8 @@ std::vector tokenInfoResult = { RPCResult{RPCResult::Type::NUM, "type", "the token type"}, RPCResult{RPCResult::Type::ANY, "metadata", "the token metadata"}, RPCResult{RPCResult::Type::NUM, "maxSupply", "the token max supply"}, + RPCResult{RPCResult::Type::NUM, "currentSupply", "the token current supply"}, + }; void TokenToUniValue(UniValue& obj, const blsct::TokenEntry& token) @@ -29,6 +31,19 @@ void TokenToUniValue(UniValue& obj, const blsct::TokenEntry& token) } obj.pushKV("metadata", metadata); obj.pushKV("maxSupply", token.info.nTotalSupply); + if (token.info.type == blsct::TokenType::TOKEN) + obj.pushKV("currentSupply", token.nSupply); + else if (token.info.type == blsct::TokenType::NFT) { + UniValue mintedNft{UniValue::VOBJ}; + for (auto& it : token.mapMintedNft) { + UniValue nftMetadata{UniValue::VOBJ}; + for (auto& it2 : it.second) { + nftMetadata.pushKV(it2.first, it2.second); + } + mintedNft.pushKV(std::to_string(it.first), nftMetadata); + } + obj.pushKV("mintedNft", mintedNft); + } } RPCHelpMan @@ -55,7 +70,7 @@ gettoken() CCoinsViewCache* coins_view; coins_view = &active_chainstate.CoinsTip(); - uint256 tokenId(ParseHashV(request.params[0], "txid")); + uint256 tokenId(ParseHashV(request.params[0], "tokenId")); blsct::TokenEntry token; if (!coins_view->GetToken(tokenId, token)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp index 83dc1aea0c60f..2ae85a77f6e3e 100644 --- a/src/blsct/wallet/rpc.cpp +++ b/src/blsct/wallet/rpc.cpp @@ -65,7 +65,7 @@ UniValue CreateTokenOrNft(const RPCHelpMan& self, const JSONRPCRequest& request, mapMetadata[it.first] = it.second.get_str(); } - CAmount max_supply = AmountFromValue(request.params[1]); + CAmount max_supply = type == blsct::TokenType::TOKEN ? AmountFromValue(request.params[1]) : request.params[1].get_uint64(); blsct::TokenInfo tokenInfo; tokenInfo.nTotalSupply = max_supply; @@ -74,17 +74,14 @@ UniValue CreateTokenOrNft(const RPCHelpMan& self, const JSONRPCRequest& request, auto tokenId = (HashWriter{} << tokenInfo.mapMetadata << tokenInfo.nTotalSupply).GetHash(); - { - LOCK(cs_main); - ChainstateManager& chainman = EnsureAnyChainman(request.context); - Chainstate& active_chainstate = chainman.ActiveChainstate(); + std::map tokens; + tokens[tokenId]; + pwallet->chain().findTokens(tokens); - CCoinsViewCache* coins_view; - coins_view = &active_chainstate.CoinsTip(); + if (tokens.count(tokenId)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Token already exists"); - if (coins_view->HaveToken(tokenId)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Token already exists"); - } + auto token = tokens[tokenId]; tokenInfo.publicKey = blsct_km->GetTokenKey(tokenId).GetPublicKey(); @@ -173,6 +170,7 @@ RPCHelpMan minttoken() RPCExamples{HelpExampleRpc("minttoken", "d46a375d31843d6a303dc7a8c0e0cccaa2d89f442052226fd5337b4d77afcc80 " + BLSCT_EXAMPLE_ADDRESS[0] + " 1000")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; // Make sure the results are valid at least up to the most recent block @@ -186,23 +184,21 @@ RPCHelpMan minttoken() uint256 token_id(ParseHashV(request.params[0], "token_id")); const std::string address = request.params[1].get_str(); CAmount mint_amount = AmountFromValue(request.params[2]); - blsct::TokenEntry token; - { - LOCK(cs_main); - ChainstateManager& chainman = EnsureAnyChainman(request.context); - Chainstate& active_chainstate = chainman.ActiveChainstate(); - CCoinsViewCache* coins_view; - coins_view = &active_chainstate.CoinsTip(); + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); - if (!coins_view->GetToken(token_id, token)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); - auto publicKey = blsct_km->GetTokenKey(token_id).GetPublicKey(); + auto token = tokens[token_id]; - if (publicKey != token.info.publicKey) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); - } + auto tokenId = (HashWriter{} << token.info.mapMetadata << token.info.nTotalSupply).GetHash(); + auto publicKey = blsct_km->GetTokenKey(tokenId).GetPublicKey(); + + if (publicKey != token.info.publicKey) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); blsct::CreateTransactionData transactionData(token.info, mint_amount, address); @@ -219,7 +215,7 @@ RPCHelpMan minttoken() }; } -RPCHelpMan mintnft() +static RPCHelpMan mintnft() { return RPCHelpMan{ "mintnft", @@ -281,26 +277,23 @@ RPCHelpMan mintnft() mapMetadata[it.first] = it.second.get_str(); } - blsct::TokenEntry token; + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); - { - LOCK(cs_main); - ChainstateManager& chainman = EnsureAnyChainman(request.context); - Chainstate& active_chainstate = chainman.ActiveChainstate(); - CCoinsViewCache* coins_view; - coins_view = &active_chainstate.CoinsTip(); + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); - if (!coins_view->GetToken(token_id, token)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + auto token = tokens[token_id]; - auto publicKey = blsct_km->GetTokenKey(token_id).GetPublicKey(); + auto tokenId = (HashWriter{} << token.info.mapMetadata << token.info.nTotalSupply).GetHash(); + auto publicKey = blsct_km->GetTokenKey(tokenId).GetPublicKey(); - if (publicKey != token.info.publicKey) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); + if (publicKey != token.info.publicKey) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "You don't own the token"); - if (token.mapMintedNft.count(nft_id)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The NFT is already minted"); - } + if (token.mapMintedNft.count(nft_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The NFT is already minted"); blsct::CreateTransactionData transactionData(token.info, nft_id, address, mapMetadata); diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp index 925ec9ef1a13c..51832b92879b6 100644 --- a/src/blsct/wallet/txfactory.cpp +++ b/src/blsct/wallet/txfactory.cpp @@ -28,7 +28,7 @@ void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmo }; if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0}; + nAmounts[token_id] = {0, 0, 0}; nAmounts[token_id].nFromOutputs += nAmount - nFee; @@ -93,7 +93,7 @@ bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, cons vInputs[token_id].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey, stakedCommitment}); if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0}; + nAmounts[token_id] = {0, 0, 0}; nAmounts[token_id].nFromInputs += amount; @@ -107,8 +107,7 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA std::vector outputSignatures; Scalar outputGammas; - CAmount nFee = 0; - + nAmounts[TokenId()].nFromFee = 0; for (auto& out_ : vOutputs) { for (auto& out : out_.second) { @@ -139,16 +138,14 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA for (auto& in_ : vInputs) { for (auto& in : in_.second) { if (!in.is_staked_commitment) continue; + if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; + if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs) break; tx.vin.push_back(in.in); gammaAcc = gammaAcc + in.gamma; txSigs.push_back(in.sk.Sign(in.in.GetHash())); - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - mapInputs[in_.first] += in.value.GetUint64(); - - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nFee) break; } } } @@ -156,21 +153,18 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA for (auto& in_ : vInputs) { for (auto& in : in_.second) { if (in.is_staked_commitment) continue; + if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; + if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nAmounts[in_.first].nFromFee) break; tx.vin.push_back(in.in); gammaAcc = gammaAcc + in.gamma; txSigs.push_back(in.sk.Sign(in.in.GetHash())); - - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - mapInputs[in_.first] += in.value.GetUint64(); - - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + in_.first.IsNull() ? nFee : 0) break; } } for (auto& amounts : nAmounts) { - auto tokenFee = (amounts.first == TokenId() ? nFee : 0); + auto tokenFee = nAmounts[amounts.first].nFromFee; auto nFromInputs = mapInputs[amounts.first]; @@ -190,8 +184,8 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA txSigs.push_back(PrivateKey(changeOutput.blindingKey).Sign(changeOutput.out.GetHash())); } - if (nFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { - CTxOut fee_out{nFee, CScript(OP_RETURN)}; + if (nAmounts[TokenId()].nFromFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { + CTxOut fee_out{nAmounts[TokenId()].nFromFee, CScript(OP_RETURN)}; auto feeKey = blsct::PrivateKey(MclScalar::Rand()); fee_out.predicate = blsct::PayFeePredicate(feeKey.GetPublicKey()).GetVch(); @@ -205,7 +199,7 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA return tx; } - nFee = GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE; + nAmounts[TokenId()].nFromFee = GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE; } return std::nullopt; @@ -291,7 +285,7 @@ bool TxFactory::AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint vInputs[coin.out.tokenId].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), recoveredInfo.amounts[0].amount, recoveredInfo.amounts[0].gamma, km->GetSpendingKeyForOutput(coin.out), stakedCommitment}); if (nAmounts.count(coin.out.tokenId) == 0) - nAmounts[coin.out.tokenId] = {0, 0}; + nAmounts[coin.out.tokenId] = {0, 0, 0}; nAmounts[coin.out.tokenId].nFromInputs += recoveredInfo.amounts[0].amount; @@ -317,7 +311,7 @@ bool TxFactory::AddInput(wallet::CWallet* wallet, const COutPoint& outpoint, con vInputs[out.tokenId].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), recoveredInfo.amount, recoveredInfo.gamma, km->GetSpendingKeyForOutput(out), stakedCommitment}); if (nAmounts.count(out.tokenId) == 0) - nAmounts[out.tokenId] = {0, 0}; + nAmounts[out.tokenId] = {0, 0, 0}; nAmounts[out.tokenId].nFromInputs += recoveredInfo.amount; @@ -362,7 +356,7 @@ void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* bl AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); } - if (type == CreateTransactionType::NORMAL && !token_id.IsNull()) { + if ((type == CreateTransactionType::NORMAL && !token_id.IsNull()) || type == CreateTransactionType::TX_MINT_TOKEN) { coins_params.token_id.SetNull(); AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); } diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index 2bcc5a52782c6..e4f6f4a06c70a 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -71,7 +71,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar { TokenId tokenId{tokenPublicKey.GetHash(), nftId}; - auto ret = CreateOutput(destKeys, 0, "", tokenId, blindingKey, TX_MINT_TOKEN); + auto ret = CreateOutput(destKeys, 1, "", tokenId, blindingKey, TX_MINT_TOKEN); if (tokenId.IsNFT()) { ret.out.predicate = MintNftPredicate(tokenPublicKey, nftId, nftMetadata).GetVch(); @@ -103,7 +103,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun throw std::runtime_error(strprintf("%s: could not get view key from destination address\n", __func__)); } - auto nonce = vk * blindingKey; + auto nonce = vk * ret.blindingKey; nonces.Add(nonce); ret.value = nAmount; @@ -126,7 +126,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun } auto p = rp.Prove(vs, nonce, memo, tokenId); ret.out.blsctData.rangeProof = p; - ret.GenerateKeys(blindingKey, destKeys); + ret.GenerateKeys(ret.blindingKey, destKeys); HashWriter hash{}; hash << nonce; ret.out.blsctData.viewTag = (hash.GetHash().GetUint64(0) & 0xFFFF); diff --git a/src/blsct/wallet/txfactory_global.h b/src/blsct/wallet/txfactory_global.h index 9add20e7035a8..9eb9e099f8b45 100644 --- a/src/blsct/wallet/txfactory_global.h +++ b/src/blsct/wallet/txfactory_global.h @@ -71,6 +71,7 @@ struct UnsignedInput { struct Amounts { CAmount nFromInputs; CAmount nFromOutputs; + CAmount nFromFee; }; CTransactionRef diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp index 99525b4bc1260..6925683d82df1 100644 --- a/src/blsct/wallet/verification.cpp +++ b/src/blsct/wallet/verification.cpp @@ -51,9 +51,10 @@ bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& for (auto& out : tx.vout) { auto out_hash = out.GetHash(); + blsct::ParsedPredicate parsedPredicate; if (out.predicate.size() > 0) { - auto parsedPredicate = ParsePredicate(out.predicate); + parsedPredicate = ParsePredicate(out.predicate); if (parsedPredicate.IsMintTokenPredicate()) { vPubKeys.emplace_back(parsedPredicate.GetPublicKey()); @@ -102,7 +103,8 @@ bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& return state.Invalid(TxValidationResult::TX_CONSENSUS, "more-than-one-fee-output"); } if (out.nValue == 0) continue; - nFee = out.nValue; + if (parsedPredicate.IsPayFeePredicate()) + nFee = out.nValue; range_proof::Generators gen = gf.GetInstance(out.tokenId); balanceKey = balanceKey - (gen.G * MclScalar(out.nValue)); } diff --git a/src/coins.h b/src/coins.h index 8dce3fcef4224..deb273eb5633a 100644 --- a/src/coins.h +++ b/src/coins.h @@ -180,6 +180,11 @@ struct TokenCacheEntry { { return flags & ERASE; } + + void Erase() + { + flags |= ERASE; + } }; using TokensMap = std::map; diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 9da5cb96373f3..3256faba79986 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -6,6 +6,7 @@ #define BITCOIN_INTERFACES_CHAIN_H #include +#include #include #include // For CTransactionRef #include @@ -32,6 +33,9 @@ enum class ChainstateRole; struct bilingual_str; struct CBlockLocator; struct FeeCalculation; +namespace blsct { +class TokenEntry; +} namespace node { struct NodeContext; } // namespace node @@ -188,6 +192,7 @@ class Chain //! the current chain UTXO set. Iterates through all the keys in the map and //! populates the values. virtual void findCoins(std::map& coins) = 0; + virtual void findTokens(std::map& tokens) = 0; //! Estimate fraction of total transactions verified if blocks up to //! the specified block hash are verified. diff --git a/src/node/coin.cpp b/src/node/coin.cpp index 221854c5f67d5..eb5a288fe4a7e 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -23,4 +23,22 @@ void FindCoins(const NodeContext& node, std::map& coins) } } } + +void FindTokens(const NodeContext& node, std::map& tokens) +{ + assert(node.mempool); + assert(node.chainman); + LOCK2(cs_main, node.mempool->cs); + CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); + CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); + + + for (auto it = tokens.begin(); it != tokens.end();) { + if (!mempool_view.GetToken(it->first, it->second)) { + it = tokens.erase(it); + } else { + ++it; + }; + } +} } // namespace node diff --git a/src/node/coin.h b/src/node/coin.h index b32e410e1cceb..1d123161c61b9 100644 --- a/src/node/coin.h +++ b/src/node/coin.h @@ -9,6 +9,11 @@ class COutPoint; class Coin; +class uint256; + +namespace blsct { +class TokenEntry; +} namespace node { struct NodeContext; @@ -22,6 +27,7 @@ struct NodeContext; * @param[in,out] coins map to fill */ void FindCoins(const node::NodeContext& node, std::map& coins); +void FindTokens(const NodeContext& node, std::map& tokens); } // namespace node #endif // BITCOIN_NODE_COIN_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 6d5bb3ca550fa..667b47e9a5dec 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -613,6 +613,7 @@ class ChainImpl : public Chain int{FillBlock(block2, block2_out, lock, active, chainman().m_blockman)}; } void findCoins(std::map& coins) override { return FindCoins(m_node, coins); } + void findTokens(std::map& tokens) override { return FindTokens(m_node, tokens); }; double guessVerificationProgress(const uint256& block_hash) override { LOCK(::cs_main); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 0dde5bb313ea7..b6fa6fc525744 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -231,12 +231,28 @@ std::unique_ptr BlockAssembler::CreateNewBLSCTBlock(const blsct: addPackageTxs(*m_mempool, nPackagesSelected, nDescendantsUpdated); } + CCoinsViewCache viewNew(&m_chainstate.CoinsTip()); + for (const CMutableTransaction& tx : txns) { + bool validPredicate = true; + CAmount txFees = 0; + for (auto& out : tx.vout) { + if (out.predicate.size() > 0) { + auto parsedPredicate = blsct::ParsePredicate(out.predicate); + if (!ExecutePredicate(parsedPredicate, viewNew)) { + validPredicate = false; + break; + } + } if (out.scriptPubKey.IsFee()) { - nFees += out.nValue; + txFees += out.nValue; } } + + if (!validPredicate) continue; + + nFees += txFees; pblock->vtx.push_back(MakeTransactionRef(tx)); } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index ec7471ff42fec..ce51038b555f5 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -376,7 +376,6 @@ template void UnserializeTransaction(TxType& tx, Stream& s, const TransactionSerParams& params) { const bool fAllowWitness = params.allow_witness; - s >> tx.nVersion; unsigned char flags = 0; tx.vin.clear(); diff --git a/src/txdb.cpp b/src/txdb.cpp index 3319de47b02bf..6c660c577ef9b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -86,7 +86,8 @@ bool CCoinsViewDB::HaveCoin(const COutPoint& outpoint) const bool CCoinsViewDB::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const { - return m_db->Read(TokenDbEntry(&tokenId), token); + auto ret = m_db->Read(TokenDbEntry(&tokenId), token); + return ret; }; bool CCoinsViewDB::GetAllTokens(TokensMap& tokensMap) const diff --git a/src/txmempool.cpp b/src/txmempool.cpp index df72387979cd8..cc1b1ef3450d4 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1008,6 +1008,11 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); } +bool CCoinsViewMemPool::GetToken(const uint256& tokenId, blsct::TokenEntry& token) const +{ + return base->GetToken(tokenId, token); +}; + void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx) { for (unsigned int n = 0; n < tx->vout.size(); ++n) { diff --git a/src/txmempool.h b/src/txmempool.h index 0b483f8e9d87d..1edeff29cb3ad 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -839,9 +839,10 @@ class CCoinsViewMemPool : public CCoinsViewBacked public: CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); + bool GetToken(const uint256& tokenId, blsct::TokenEntry& token) const override; /** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the * coin is not fetched from base. */ - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + bool GetCoin(const COutPoint& outpoint, Coin& coin) const override; /** Add the coins created by this transaction. These coins are only temporarily stored in * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); diff --git a/src/validation.cpp b/src/validation.cpp index b981bfba18f7c..0cd511702badc 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1104,7 +1104,9 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) } if (args.m_chainparams.GetConsensus().fBLSCT) { - if (!blsct::VerifyTx(tx, m_view, state, 0, args.m_chainparams.GetConsensus().nPePoSMinStakeAmount)) { + CCoinsViewCache viewNew(&m_active_chainstate.CoinsTip()); + + if (!blsct::VerifyTx(tx, viewNew, state, 0, args.m_chainparams.GetConsensus().nPePoSMinStakeAmount)) { return error("MemPoolAccept::ConsensusScriptChecks(): VerifyTx on transaction %s failed with %s", tx.GetHash().ToString(), state.ToString()); } @@ -2071,7 +2073,7 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn } if (tx.vout[o].predicate.size() > 0) { if (!blsct::ExecutePredicate(tx.vout[o].predicate, view, true)) { - error("DisconnectBlock(): Could not revert predicate"); + error("DisconnectBlock(): Could not revert predicate: %s", blsct::PredicateToString(tx.vout[o].predicate)); return DISCONNECT_FAILED; } } diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 20d3d092fbdf9..1bfbf8a5c5c9f 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1421,8 +1421,8 @@ static DBErrors LoadTxRecords(CWallet* pwallet, DatabaseBatch& batch, std::vecto return false; } value >> wtx; - if (wtx.GetHash() != hash) - return false; + + if (wtx.GetHash() != hash) return false; // Undo serialize changes in 31600 if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 46aa0de7f24f8..9421031ceb1a1 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -316,13 +316,6 @@ def run_test(self): result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}], rawtxs=[tx.serialize().hex()], ) - tx = tx_from_hex(raw_tx_reference) - tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) - tx.vout = [tx.vout[0]] * 2 - self.check_mempool_result( - result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}], - rawtxs=[tx.serialize().hex()], - ) self.log.info('A timelocked transaction') tx = tx_from_hex(raw_tx_reference) From e267c9137d62905c121338523fbbef14964c2f16 Mon Sep 17 00:00:00 2001 From: alex v Date: Fri, 22 Nov 2024 22:53:22 +0100 Subject: [PATCH 10/27] send token --- src/Makefile.am | 3 + .../bulletproofs_plus/range_proof.h | 36 +- src/blsct/range_proof/proof_base.h | 12 +- src/blsct/tokens/info.cpp | 21 ++ src/blsct/tokens/info.h | 16 +- src/blsct/tokens/predicate_exec.cpp | 68 ++++ src/blsct/tokens/predicate_exec.h | 16 + src/blsct/tokens/predicate_parser.cpp | 61 --- src/blsct/tokens/predicate_parser.h | 6 - src/blsct/tokens/rpc.cpp | 2 +- src/blsct/wallet/rpc.cpp | 353 +++++++++++++++++- src/blsct/wallet/txfactory.cpp | 13 +- src/blsct/wallet/txfactory_global.cpp | 10 +- src/blsct/wallet/verification.cpp | 9 +- src/blsct/wallet/verification.h | 2 +- src/consensus/tx_verify.cpp | 2 +- src/core_write.cpp | 4 +- src/node/miner.cpp | 7 +- src/policy/policy.cpp | 4 +- src/primitives/transaction.cpp | 4 +- src/primitives/transaction.h | 26 +- src/rpc/client.cpp | 5 + src/validation.cpp | 4 +- src/wallet/receive.cpp | 105 ++++-- src/wallet/receive.h | 28 +- src/wallet/rpc/coins.cpp | 79 ++-- src/wallet/rpc/spend.cpp | 205 ---------- src/wallet/rpc/wallet.cpp | 6 - src/wallet/spend.cpp | 4 +- src/wallet/transaction.h | 4 +- src/wallet/wallet.cpp | 14 +- src/wallet/wallet.h | 4 +- test/functional/blsct_token.py | 111 ++++++ .../test_framework/test_framework.py | 16 +- test/functional/test_framework/test_node.py | 4 + test/functional/test_runner.py | 1 + 36 files changed, 807 insertions(+), 458 deletions(-) create mode 100644 src/blsct/tokens/info.cpp create mode 100644 src/blsct/tokens/predicate_exec.cpp create mode 100644 src/blsct/tokens/predicate_exec.h create mode 100755 test/functional/blsct_token.py diff --git a/src/Makefile.am b/src/Makefile.am index 81a6fe7a4e3e5..55a8323396b8a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -199,6 +199,7 @@ BLSCT_H = \ blsct/signature.h \ blsct/tokens/info.h \ blsct/tokens/predicate.h \ + blsct/tokens/predicate_exec.h \ blsct/tokens/predicate_parser.h \ blsct/tokens/rpc.h \ blsct/wallet/address.h \ @@ -252,6 +253,8 @@ BLSCT_CPP = \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/signature.cpp \ + blsct/tokens/info.cpp \ + blsct/tokens/predicate_exec.cpp \ blsct/tokens/predicate_parser.cpp \ blsct/wallet/verification.cpp diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.h b/src/blsct/range_proof/bulletproofs_plus/range_proof.h index da7a6dec3c0dc..4422c7f6bcda5 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.h @@ -44,28 +44,32 @@ struct RangeProof: public range_proof::ProofBase { void Serialize(Stream& s) const { range_proof::ProofBase::Serialize(s); - ::Serialize(s, A); - ::Serialize(s, A_wip); - ::Serialize(s, B); - ::Serialize(s, r_prime); - ::Serialize(s, s_prime); - ::Serialize(s, delta_prime); - ::Serialize(s, alpha_hat); - ::Serialize(s, tau_x); + if (range_proof::ProofBase::Vs.Size() > 0) { + ::Serialize(s, A); + ::Serialize(s, A_wip); + ::Serialize(s, B); + ::Serialize(s, r_prime); + ::Serialize(s, s_prime); + ::Serialize(s, delta_prime); + ::Serialize(s, alpha_hat); + ::Serialize(s, tau_x); + } } template void Unserialize(Stream& s) { range_proof::ProofBase::Unserialize(s); - ::Unserialize(s, A); - ::Unserialize(s, A_wip); - ::Unserialize(s, B); - ::Unserialize(s, r_prime); - ::Unserialize(s, s_prime); - ::Unserialize(s, delta_prime); - ::Unserialize(s, alpha_hat); - ::Unserialize(s, tau_x); + if (range_proof::ProofBase::Vs.Size() > 0) { + ::Unserialize(s, A); + ::Unserialize(s, A_wip); + ::Unserialize(s, B); + ::Unserialize(s, r_prime); + ::Unserialize(s, s_prime); + ::Unserialize(s, delta_prime); + ::Unserialize(s, alpha_hat); + ::Unserialize(s, tau_x); + } } }; diff --git a/src/blsct/range_proof/proof_base.h b/src/blsct/range_proof/proof_base.h index 38e4dcb3cc32b..fb1d0d119a4c8 100644 --- a/src/blsct/range_proof/proof_base.h +++ b/src/blsct/range_proof/proof_base.h @@ -33,16 +33,20 @@ struct ProofBase { void Serialize(Stream& s) const { ::Serialize(s, Vs); - ::Serialize(s, Ls); - ::Serialize(s, Rs); + if (Vs.Size() > 0) { + ::Serialize(s, Ls); + ::Serialize(s, Rs); + } } template void Unserialize(Stream& s) { ::Unserialize(s, Vs); - ::Unserialize(s, Ls); - ::Unserialize(s, Rs); + if (Vs.Size() > 0) { + ::Unserialize(s, Ls); + ::Unserialize(s, Rs); + } } }; diff --git a/src/blsct/tokens/info.cpp b/src/blsct/tokens/info.cpp new file mode 100644 index 0000000000000..fbd2f12bd59ac --- /dev/null +++ b/src/blsct/tokens/info.cpp @@ -0,0 +1,21 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +namespace blsct { +std::string TokenTypeToString(const TokenType& type) +{ + switch (type) { + case TOKEN: { + return "token"; + } + case NFT: { + return "nft"; + } + default: + return "unknown"; + } +} +} // namespace blsct \ No newline at end of file diff --git a/src/blsct/tokens/info.h b/src/blsct/tokens/info.h index 6e25a6f657033..b2b3d1ca3926d 100644 --- a/src/blsct/tokens/info.h +++ b/src/blsct/tokens/info.h @@ -15,19 +15,9 @@ enum TokenType : unsigned char { TOKEN = 0, NFT = 1 }; -std::string TokenTypeToString(const TokenType& type) -{ - switch (type) { - case TOKEN: { - return "token"; - } - case NFT: { - return "nft"; - } - default: - return "unknown"; - } -} + +std::string TokenTypeToString(const TokenType& type); + class TokenInfo { public: diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp new file mode 100644 index 0000000000000..1188390801269 --- /dev/null +++ b/src/blsct/tokens/predicate_exec.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +namespace blsct { +bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect) +{ + if (predicate.IsCreateTokenPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + blsct::TokenEntry token; + if (view.GetToken(hash, token) == !fDisconnect) return false; + + if (fDisconnect) + view.EraseToken(hash); + else + view.AddToken(hash, std::move(predicate.GetTokenInfo())); + + return true; + } else if (predicate.IsMintTokenPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + blsct::TokenEntry token; + + if (!view.GetToken(hash, token)) + return false; + if (!token.Mint(predicate.GetAmount() * (1 - 2 * fDisconnect))) + return false; + + view.AddToken(hash, std::move(token)); + + return true; + } else if (predicate.IsMintNftPredicate()) { + auto hash = predicate.GetPublicKey().GetHash(); + + blsct::TokenEntry token; + + if (!view.GetToken(hash, token)) + return false; + + if (predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) + return false; + + if (token.mapMintedNft.contains(predicate.GetNftId()) == !fDisconnect) + return false; + + if (fDisconnect) + token.mapMintedNft.erase(predicate.GetNftId()); + else + token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); + + view.AddToken(hash, std::move(token)); + + return true; + } else if (predicate.IsPayFeePredicate()) { + return true; + } + + return false; +} + +bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect) +{ + return ExecutePredicate(ParsePredicate(vch), view, fDisconnect); +} +} // namespace blsct \ No newline at end of file diff --git a/src/blsct/tokens/predicate_exec.h b/src/blsct/tokens/predicate_exec.h new file mode 100644 index 0000000000000..b9d81f20500f0 --- /dev/null +++ b/src/blsct/tokens/predicate_exec.h @@ -0,0 +1,16 @@ +// Copyright (c) 2024 The Navio developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef NAVIO_BLSCT_TOKENS_PREDICATE_EXEC_H +#define NAVIO_BLSCT_TOKENS_PREDICATE_EXEC_H + +#include +#include + +namespace blsct { +bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect = false); +bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect = false); +} // namespace blsct + +#endif // NAVIO_BLSCT_TOKENS_PREDICATE_EXEC_H \ No newline at end of file diff --git a/src/blsct/tokens/predicate_parser.cpp b/src/blsct/tokens/predicate_parser.cpp index 63beec5a2d129..2882177421443 100644 --- a/src/blsct/tokens/predicate_parser.cpp +++ b/src/blsct/tokens/predicate_parser.cpp @@ -49,65 +49,4 @@ std::string PredicateToString(const VectorPredicate& vch) return ret; } - -bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect) -{ - if (predicate.IsCreateTokenPredicate()) { - auto hash = predicate.GetPublicKey().GetHash(); - - blsct::TokenEntry token; - if (view.GetToken(hash, token) == !fDisconnect) return false; - - if (fDisconnect) - view.EraseToken(hash); - else - view.AddToken(hash, std::move(predicate.GetTokenInfo())); - - return true; - } else if (predicate.IsMintTokenPredicate()) { - auto hash = predicate.GetPublicKey().GetHash(); - - blsct::TokenEntry token; - - if (!view.GetToken(hash, token)) - return false; - if (!token.Mint(predicate.GetAmount() * (1 - 2 * fDisconnect))) - return false; - - view.AddToken(hash, std::move(token)); - - return true; - } else if (predicate.IsMintNftPredicate()) { - auto hash = predicate.GetPublicKey().GetHash(); - - blsct::TokenEntry token; - - if (!view.GetToken(hash, token)) - return false; - - if (predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) - return false; - - if (token.mapMintedNft.contains(predicate.GetNftId()) == !fDisconnect) - return false; - - if (fDisconnect) - token.mapMintedNft.erase(predicate.GetNftId()); - else - token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); - - view.AddToken(hash, std::move(token)); - - return true; - } else if (predicate.IsPayFeePredicate()) { - return true; - } - - return false; -} - -bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect) -{ - return ExecutePredicate(ParsePredicate(vch), view, fDisconnect); -} } // namespace blsct \ No newline at end of file diff --git a/src/blsct/tokens/predicate_parser.h b/src/blsct/tokens/predicate_parser.h index 6feeac627aa06..5abb91c0ae9cf 100644 --- a/src/blsct/tokens/predicate_parser.h +++ b/src/blsct/tokens/predicate_parser.h @@ -8,10 +8,8 @@ #include #include #include -#include namespace blsct { - enum PredicateOperation : uint8_t { CREATE_TOKEN, MINT, @@ -187,10 +185,6 @@ class ParsedPredicate ParsedPredicate ParsePredicate(const VectorPredicate& vch); std::string PredicateToString(const VectorPredicate& vch); -bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, const bool& fDisconnect = false); -bool ExecutePredicate(const VectorPredicate& vch, CCoinsViewCache& view, const bool& fDisconnect = false); - - } // namespace blsct #endif // NAVIO_BLSCT_TOKENS_PREDICATE_PARSER_H \ No newline at end of file diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp index ea481f8d16e33..81729e13f8504 100644 --- a/src/blsct/tokens/rpc.cpp +++ b/src/blsct/tokens/rpc.cpp @@ -14,7 +14,7 @@ std::vector tokenInfoResult = { RPCResult{RPCResult::Type::STR_HEX, "tokenId", "the token id"}, RPCResult{RPCResult::Type::STR_HEX, "publicKey", "the token public key"}, - RPCResult{RPCResult::Type::NUM, "type", "the token type"}, + RPCResult{RPCResult::Type::STR, "type", "the token type"}, RPCResult{RPCResult::Type::ANY, "metadata", "the token metadata"}, RPCResult{RPCResult::Type::NUM, "maxSupply", "the token max supply"}, RPCResult{RPCResult::Type::NUM, "currentSupply", "the token current supply"}, diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp index 2ae85a77f6e3e..79601bf5376b9 100644 --- a/src/blsct/wallet/rpc.cpp +++ b/src/blsct/wallet/rpc.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -11,9 +12,40 @@ #include #include #include +#include #include namespace blsct { +static void ParseBLSCTRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, const std::string& sMemo, std::vector& recipients) +{ + std::set destinations; + int i = 0; + for (const std::string& address : address_amounts.getKeys()) { + CTxDestination dest = DecodeDestination(address); + if (!IsValidDestination(dest) || dest.index() != 8) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid BLSCT address: ") + address); + } + + if (destinations.count(dest)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address); + } + destinations.insert(dest); + + CAmount amount = AmountFromValue(address_amounts[i++]); + + bool subtract_fee = false; + for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) { + const UniValue& addr = subtract_fee_outputs[idx]; + if (addr.get_str() == address) { + subtract_fee = true; + } + } + + wallet::CBLSCTRecipient recipient = {amount, sMemo, dest, subtract_fee, false}; + recipients.push_back(recipient); + } +} + UniValue SendTransaction(wallet::CWallet& wallet, const blsct::CreateTransactionData& transactionData, const bool& verbose) { // This should always try to sign, if we don't have private keys, don't try to do anything here. @@ -139,7 +171,10 @@ RPCHelpMan createtoken() }, {"max_supply", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The token max supply."}}, RPCResult{ - RPCResult::Type::STR_HEX, "tokenId", "the token id"}, + RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR_HEX, "hash", "The broadcasted transaction hash"}, + {RPCResult::Type::STR_HEX, "tokenId", "The token id"}, + }}, RPCExamples{HelpExampleRpc("createtoken", "{\"name\":\"Token\"} 1000")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { return CreateTokenOrNft(self, request, blsct::TOKEN); @@ -207,10 +242,7 @@ RPCHelpMan minttoken() auto hash = blsct::SendTransaction(*pwallet, transactionData, false); - UniValue ret{UniValue::VOBJ}; - ret.pushKV("hash", hash); - - return ret; + return hash; }, }; } @@ -302,10 +334,310 @@ static RPCHelpMan mintnft() auto hash = blsct::SendTransaction(*pwallet, transactionData, false); - UniValue ret{UniValue::VOBJ}; - ret.pushKV("hash", hash); + return hash; + }, + }; +} + +RPCHelpMan gettokenbalance() +{ + return RPCHelpMan{ + "gettokenbalance", + "\nReturns the total available balance of a token.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n", + { + {"token_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The token id"}, + {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, + {"minconf", RPCArg::Type::NUM, RPCArg::Default{0}, "Only include transactions confirmed at least this many times."}, + {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"}, + {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."}, + }, + RPCResult{ + RPCResult::Type::STR_AMOUNT, "amount", "The total amount received for this wallet."}, + RPCExamples{ + "\nThe total amount in the wallet with 0 or more confirmations\n" + HelpExampleCli("gettokenbalance", "0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d") + + "\nThe total amount in the wallet with at least 6 confirmations\n" + HelpExampleCli("gettokenbalance", "0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d \"*\" 6") + + "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettokenbalance", "\"0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d\", \"*\", 6")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const std::shared_ptr pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); + + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto token = tokens[token_id]; + + if (token.info.type != blsct::TokenType::TOKEN) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Wrong token type"); + + const auto dummy_value{self.MaybeArg(1)}; + if (dummy_value && *dummy_value != "*") { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); + } + + int min_depth = 0; + if (!request.params[2].isNull()) { + min_depth = request.params[2].getInt(); + } + + bool include_watchonly = ParseIncludeWatchonly(request.params[3], *pwallet); + + bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[4]); + + const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse, token_id); + + return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); + }, + }; +} + +RPCHelpMan sendtoblsctaddress() +{ + return RPCHelpMan{ + "sendtoblsctaddress", + "\nSend an amount to a given blsct address." + + wallet::HELP_REQUIRING_PASSPHRASE, + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLSCT address to send to."}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, + {"memo", RPCArg::Type::STR, RPCArg::Default{""}, "A memo used to store in the transaction.\n" + "The recipient will see its value."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, + }, + { + RPCResult{"if verbose is not set or set to false", + RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + RPCResult{ + "if verbose is set to true", + RPCResult::Type::OBJ, + "", + "", + {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, + }, + }, + RPCExamples{ + "\nSend 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1") + + "\nSend 0.1 " + CURRENCY_UNIT + " including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + // Wallet comments + std::string sMemo; + if (!request.params[2].isNull() && !request.params[2].get_str().empty()) + sMemo = request.params[2].get_str(); + + const std::string address = request.params[1].get_str(); + + const bool verbose{request.params[3].isNull() ? false : request.params[10].get_bool()}; + + blsct::CreateTransactionData transactionData(address, AmountFromValue(request.params[1]), sMemo, TokenId(), blsct::CreateTransactionType::NORMAL, 0); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); + }, + }; +} + +RPCHelpMan sendtokentoblsctaddress() +{ + return RPCHelpMan{ + "sendtokentoblsctaddress", + "\nSend an amount to tokens to a given blsct address." + + wallet::HELP_REQUIRING_PASSPHRASE, + { + {"token_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The token id."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLSCT address to send to."}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, + {"memo", RPCArg::Type::STR, RPCArg::Default{""}, "A memo used to store in the transaction.\n" + "The recipient will see its value."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, + }, + { + RPCResult{"if verbose is not set or set to false", + RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + RPCResult{ + "if verbose is set to true", + RPCResult::Type::OBJ, + "", + "", + {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, + }, + }, + RPCExamples{ + "\nSend 0.1 tokens\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1") + + "\nSend 0.1 tokens including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendtotokensblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); + + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto token = tokens[token_id]; + + if (token.info.type != blsct::TokenType::TOKEN) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Wrong token type"); + + // Wallet comments + std::string sMemo; + if (!request.params[3].isNull() && !request.params[3].get_str().empty()) + sMemo = request.params[3].get_str(); + + const std::string address = request.params[1].get_str(); + + const bool verbose{request.params[4].isNull() ? false : request.params[11].get_bool()}; + + blsct::CreateTransactionData transactionData(address, AmountFromValue(request.params[2]), sMemo, TokenId(token_id), blsct::CreateTransactionType::NORMAL, 0); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); + }, + }; +} + + +RPCHelpMan stakelock() +{ + return RPCHelpMan{ + "stakelock", + "\nLock an amount in order to stake it." + + wallet::HELP_REQUIRING_PASSPHRASE, + { + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to stake. eg 0.1"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, + }, + { + RPCResult{"if verbose is not set or set to false", + RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + RPCResult{ + "if verbose is set to true", + RPCResult::Type::OBJ, + "", + "", + {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, + }, + }, + RPCExamples{ + "\nLock 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("stakelock", "0.1")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + UniValue address_amounts(UniValue::VOBJ); + auto op_dest = pwallet->GetNewDestination(OutputType::BLSCT_STAKE, "Locked Stake"); + if (!op_dest) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); + } + + const std::string address = EncodeDestination(*op_dest); + address_amounts.pushKV(address, request.params[0]); + + std::vector recipients; + blsct::ParseBLSCTRecipients(address_amounts, false, "", recipients); + const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; + + blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT, Params().GetConsensus().nPePoSMinStakeAmount); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); + }, + }; +} + + +RPCHelpMan stakeunlock() +{ + return RPCHelpMan{ + "stakeunlock", + "\nUnlocks an staked amount." + + wallet::HELP_REQUIRING_PASSPHRASE, + { + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to unstake. eg 0.1"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, + }, + { + RPCResult{"if verbose is not set or set to false", + RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + RPCResult{ + "if verbose is set to true", + RPCResult::Type::OBJ, + "", + "", + {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, + }, + }, + RPCExamples{ + "\nLock 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("stakelock", "0.1")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + UniValue address_amounts(UniValue::VOBJ); + auto op_dest = pwallet->GetNewDestination(OutputType::BLSCT_STAKE, ""); + if (!op_dest) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); + } + + const std::string address = EncodeDestination(*op_dest); + address_amounts.pushKV(address, request.params[0]); + + std::vector recipients; + blsct::ParseBLSCTRecipients(address_amounts, false, "", recipients); + const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; + + + blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT_UNSTAKE, Params().GetConsensus().nPePoSMinStakeAmount); + + EnsureWalletIsUnlocked(*pwallet); - return ret; + return blsct::SendTransaction(*pwallet, transactionData, verbose); }, }; } @@ -317,6 +649,11 @@ Span GetBLSCTWalletRPCCommands() {"blsct", &createtoken}, {"blsct", &minttoken}, {"blsct", &mintnft}, + {"blsct", &gettokenbalance}, + {"blsct", &sendtoblsctaddress}, + {"blsct", &sendtokentoblsctaddress}, + {"blsct", &stakelock}, + {"blsct", &stakeunlock}, }; return commands; } \ No newline at end of file diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp index 51832b92879b6..f207b94cf703f 100644 --- a/src/blsct/wallet/txfactory.cpp +++ b/src/blsct/wallet/txfactory.cpp @@ -114,8 +114,10 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA this->tx.vout.push_back(out.out); auto outHash = out.out.GetHash(); - if (out.out.IsBLSCT()) { + if (out.out.HasBLSCTRangeProof()) { outputGammas = outputGammas - out.gamma; + } + if (out.out.HasBLSCTKeys()) { outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(outHash)); } @@ -252,15 +254,18 @@ std::optional TxFactoryBase::CreateTransaction(const std::v } } - bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; + //bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; if (transactionData.type == TX_CREATE_TOKEN) { tx.AddOutput(transactionData.tokenKey, transactionData.tokenInfo); } else if (transactionData.type == TX_MINT_TOKEN) { - if (!transactionData.token_id.IsNFT()) + if (!transactionData.token_id.IsNFT()) { tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.nAmount); - else + } else { tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.token_id.subid, transactionData.nftMetadata); + } + } else if (transactionData.type == NORMAL) { + tx.AddOutput(transactionData.destination, transactionData.nAmount, transactionData.sMemo, transactionData.token_id, transactionData.type); } } diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index e4f6f4a06c70a..98b83f5e37eca 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -124,8 +124,12 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun ret.out.scriptPubKey = CScript() << OP_STAKED_COMMITMENT << blsct::Common::DataStreamToVector(ss) << OP_DROP << OP_TRUE; } - auto p = rp.Prove(vs, nonce, memo, tokenId); - ret.out.blsctData.rangeProof = p; + if (tokenId.IsNFT()) { + ret.out.nValue = nAmount; + } else { + auto p = rp.Prove(vs, nonce, memo, tokenId); + ret.out.blsctData.rangeProof = p; + } ret.GenerateKeys(ret.blindingKey, destKeys); HashWriter hash{}; hash << nonce; @@ -150,7 +154,7 @@ CTransactionRef AggregateTransactions(const std::vector& txs) ret.vin.push_back(in); } for (auto& out : tx->vout) { - if (out.scriptPubKey.IsFee()) { + if (out.IsFee()) { if (out.predicate.size() > 0) { auto parsedPredicate = blsct::ParsePredicate(out.predicate); if (parsedPredicate.IsPayFeePredicate()) { diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp index 6925683d82df1..7fcbdef9723f2 100644 --- a/src/blsct/wallet/verification.cpp +++ b/src/blsct/wallet/verification.cpp @@ -78,13 +78,14 @@ bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& return state.Invalid(TxValidationResult::TX_CONSENSUS, "failed-to-execute-predicate"); } - if (out.IsBLSCT()) { - bulletproofs_plus::RangeProofWithSeed proof{out.blsctData.rangeProof, out.tokenId}; - + if (out.HasBLSCTKeys()) { vPubKeys.emplace_back(out.blsctData.ephemeralKey); vMessages.emplace_back(out_hash.begin(), out_hash.end()); - vProofs.emplace_back(proof); + } + if (out.HasBLSCTRangeProof()) { + bulletproofs_plus::RangeProofWithSeed proof{out.blsctData.rangeProof, out.tokenId}; + vProofs.emplace_back(proof); balanceKey = balanceKey - out.blsctData.rangeProof.Vs[0]; if (out.GetStakedCommitmentRangeProof(stakedCommitmentRangeProof)) { diff --git a/src/blsct/wallet/verification.h b/src/blsct/wallet/verification.h index 91d5ecdb1cee6..09309085cf06b 100644 --- a/src/blsct/wallet/verification.h +++ b/src/blsct/wallet/verification.h @@ -5,7 +5,7 @@ #ifndef BLSCT_VERIFICATION_H #define BLSCT_VERIFICATION_H -#include +#include #include #include #include diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 07ada34127351..d3d5d4a378cb1 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -208,7 +208,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, txfee = txfee_aux; } else { for (auto& out : tx.vout) { - if (out.scriptPubKey.IsFee()) + if (out.IsFee()) txfee = out.nValue; } } diff --git a/src/core_write.cpp b/src/core_write.cpp index bb81cd31a7a0d..37b00e03e66be 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -281,7 +281,9 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry out.pushKV("spendingKey", HexStr(txout.blsctData.spendingKey.GetVch())); out.pushKV("blindingKey", HexStr(txout.blsctData.blindingKey.GetVch())); UniValue rp(UniValue::VOBJ); - RangeProofToUniv(txout.blsctData.rangeProof, rp, extendedRangeProof); + if (txout.blsctData.rangeProof.Vs.Size() > 0) { + RangeProofToUniv(txout.blsctData.rangeProof, rp, extendedRangeProof); + } out.pushKV("rangeProof", rp); out.pushKV("viewTag", txout.blsctData.viewTag); out.pushKV("tokenId", txout.tokenId.ToString()); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index b6fa6fc525744..abd18ab4e7af1 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -241,11 +241,12 @@ std::unique_ptr BlockAssembler::CreateNewBLSCTBlock(const blsct: if (out.predicate.size() > 0) { auto parsedPredicate = blsct::ParsePredicate(out.predicate); if (!ExecutePredicate(parsedPredicate, viewNew)) { + LogPrintf("%s: Failed validation of predicate of output %s\n", __func__, out.ToString()); validPredicate = false; break; } } - if (out.scriptPubKey.IsFee()) { + if (out.IsFee()) { txFees += out.nValue; } } @@ -291,7 +292,7 @@ std::unique_ptr BlockAssembler::CreateNewBLSCTBlock(const blsct: pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); pblocktemplate->vTxFees[0] = -nFees; - LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); + LogPrintf("CreateNewBLSCTBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); @@ -363,7 +364,7 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) nBlockSigOpsCost += iter->GetSigOpCost(); if (iter->GetTx().IsBLSCT()) { for (auto& out : iter->GetTx().vout) { - if (out.scriptPubKey.IsFee()) + if (out.IsFee()) nFees += out.nValue; } } else { diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index c287b2fadfec2..24bec69135f78 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -130,7 +130,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat TxoutType whichType; for (const CTxOut& txout : tx.vout) { - if (txout.IsBLSCT()) + if (txout.HasBLSCTRangeProof() || txout.HasBLSCTKeys()) continue; if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) { @@ -180,7 +180,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxOut& prev = mapInputs.AccessCoin(tx.vin[i].prevout).out; - if (prev.IsBLSCT()) continue; + if (prev.HasBLSCTRangeProof() || prev.HasBLSCTKeys()) continue; std::vector > vSolutions; TxoutType whichType = Solver(prev.scriptPubKey, vSolutions); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 8a951e583be97..1808f1e897a26 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -67,9 +67,9 @@ CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, TokenId tokenIdI std::string CTxOut::ToString() const { return strprintf("CTxOut(scriptPubKey=%s%s%s%s%s)", HexStr(scriptPubKey).substr(0, 30), - IsBLSCT() ? strprintf(", spendingKey=%s, blindingKey=%s, ephemeralKey=%s", HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch())) : "", + HasBLSCTKeys() ? strprintf(", spendingKey=%s, blindingKey=%s, ephemeralKey=%s", HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch())) : "", tokenId.IsNull() ? "" : strprintf(", tokenId=%s", tokenId.ToString()), - IsBLSCT() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue)), predicate.size() > 0 ? strprintf(", predicate=%s", HexStr(predicate)) : ""); + HasBLSCTRangeProof() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue)), predicate.size() > 0 ? strprintf(", predicate=%s", HexStr(predicate)) : ""); } uint256 CTxOut::GetHash() const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index ce51038b555f5..d2e82417377c1 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -251,6 +251,8 @@ class CTxOut if (nFlags != 0) { ::Serialize(s, std::numeric_limits::max()); ::Serialize(s, nFlags); + if (nFlags & PREDICATE_MARKER) + ::Serialize(s, nValue); } else { ::Serialize(s, nValue); } @@ -272,6 +274,8 @@ class CTxOut if (nValue == std::numeric_limits::max()) { nValue = 0; ::Unserialize(s, nFlags); + if (nFlags & PREDICATE_MARKER) + ::Unserialize(s, nValue); } ::Unserialize(s, scriptPubKey); if (nFlags & BLSCT_MARKER) { @@ -296,11 +300,16 @@ class CTxOut return nValue == -1; } - bool IsBLSCT() const + bool HasBLSCTRangeProof() const { return blsctData.rangeProof.Vs.Size() > 0; } + bool HasBLSCTKeys() const + { + return !blsctData.ephemeralKey.IsZero() || !blsctData.blindingKey.IsZero() || !blsctData.spendingKey.IsZero(); + } + bool IsStakedCommitment() const { bulletproofs_plus::RangeProofWithSeed dummy; @@ -310,7 +319,7 @@ class CTxOut bool GetStakedCommitmentRangeProof(bulletproofs_plus::RangeProofWithSeed& rangeProof) const { - if (!IsBLSCT()) + if (!HasBLSCTRangeProof()) return false; if (scriptPubKey.size() <= 7) return false; if (blsctData.rangeProof.Vs.Size() == 0) @@ -342,6 +351,17 @@ class CTxOut return !(a == b); } + bool IsFee() const + { + blsct::ParsedPredicate parsedPredicate; + + if (predicate.size() > 0) { + parsedPredicate = blsct::ParsePredicate(predicate); + } + + return parsedPredicate.IsPayFeePredicate(); + } + std::string ToString() const; uint256 GetHash() const; }; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 9e5bc5e4c4d5a..891b8808e9fe8 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -44,6 +44,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getnetworkhashps", 1, "height" }, { "sendtoblsctaddress", 1, "amount" }, { "sendtoblsctaddress", 3, "verbose" }, + { "sendtokentoblsctaddress", 2, "amount" }, + { "sendtokentoblsctaddress", 4, "verbose" }, { "sendtoaddress", 1, "amount" }, { "sendtoaddress", 4, "subtractfeefromamount" }, { "sendtoaddress", 5 , "replaceable" }, @@ -72,6 +74,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getbalance", 1, "minconf" }, { "getbalance", 2, "include_watchonly" }, { "getbalance", 3, "avoid_reuse" }, + { "gettokenbalance", 2, "minconf" }, + { "gettokenbalance", 3, "include_watchonly" }, + { "gettokenbalance", 4, "avoid_reuse" }, { "getblockfrompeer", 1, "peer_id" }, { "getblockhash", 0, "height" }, { "waitforblockheight", 0, "height" }, diff --git a/src/validation.cpp b/src/validation.cpp index 0cd511702badc..3712aa2106c01 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2649,8 +2649,8 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, if (!blsct::VerifyTx(tx, view, tx_state, 0, params.GetConsensus().nPePoSMinStakeAmount)) { state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); - return error("ConnectBlock(): VerifyTx on transaction %s failed with %s", - tx.GetHash().ToString(), state.ToString()); + return error("ConnectBlock(): VerifyTx on transaction %s %s failed with %s", + tx.GetHash().ToString(), tx.ToString(), state.ToString()); } } else { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "blsct-tx-not-allowed"); diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 25d40b8fce900..f1f215ee0e8f3 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -29,12 +29,13 @@ bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefi return true; } -CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter) +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter, const TokenId& token_id) { - if (!txout.IsBLSCT() && !MoneyRange(txout.nValue)) + if (txout.tokenId != token_id) return 0; + if (!txout.HasBLSCTRangeProof() && !MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); LOCK(wallet.cs_wallet); - if (txout.IsBLSCT()) { + if (txout.HasBLSCTRangeProof()) { if (wallet.IsMine(txout) & filter) { CAmount ret = 0; auto blsct_man = wallet.GetBLSCTKeyMan(); @@ -55,11 +56,12 @@ CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const ismine return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0); } -CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter, const TokenId& token_id) { CAmount nCredit = 0; for (const CTxOut& txout : tx.vout) { + if (txout.tokenId != token_id) continue; nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); @@ -89,29 +91,32 @@ bool ScriptIsChange(const CWallet& wallet, const CScript& script) return false; } -bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout, const TokenId& token_id) { - if (txout.IsBLSCT()) { + if (txout.tokenId != token_id) return false; + if (txout.HasBLSCTRangeProof()) { auto blsct_km = wallet.GetBLSCTKeyMan(); if (blsct_km) return blsct_km->OutputIsChange(txout); } return ScriptIsChange(wallet, txout.scriptPubKey); } -CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout, const TokenId& token_id) { AssertLockHeld(wallet.cs_wallet); - if (!txout.IsBLSCT() && !MoneyRange(txout.nValue)) + if (txout.tokenId != token_id) return 0; + if (!txout.HasBLSCTRangeProof() && !MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); return (OutputIsChange(wallet, txout) ? txout.nValue : 0); } -CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx) +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx, const TokenId& token_id) { LOCK(wallet.cs_wallet); CAmount nChange = 0; for (const CTxOut& txout : tx.vout) { + if (txout.tokenId != token_id) continue; nChange += OutputGetChange(wallet, txout); if (!MoneyRange(nChange)) throw std::runtime_error(std::string(__func__) + ": value out of range"); @@ -119,8 +124,11 @@ CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx) return nChange; } -static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter) +static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter, const TokenId& token_id) { + if (!token_id.IsNull()) { + return type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter, token_id) : TxGetCredit(wallet, *wtx.tx, filter, token_id); + } auto& amount = wtx.m_amounts[type]; if (!amount.m_cached[filter]) { amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter)); @@ -129,7 +137,7 @@ static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CW return amount.m_value[filter]; } -CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id) { AssertLockHeld(wallet.cs_wallet); @@ -141,12 +149,12 @@ CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ism const isminefilter get_amount_filter{filter & ISMINE_ALL}; if (get_amount_filter) { // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, get_amount_filter); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, get_amount_filter, token_id); } return credit; } -CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id) { if (wtx.tx->vin.empty()) return 0; @@ -154,32 +162,32 @@ CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const ismi CAmount debit = 0; const isminefilter get_amount_filter{filter & ISMINE_ALL}; if (get_amount_filter) { - debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, get_amount_filter); + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, get_amount_filter, token_id); } return debit; } -CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx, const TokenId& token_id) { if (wtx.fChangeCached) return wtx.nChangeCached; - wtx.nChangeCached = TxGetChange(wallet, *wtx.tx); + wtx.nChangeCached = TxGetChange(wallet, *wtx.tx, token_id); wtx.fChangeCached = true; return wtx.nChangeCached; } -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id) { AssertLockHeld(wallet.cs_wallet); if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { - return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter); + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter, token_id); } return 0; } -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id) { AssertLockHeld(wallet.cs_wallet); @@ -199,8 +207,9 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, Txid hashTx = wtx.GetHash(); for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut& txout = wtx.tx->vout[i]; + if (txout.tokenId != token_id) continue; if (!wallet.IsSpent(COutPoint(hashTx, i)) && (allow_used_addresses || !wallet.IsSpentKey(txout.scriptPubKey))) { - nCredit += OutputGetCredit(wallet, txout, filter); + nCredit += OutputGetCredit(wallet, txout, filter, token_id); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + " : value out of range"); } @@ -238,7 +247,7 @@ std::vector GetStakedCommitmentInfo(const CWallet& wallet, void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, std::list& listReceived, std::list& listSent, CAmount& nFee, const isminefilter& filter, - bool include_change) + bool include_change, const TokenId& token_id) { nFee = 0; listReceived.clear(); @@ -264,24 +273,25 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { const CTxOut& txout = wtx.tx->vout[i]; + if (txout.tokenId != token_id) continue; isminetype fIsMine = wallet.IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > 0) { - if (OutputIsChange(wallet, txout) && (txout.IsBLSCT() || !include_change)) continue; + if (OutputIsChange(wallet, txout) && (txout.HasBLSCTRangeProof() || !include_change)) continue; } else if (!(fIsMine & filter)) continue; - if (wtx.tx->IsBLSCT() && txout.scriptPubKey.IsFee()) + if (wtx.tx->IsBLSCT() && txout.IsFee()) continue; // In either case, we need to get the destination address CTxDestination address; - if (txout.IsBLSCT()) { + if (txout.HasBLSCTRangeProof()) { auto blsct_km = wallet.GetBLSCTKeyMan(); if (!blsct_km) { address = CNoDestination(); @@ -364,7 +374,7 @@ bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx) return CachedTxIsTrusted(wallet, wtx, trusted_parents); } -Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) +Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse, const TokenId& token_id) { Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; @@ -376,9 +386,9 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) const CWalletTx& wtx = entry.second; const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)}; const int tx_depth{wallet.GetTxDepthInMainChain(wtx)}; - const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT | reuse_filter)}; - const CAmount tx_credit_staked_commitment{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_STAKED_COMMITMENT_BLSCT)}; - const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_WATCH_ONLY | reuse_filter)}; + const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT | reuse_filter, token_id)}; + const CAmount tx_credit_staked_commitment{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_STAKED_COMMITMENT_BLSCT, token_id)}; + const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_WATCH_ONLY | reuse_filter, token_id)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; @@ -388,8 +398,8 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) ret.m_mine_untrusted_pending += tx_credit_mine + tx_credit_staked_commitment; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } - ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT); - ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY); + ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT, token_id); + ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY, token_id); } } return ret; @@ -413,7 +423,7 @@ std::vector GetStakedCommitmentInfo(const CWallet& wallet) return ret; } -std::map GetAddressBalances(const CWallet& wallet) +std::map GetAddressBalances(const CWallet& wallet, const TokenId& token_id) { std::map balances; @@ -435,14 +445,31 @@ std::map GetAddressBalances(const CWallet& wallet) for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { const auto& output = wtx.tx->vout[i]; - CTxDestination addr; - if (!wallet.IsMine(output)) - continue; - if (!ExtractDestination(output.scriptPubKey, addr)) - continue; + if (output.tokenId != token_id) continue; + + if (output.HasBLSCTRangeProof()) { + auto blsct_km = wallet.GetBLSCTKeyMan(); + CTxDestination address; + if (!blsct_km) { + address = CNoDestination(); + } else { + address = blsct_km->GetDestination(output); + } - CAmount n = wallet.IsSpent(COutPoint(Txid::FromUint256(walletEntry.first), i)) ? 0 : output.nValue; - balances[addr] += n; + auto recoveryData = wtx.GetBLSCTRecoveryData(i); + + CAmount n = wallet.IsSpent(COutPoint(Txid::FromUint256(walletEntry.first), i)) ? 0 : recoveryData.amount; + balances[address] += n; + } else { + CTxDestination addr; + if (!wallet.IsMine(output)) + continue; + if (!ExtractDestination(output.scriptPubKey, addr)) + continue; + + CAmount n = wallet.IsSpent(COutPoint(Txid::FromUint256(walletEntry.first), i)) ? 0 : output.nValue; + balances[addr] += n; + } } } } @@ -450,7 +477,7 @@ std::map GetAddressBalances(const CWallet& wallet) return balances; } -std::set> GetAddressGroupings(const CWallet& wallet) +std::set> GetAddressGroupings(const CWallet& wallet, const TokenId& token_id) { AssertLockHeld(wallet.cs_wallet); std::set> groupings; diff --git a/src/wallet/receive.h b/src/wallet/receive.h index bc44fd29302c9..d7889a8556787 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -16,22 +16,22 @@ isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin) EXCLUSIVE_LOCKS /** Returns whether all of the inputs match the filter */ bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); -CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter); -CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter, const TokenId& token_id = TokenId()); +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter, const TokenId& token_id = TokenId()); bool ScriptIsChange(const CWallet& wallet, const CScript& script) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx); +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx, const TokenId& token_id = TokenId()); -CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); //! filter decides which addresses will count towards the debit -CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); -CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id = TokenId()); +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx, const TokenId& token_id = TokenId()); +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter = ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT) +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter = ISMINE_SPENDABLE | ISMINE_SPENDABLE_BLSCT, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct COutputEntry { @@ -43,7 +43,7 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, std::list& listReceived, std::list& listSent, CAmount& nFee, const isminefilter& filter, - bool include_change); + bool include_change, const TokenId& token_id = TokenId()); bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx); @@ -57,10 +57,10 @@ struct Balance { CAmount m_watchonly_untrusted_pending{0}; CAmount m_watchonly_immature{0}; }; -Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true); +Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true, const TokenId& token_id = TokenId()); -std::map GetAddressBalances(const CWallet& wallet); -std::set> GetAddressGroupings(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +std::map GetAddressBalances(const CWallet& wallet, const TokenId& token_id = TokenId()); +std::set> GetAddressGroupings(const CWallet& wallet, const TokenId& token_id = TokenId()) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct StakedCommitmentInfo { Txid hashTx; diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index e83ead76f4b2a..d561bed4bed7c 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -217,56 +217,51 @@ liststakedcommitments() RPCHelpMan getbalance() { - return RPCHelpMan{"getbalance", - "\nReturns the total available balance.\n" - "The available balance is what the wallet considers currently spendable, and is\n" - "thus affected by options which limit spendability such as -spendzeroconfchange.\n", - { - {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, - {"minconf", RPCArg::Type::NUM, RPCArg::Default{0}, "Only include transactions confirmed at least this many times."}, - {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"}, - {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."}, - }, - RPCResult{ - RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this wallet." - }, - RPCExamples{ - "\nThe total amount in the wallet with 0 or more confirmations\n" - + HelpExampleCli("getbalance", "") + - "\nThe total amount in the wallet with at least 6 confirmations\n" - + HelpExampleCli("getbalance", "\"*\" 6") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("getbalance", "\"*\", 6") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; + return RPCHelpMan{ + "getbalance", + "\nReturns the total available balance.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n", + { + {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, + {"minconf", RPCArg::Type::NUM, RPCArg::Default{0}, "Only include transactions confirmed at least this many times."}, + {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"}, + {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."}, + }, + RPCResult{ + RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this wallet."}, + RPCExamples{ + "\nThe total amount in the wallet with 0 or more confirmations\n" + HelpExampleCli("getbalance", "") + + "\nThe total amount in the wallet with at least 6 confirmations\n" + HelpExampleCli("getbalance", "\"*\" 6") + + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); - LOCK(pwallet->cs_wallet); + LOCK(pwallet->cs_wallet); - const auto dummy_value{self.MaybeArg(0)}; - if (dummy_value && *dummy_value != "*") { - throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); - } + const auto dummy_value{self.MaybeArg(0)}; + if (dummy_value && *dummy_value != "*") { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); + } - int min_depth = 0; - if (!request.params[1].isNull()) { - min_depth = request.params[1].getInt(); - } + int min_depth = 0; + if (!request.params[1].isNull()) { + min_depth = request.params[1].getInt(); + } - bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); + bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); - bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); + bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); - const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse); + const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse); - return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); -}, + return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); + }, }; } diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 3240b3df04065..e900aa0acc05b 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -57,36 +57,6 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& sub } } -static void ParseBLSCTRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, const std::string& sMemo, std::vector& recipients) -{ - std::set destinations; - int i = 0; - for (const std::string& address : address_amounts.getKeys()) { - CTxDestination dest = DecodeDestination(address); - if (!IsValidDestination(dest) || dest.index() != 8) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid BLSCT address: ") + address); - } - - if (destinations.count(dest)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address); - } - destinations.insert(dest); - - CAmount amount = AmountFromValue(address_amounts[i++]); - - bool subtract_fee = false; - for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) { - const UniValue& addr = subtract_fee_outputs[idx]; - if (addr.get_str() == address) { - subtract_fee = true; - } - } - - CBLSCTRecipient recipient = {amount, sMemo, dest, subtract_fee, false}; - recipients.push_back(recipient); - } -} - static void InterpretFeeEstimationInstructions(const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, UniValue& options) { if (options.exists("conf_target") || options.exists("estimate_mode")) { @@ -241,181 +211,6 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un } -RPCHelpMan sendtoblsctaddress() -{ - return RPCHelpMan{ - "sendtoblsctaddress", - "\nSend an amount to a given blsct address." + - HELP_REQUIRING_PASSPHRASE, - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLSCT address to send to."}, - {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, - {"memo", RPCArg::Type::STR, RPCArg::Default{""}, "A memo used to store in the transaction.\n" - "The recipient will see its value."}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, - }, - { - RPCResult{"if verbose is not set or set to false", - RPCResult::Type::STR_HEX, "txid", "The transaction id."}, - RPCResult{ - "if verbose is set to true", - RPCResult::Type::OBJ, - "", - "", - {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, - }, - }, - RPCExamples{ - "\nSend 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1") + - "\nSend 0.1 " + CURRENCY_UNIT + " including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\"")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); - - LOCK(pwallet->cs_wallet); - - // Wallet comments - std::string sMemo; - if (!request.params[2].isNull() && !request.params[2].get_str().empty()) - sMemo = request.params[2].get_str(); - - UniValue address_amounts(UniValue::VOBJ); - const std::string address = request.params[0].get_str(); - address_amounts.pushKV(address, request.params[1]); - - UniValue subtractFeeFromAmount(UniValue::VARR); - - std::vector recipients; - ParseBLSCTRecipients(address_amounts, false, sMemo, recipients); - const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - - blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::NORMAL, 0); - - EnsureWalletIsUnlocked(*pwallet); - - return blsct::SendTransaction(*pwallet, transactionData, verbose); - }, - }; -} - - -RPCHelpMan stakelock() -{ - return RPCHelpMan{ - "stakelock", - "\nLock an amount in order to stake it." + - HELP_REQUIRING_PASSPHRASE, - { - {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to stake. eg 0.1"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, - }, - { - RPCResult{"if verbose is not set or set to false", - RPCResult::Type::STR_HEX, "txid", "The transaction id."}, - RPCResult{ - "if verbose is set to true", - RPCResult::Type::OBJ, - "", - "", - {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, - }, - }, - RPCExamples{ - "\nLock 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("stakelock", "0.1")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); - - LOCK(pwallet->cs_wallet); - - UniValue address_amounts(UniValue::VOBJ); - auto op_dest = pwallet->GetNewDestination(OutputType::BLSCT_STAKE, "Locked Stake"); - if (!op_dest) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); - } - - const std::string address = EncodeDestination(*op_dest); - address_amounts.pushKV(address, request.params[0]); - - std::vector recipients; - ParseBLSCTRecipients(address_amounts, false, "", recipients); - const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - - blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT, Params().GetConsensus().nPePoSMinStakeAmount); - - EnsureWalletIsUnlocked(*pwallet); - - return blsct::SendTransaction(*pwallet, transactionData, verbose); - }, - }; -} - - -RPCHelpMan stakeunlock() -{ - return RPCHelpMan{ - "stakeunlock", - "\nUnlocks an staked amount." + - HELP_REQUIRING_PASSPHRASE, - { - {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to unstake. eg 0.1"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, - }, - { - RPCResult{"if verbose is not set or set to false", - RPCResult::Type::STR_HEX, "txid", "The transaction id."}, - RPCResult{ - "if verbose is set to true", - RPCResult::Type::OBJ, - "", - "", - {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, - }, - }, - RPCExamples{ - "\nLock 0.1 " + CURRENCY_UNIT + "\n" + HelpExampleCli("stakelock", "0.1")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); - - LOCK(pwallet->cs_wallet); - - UniValue address_amounts(UniValue::VOBJ); - auto op_dest = pwallet->GetNewDestination(OutputType::BLSCT_STAKE, ""); - if (!op_dest) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); - } - - const std::string address = EncodeDestination(*op_dest); - address_amounts.pushKV(address, request.params[0]); - - std::vector recipients; - ParseBLSCTRecipients(address_amounts, false, "", recipients); - const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - - - blsct::CreateTransactionData transactionData(recipients[0].destination, recipients[0].nAmount, recipients[0].sMemo, TokenId(), blsct::CreateTransactionType::STAKED_COMMITMENT_UNSTAKE, Params().GetConsensus().nPePoSMinStakeAmount); - - EnsureWalletIsUnlocked(*pwallet); - - return blsct::SendTransaction(*pwallet, transactionData, verbose); - }, - }; -} - - RPCHelpMan sendtoaddress() { return RPCHelpMan{ diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 227d887795572..3e0198a355929 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -872,11 +872,8 @@ RPCHelpMan encryptwallet(); // spend RPCHelpMan sendtoaddress(); -RPCHelpMan sendtoblsctaddress(); RPCHelpMan sendmany(); RPCHelpMan settxfee(); -RPCHelpMan stakelock(); -RPCHelpMan stakeunlock(); RPCHelpMan fundrawtransaction(); RPCHelpMan bumpfee(); RPCHelpMan psbtbumpfee(); @@ -958,7 +955,6 @@ Span GetWalletRPCCommands() {"wallet", &send}, {"wallet", &sendmany}, {"wallet", &sendtoaddress}, - {"wallet", &sendtoblsctaddress}, {"wallet", &sethdseed}, {"wallet", &setlabel}, {"wallet", &settxfee}, @@ -967,8 +963,6 @@ Span GetWalletRPCCommands() {"wallet", &signrawtransactionwithwallet}, {"wallet", &simulaterawtransaction}, {"wallet", &sendall}, - {"wallet", &stakelock}, - {"wallet", &stakeunlock}, {"wallet", &unloadwallet}, {"wallet", &upgradewallet}, {"wallet", &walletcreatefundedpsbt}, diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 521c0de20766c..9d345a8cb707f 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -385,7 +385,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, const CTxOut& output = wtx.tx->vout[i]; const COutPoint outpoint(Txid::FromUint256(txid), i); - auto nValue = output.IsBLSCT() ? wtx.GetBLSCTRecoveryData(i).amount : output.nValue; + auto nValue = output.HasBLSCTRangeProof() ? wtx.GetBLSCTRecoveryData(i).amount : output.nValue; if (nValue < params.min_amount || nValue > params.max_amount) continue; @@ -403,7 +403,7 @@ CoinsResult AvailableCoins(const CWallet& wallet, if (mine == ISMINE_NO) { continue; } - if (params.only_blsct && !output.IsBLSCT()) { + if (params.only_blsct && !(output.HasBLSCTRangeProof() || output.HasBLSCTKeys())) { continue; } if (params.include_staked_commitment && !output.IsStakedCommitment()) { diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 63615ebe13468..10608d3105322 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -359,11 +359,11 @@ class CWalletTx CAmount ret = 0; size_t i = 0; for (auto& output : tx->vout) { - if (tx->IsBLSCT() && output.scriptPubKey.IsFee()) { + if (tx->IsBLSCT() && output.IsFee()) { i++; continue; } - ret += output.IsBLSCT() ? GetBLSCTRecoveryData(i).amount : output.nValue; + ret += output.HasBLSCTRangeProof() ? GetBLSCTRecoveryData(i).amount : output.nValue; i++; } return ret; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8668cce0ba073..7ab8a70937d2c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1156,7 +1156,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const } for (auto& txout : wtx.tx->vout) { - if (txout.IsBLSCT()) { + if (txout.HasBLSCTRangeProof()) { auto blsct_man = GetBLSCTKeyMan(); if (blsct_man) { auto result = blsct_man->RecoverOutputs({wtx.tx->vout}); @@ -1592,7 +1592,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() const // Note that this function doesn't distinguish between a 0-valued input, // and a not-"is mine" (according to the filter) input. -CAmount CWallet::GetDebit(const CTxIn& txin, const isminefilter& filter) const +CAmount CWallet::GetDebit(const CTxIn& txin, const isminefilter& filter, const TokenId& token_id) const { { LOCK(cs_wallet); @@ -1600,8 +1600,8 @@ CAmount CWallet::GetDebit(const CTxIn& txin, const isminefilter& filter) const if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.tx->vout.size()) { - if (prev.tx->vout[txin.prevout.n].IsBLSCT() && IsMine(prev.tx->vout[txin.prevout.n]) & filter) { - return prev.GetBLSCTRecoveryData(txin.prevout.n).amount; + if (prev.tx->vout[txin.prevout.n].HasBLSCTRangeProof() && IsMine(prev.tx->vout[txin.prevout.n]) & filter) { + return prev.tx->vout[txin.prevout.n].tokenId == token_id ? prev.GetBLSCTRecoveryData(txin.prevout.n).amount : 0; } else if (IsMine(prev.tx->vout[txin.prevout.n]) & filter) return prev.tx->vout[txin.prevout.n].nValue; } @@ -1613,7 +1613,7 @@ CAmount CWallet::GetDebit(const CTxIn& txin, const isminefilter& filter) const isminetype CWallet::IsMine(const CTxOut& txout) const { AssertLockHeld(cs_wallet); - if (txout.IsBLSCT()) { + if (txout.HasBLSCTRangeProof()) { auto blsct_man = GetBLSCTKeyMan(); if (blsct_man) { bool mine = blsct_man->IsMine(txout); @@ -1675,11 +1675,11 @@ bool CWallet::IsFromMe(const CTransaction& tx) const return (GetDebit(tx, ISMINE_ALL) > 0); } -CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const +CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter, const TokenId& token_id) const { CAmount nDebit = 0; for (const CTxIn& txin : tx.vin) { - nDebit += GetDebit(txin, filter); + nDebit += GetDebit(txin, filter, token_id); if (!MoneyRange(nDebit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 10e26c1e3c77c..3e3924032ae1e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -803,13 +803,13 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati * Returns amount of debit if the input matches the * filter, otherwise returns 0 */ - CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; + CAmount GetDebit(const CTxIn& txin, const isminefilter& filter, const TokenId& token_id = TokenId()) const; isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; - CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; + CAmount GetDebit(const CTransaction& tx, const isminefilter& filter, const TokenId& token_id = TokenId()) const; void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override; DBErrors LoadWallet(); diff --git a/test/functional/blsct_token.py b/test/functional/blsct_token.py new file mode 100755 index 0000000000000..69a1d121ab476 --- /dev/null +++ b/test/functional/blsct_token.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 The Navio Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +class NavioBlsctTokenTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser, blsct=True) + + def set_test_params(self): + # Set up two nodes for the test + self.num_nodes = 2 + self.chain = 'blsctregtest' + self.setup_clean_chain = True + + def run_test(self): + self.log.info("Creating wallet1 with BLSCT") + + # Create a new wallet + + #self.init_wallet(node=0, blsct=True) + self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) + self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) + wallet_info = self.nodes[0].get_wallet_rpc("wallet1") + wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") + + self.log.info("Loading wallet1") + + # Ensure wallet is loaded + wallets = self.nodes[0].listwallets() + assert "wallet1" in wallets, "wallet1 was not loaded successfully" + + self.log.info("Generating BLSCT address") + + # Generate a BLSCT address + blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") + blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") + + self.log.info(f"BLSCT address NODE 1: {blsct_address}") + self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") + + # Generate blocks and fund the BLSCT address + self.log.info("Generating 101 blocks to the BLSCT address") + block_hashes = self.generatetoblsctaddress(self.nodes[0], 101, blsct_address) + + self.log.info(f"Generated blocks: {len(block_hashes)}") + + # Check the balance of the wallet + balance = wallet_info.getbalance() + self.log.info(f"Balance in wallet1: {balance}") + + assert_equal(len(block_hashes), 101) + assert balance > 0, "Balance should be greater than zero after mining" + + self.log.info("Creating token and mining 1 block") + token = self.nodes[0].createtoken({"name": "Test"}, 1000) + block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) + + tokens = self.nodes[0].listtokens() + assert len(tokens) == 1, "length of tokens is not 1" + + self.log.info(f"Created token: {token["tokenId"]}") + + assert tokens[0]['type'] == 'token', "token type is not token" + assert tokens[0]['metadata'] == {'name': 'Test'}, "incorrect metadata" + assert tokens[0]['maxSupply'] == 100000000000, "incorrect max supply" + assert tokens[0]['currentSupply'] == 0, "incorrect current supply" + + self.nodes[0].minttoken(token['tokenId'], blsct_address, 1) + block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) + + tokenInfo = self.nodes[0].gettoken(token['tokenId']) + + assert tokenInfo['type'] == 'token', "token type is not token" + assert tokenInfo['metadata'] == {'name': 'Test'}, "incorrect metadata" + assert tokenInfo['maxSupply'] == 100000000000, "incorrect max supply" + assert tokenInfo['currentSupply'] == 100000000, "incorrect current supply" + + self.log.info(f"Minted 1 token") + + token_balance = self.nodes[0].gettokenbalance(token['tokenId']) + token_balance_2 = self.nodes[1].gettokenbalance(token['tokenId']) + + self.log.info(f"Balance in NDOE 1: {token_balance}") + self.log.info(f"Balance in NODE 2: {token_balance_2}") + + assert token_balance == 1, "incorrect token balance in node 1" + assert token_balance_2 == 0, "incorrect token balance in node 2" + + self.log.info(f"Sending 0.5 token to NODE 2") + + self.nodes[0].sendtokentoblsctaddress(token['tokenId'], blsct_address_2, 0.5) + self.generatetoblsctaddress(self.nodes[0], 2, blsct_address) + + token_balance = self.nodes[0].gettokenbalance(token['tokenId']) + token_balance_2 = self.nodes[1].gettokenbalance(token['tokenId']) + + assert token_balance == 0.5, "incorrect token balance in node 1" + assert token_balance_2 == 0.5, "incorrect token balance in node 2" + + self.log.info(f"Balance in NDOE 1: {token_balance}") + self.log.info(f"Balance in NODE 2: {token_balance_2}") + + +if __name__ == '__main__': + NavioBlsctTokenTest().main() \ No newline at end of file diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index ccdef92d0ca3a..218f0fd2c0538 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -431,12 +431,12 @@ def import_deterministic_coinbase_privkeys(self): for i in range(self.num_nodes): self.init_wallet(node=i) - def init_wallet(self, *, node): + def init_wallet(self, *, node, blsct=False): wallet_name = self.default_wallet_name if self.wallet_names is None else self.wallet_names[node] if node < len(self.wallet_names) else False if wallet_name is not False: n = self.nodes[node] if wallet_name is not None: - n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True) + n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True, blsct=blsct) n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=True) def run_test(self): @@ -445,9 +445,9 @@ def run_test(self): # Public helper methods. These can be accessed by the subclass test scripts. - def add_wallet_options(self, parser, *, descriptors=True, legacy=True): + def add_wallet_options(self, parser, *, descriptors=True, legacy=True, blsct=True): kwargs = {} - if descriptors + legacy == 1: + if descriptors + legacy + blsct == 1: # If only one type can be chosen, set it as default kwargs["default"] = descriptors group = parser.add_mutually_exclusive_group( @@ -459,6 +459,9 @@ def add_wallet_options(self, parser, *, descriptors=True, legacy=True): if legacy: group.add_argument("--legacy-wallet", action='store_const', const=False, **kwargs, help="Run test using legacy wallets", dest='descriptors') + if blsct: + group.add_argument("--blsct", action='store_const', const=False, **kwargs, + help="Run test using blsct wallets", dest='blsct') def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, binary=None, binary_cli=None, versions=None): """Instantiate TestNode objects. @@ -691,6 +694,11 @@ def generatetoaddress(self, generator, *args, sync_fun=None, **kwargs): sync_fun() if sync_fun else self.sync_all() return blocks + def generatetoblsctaddress(self, generator, *args, sync_fun=None, **kwargs): + blocks = generator.generatetoblsctaddress(*args, invalid_call=False, **kwargs) + sync_fun() if sync_fun else self.sync_all() + return blocks + def generatetodescriptor(self, generator, *args, sync_fun=None, **kwargs): blocks = generator.generatetodescriptor(*args, invalid_call=False, **kwargs) sync_fun() if sync_fun else self.sync_all() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index ba24ba51fce43..101be756e2d64 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -335,6 +335,10 @@ def generatetoaddress(self, *args, invalid_call, **kwargs): assert not invalid_call return self.__getattr__('generatetoaddress')(*args, **kwargs) + def generatetoblsctaddress(self, *args, invalid_call, **kwargs): + assert not invalid_call + return self.__getattr__('generatetoblsctaddress')(*args, **kwargs) + def generatetodescriptor(self, *args, invalid_call, **kwargs): assert not invalid_call return self.__getattr__('generatetodescriptor')(*args, **kwargs) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 285279fe731a5..a84f8c4fbe9bf 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -396,6 +396,7 @@ 'p2p_dandelionpp_loop.py', 'p2p_dandelionpp_mempool_leak.py', 'p2p_dandelionpp_probing.py', + 'blsct_token.py' # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] From 9f60ed928606437b9101737ca76fe6c29ccb232f Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 16:15:42 +0100 Subject: [PATCH 11/27] nft transfer --- src/blsct/arith/elements.cpp | 1 + src/blsct/arith/mcl/mcl_g1point.cpp | 2 + src/blsct/tokens/predicate_exec.cpp | 4 +- src/blsct/tokens/rpc.cpp | 3 +- src/blsct/wallet/keyman.cpp | 1 + src/blsct/wallet/rpc.cpp | 162 +++++++++++++++++++++++++++- src/blsct/wallet/txfactory.cpp | 4 +- src/blsct/wallet/verification.cpp | 19 ++-- src/primitives/transaction.cpp | 6 +- src/primitives/transaction.h | 9 +- src/rpc/client.cpp | 5 + src/wallet/spend.cpp | 6 +- src/wallet/wallet.cpp | 20 ++-- test/functional/blsct_nft.py | 111 +++++++++++++++++++ test/functional/test_runner.py | 3 +- 15 files changed, 317 insertions(+), 39 deletions(-) create mode 100755 test/functional/blsct_nft.py diff --git a/src/blsct/arith/elements.cpp b/src/blsct/arith/elements.cpp index 070fd6d44734a..c429872dd997b 100644 --- a/src/blsct/arith/elements.cpp +++ b/src/blsct/arith/elements.cpp @@ -200,6 +200,7 @@ template void Elements::ConfirmIndexInsideRange(const uint32_t& index) const { if (index >= m_vec.size()) { + assert(0); auto s = strprintf("index %d is out of range [0..%d]", index, m_vec.size() - 1ul); throw std::runtime_error(s); } diff --git a/src/blsct/arith/mcl/mcl_g1point.cpp b/src/blsct/arith/mcl/mcl_g1point.cpp index 6d5d1f167973d..1c4c968ebe46f 100644 --- a/src/blsct/arith/mcl/mcl_g1point.cpp +++ b/src/blsct/arith/mcl/mcl_g1point.cpp @@ -170,6 +170,8 @@ bool MclG1Point::IsValid() const bool MclG1Point::IsZero() const { + MclG1Point zero; + if (std::memcmp(&m_point, &zero.m_point, sizeof(MclG1Point::Underlying)) == 0) return true; return mclBnG1_isZero(&m_point); } diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 1188390801269..01b5cba8909fe 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -43,12 +43,12 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) return false; - if (token.mapMintedNft.contains(predicate.GetNftId()) == !fDisconnect) + if ((token.mapMintedNft.find(predicate.GetNftId()) != token.mapMintedNft.end()) == !fDisconnect) return false; if (fDisconnect) token.mapMintedNft.erase(predicate.GetNftId()); - else + else token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); view.AddToken(hash, std::move(token)); diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp index 81729e13f8504..c713c7afb0fcb 100644 --- a/src/blsct/tokens/rpc.cpp +++ b/src/blsct/tokens/rpc.cpp @@ -17,7 +17,8 @@ std::vector tokenInfoResult = { RPCResult{RPCResult::Type::STR, "type", "the token type"}, RPCResult{RPCResult::Type::ANY, "metadata", "the token metadata"}, RPCResult{RPCResult::Type::NUM, "maxSupply", "the token max supply"}, - RPCResult{RPCResult::Type::NUM, "currentSupply", "the token current supply"}, + RPCResult{RPCResult::Type::NUM, "currentSupply", true, "the token current supply"}, + RPCResult{RPCResult::Type::ANY, "mintedNft", true, "the nfts already minted"}, }; diff --git a/src/blsct/wallet/keyman.cpp b/src/blsct/wallet/keyman.cpp index eccd41703de46..96e1256e7084c 100644 --- a/src/blsct/wallet/keyman.cpp +++ b/src/blsct/wallet/keyman.cpp @@ -575,6 +575,7 @@ bulletproofs_plus::AmountRecoveryResult KeyMan::RecoverOutputs(const std: for (size_t i = 0; i < outs.size(); i++) { CTxOut out = outs[i]; + if (!out.HasBLSCTKeys() || !out.HasBLSCTRangeProof()) continue; if (out.blsctData.viewTag != CalculateViewTag(out.blsctData.blindingKey, viewKey.GetScalar())) continue; auto nonce = CalculateNonce(out.blsctData.blindingKey, viewKey.GetScalar()); diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp index 79601bf5376b9..7c1c0a196be79 100644 --- a/src/blsct/wallet/rpc.cpp +++ b/src/blsct/wallet/rpc.cpp @@ -61,6 +61,7 @@ UniValue SendTransaction(wallet::CWallet& wallet, const blsct::CreateTransaction } const CTransactionRef& tx = MakeTransactionRef(res.value()); + wallet::mapValue_t map_value; wallet.CommitTransaction(tx, std::move(map_value), /*orderForm=*/{}); if (verbose) { @@ -147,7 +148,10 @@ RPCHelpMan createnft() }, {"max_supply", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The NFT max supply."}}, RPCResult{ - RPCResult::Type::STR_HEX, "tokenId", "the token id"}, + RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR_HEX, "hash", "The broadcasted transaction hash"}, + {RPCResult::Type::STR_HEX, "tokenId", "The token id"}, + }}, RPCExamples{HelpExampleRpc("createnft", "{'name':'My NFT Collection'} 1000")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { return CreateTokenOrNft(self, request, blsct::NFT); @@ -295,7 +299,7 @@ static RPCHelpMan mintnft() auto blsct_km = pwallet->GetOrCreateBLSCTKeyMan(); uint256 token_id(ParseHashV(request.params[0], "token_id")); - CAmount nft_id = AmountFromValue(request.params[1]); + CAmount nft_id = AmountFromValue(request.params[1], 0); const std::string address = request.params[2].get_str(); std::map metadata; if (!request.params[3].isNull() && !request.params[3].get_obj().empty()) @@ -404,6 +408,82 @@ RPCHelpMan gettokenbalance() }; } +RPCHelpMan getnftbalance() +{ + return RPCHelpMan{ + "getnftbalance", + "\nReturns the NFTs owned from a collection.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n", + { + {"token_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The token id from the collection"}, + {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, + {"minconf", RPCArg::Type::NUM, RPCArg::Default{0}, "Only include transactions confirmed at least this many times."}, + {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"}, + {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."}, + }, + RPCResult{RPCResult::Type::ANY, "mintedNft", true, "the nfts already minted"}, + RPCExamples{ + "\nThe total amount in the wallet with 0 or more confirmations\n" + HelpExampleCli("getnftbalance", "0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d") + + "\nThe total amount in the wallet with at least 6 confirmations\n" + HelpExampleCli("getnftbalance", "0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d \"*\" 6") + + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getnftbalance", "\"0e8ba9acaef5a91e5933393baf0b1187fae81f158cd9455437378b1796fc893d\", \"*\", 6")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const std::shared_ptr pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); + + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto token = tokens[token_id]; + + if (token.info.type != blsct::TokenType::NFT) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Wrong token type"); + + const auto dummy_value{self.MaybeArg(1)}; + if (dummy_value && *dummy_value != "*") { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); + } + + int min_depth = 0; + if (!request.params[2].isNull()) { + min_depth = request.params[2].getInt(); + } + + bool include_watchonly = ParseIncludeWatchonly(request.params[3], *pwallet); + + bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[4]); + + UniValue ret(UniValue::VOBJ); + + for (auto& it : token.mapMintedNft) { + const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse, TokenId(token_id, it.first)); + + if ((bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)) > 0) { + UniValue metadata(UniValue::VOBJ); + for (auto& md_it : it.second) { + metadata.pushKV(md_it.first, md_it.second); + } + ret.pushKV(strprintf("%llu", it.first), metadata); + } + } + + return ret; + }, + }; +} + RPCHelpMan sendtoblsctaddress() { return RPCHelpMan{ @@ -485,8 +565,8 @@ RPCHelpMan sendtokentoblsctaddress() }, }, RPCExamples{ - "\nSend 0.1 tokens\n" + HelpExampleCli("sendtoblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1") + - "\nSend 0.1 tokens including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendtotokensblsctaddress", "\"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\"")}, + "\nSend 0.1 tokens\n" + HelpExampleCli("sendtokentoblsctaddress", "a685e520f85d111a6c55bd2b8226f6b916a3bcdd3b549c75e0abddc55df70951 \"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1") + + "\nSend 0.1 tokens including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendtotokensblsctaddress", "a685e520f85d111a6c55bd2b8226f6b916a3bcdd3b549c75e0abddc55df70951 \"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; @@ -530,6 +610,78 @@ RPCHelpMan sendtokentoblsctaddress() } +RPCHelpMan sendnfttoblsctaddress() +{ + return RPCHelpMan{ + "sendnfttoblsctaddress", + "\nSend an NFT to a given blsct address." + + wallet::HELP_REQUIRING_PASSPHRASE, + { + {"token_id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The token id."}, + {"nft_id", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The nft id."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLSCT address to send to."}, + {"memo", RPCArg::Type::STR, RPCArg::Default{""}, "A memo used to store in the transaction.\n" + "The recipient will see its value."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, + }, + { + RPCResult{"if verbose is not set or set to false", + RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + RPCResult{ + "if verbose is set to true", + RPCResult::Type::OBJ, + "", + "", + {{RPCResult::Type::STR_HEX, "txid", "The transaction id."}}, + }, + }, + RPCExamples{ + "\nSend NFT\n" + HelpExampleCli("sendnfttoblsctaddress", "a685e520f85d111a6c55bd2b8226f6b916a3bcdd3b549c75e0abddc55df70951 0 \"" + BLSCT_EXAMPLE_ADDRESS[0] + "\"") + + "\nSend NFT including \"donation\" as memo in the transaction using positional arguments\n" + HelpExampleCli("sendnfttoblsctaddress", "a685e520f85d111a6c55bd2b8226f6b916a3bcdd3b549c75e0abddc55df70951 0 \"" + BLSCT_EXAMPLE_ADDRESS[0] + "\" \"donation\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr const pwallet = wallet::GetWalletForJSONRPCRequest(request); + if (!pwallet) return UniValue::VNULL; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + uint256 token_id(ParseHashV(request.params[0], "token_id")); + CAmount nft_id(AmountFromValue(request.params[1], 0)); + + std::map tokens; + tokens[token_id]; + pwallet->chain().findTokens(tokens); + + if (!tokens.count(token_id)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown token"); + + auto token = tokens[token_id]; + + if (token.info.type != blsct::TokenType::NFT) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Wrong token type"); + + // Wallet comments + std::string sMemo; + if (!request.params[3].isNull() && !request.params[3].get_str().empty()) + sMemo = request.params[3].get_str(); + + const std::string address = request.params[2].get_str(); + + const bool verbose{request.params[4].isNull() ? false : request.params[4].get_bool()}; + + blsct::CreateTransactionData transactionData(address, 1, sMemo, TokenId(token_id, nft_id), blsct::CreateTransactionType::NORMAL, 0); + + EnsureWalletIsUnlocked(*pwallet); + + return blsct::SendTransaction(*pwallet, transactionData, verbose); + }, + }; +} + + RPCHelpMan stakelock() { return RPCHelpMan{ @@ -649,8 +801,10 @@ Span GetBLSCTWalletRPCCommands() {"blsct", &createtoken}, {"blsct", &minttoken}, {"blsct", &mintnft}, + {"blsct", &getnftbalance}, {"blsct", &gettokenbalance}, {"blsct", &sendtoblsctaddress}, + {"blsct", &sendnfttoblsctaddress}, {"blsct", &sendtokentoblsctaddress}, {"blsct", &stakelock}, {"blsct", &stakeunlock}, diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp index f207b94cf703f..c9002e0890b34 100644 --- a/src/blsct/wallet/txfactory.cpp +++ b/src/blsct/wallet/txfactory.cpp @@ -341,7 +341,9 @@ void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* bl auto out = tx->tx->vout[output.outpoint.n]; auto recoveredInfo = tx->GetBLSCTRecoveryData(output.outpoint.n); - inputCandidates.push_back({recoveredInfo.amount, recoveredInfo.gamma, blsct_km->GetSpendingKeyForOutput(out), out.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n), out.IsStakedCommitment()}); + auto value = out.HasBLSCTRangeProof() ? recoveredInfo.amount : out.nValue; + + inputCandidates.push_back({value, recoveredInfo.gamma, blsct_km->GetSpendingKeyForOutput(out), out.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n), out.IsStakedCommitment()}); } } diff --git a/src/blsct/wallet/verification.cpp b/src/blsct/wallet/verification.cpp index 7fcbdef9723f2..201aa2506841a 100644 --- a/src/blsct/wallet/verification.cpp +++ b/src/blsct/wallet/verification.cpp @@ -42,7 +42,12 @@ bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& auto in_hash = in.GetHash(); vMessages.emplace_back(in_hash.begin(), in_hash.end()); - balanceKey = balanceKey + coin.out.blsctData.rangeProof.Vs[0]; + if (coin.out.HasBLSCTRangeProof()) + balanceKey = balanceKey + coin.out.blsctData.rangeProof.Vs[0]; + else { + range_proof::Generators gen = gf.GetInstance(coin.out.tokenId); + balanceKey = balanceKey + (gen.G * MclScalar(coin.out.nValue)); + } } } @@ -97,15 +102,13 @@ bool VerifyTx(const CTransaction& tx, CCoinsViewCache& view, TxValidationState& vProofs.push_back(proof); } } else { - if (!out.scriptPubKey.IsUnspendable() && out.nValue > 0) { - return state.Invalid(TxValidationResult::TX_CONSENSUS, "spendable-output-with-public-value"); - } - if (nFee > 0 || !MoneyRange(out.nValue)) { - return state.Invalid(TxValidationResult::TX_CONSENSUS, "more-than-one-fee-output"); - } if (out.nValue == 0) continue; - if (parsedPredicate.IsPayFeePredicate()) + if (parsedPredicate.IsPayFeePredicate()) { + if (nFee > 0 || !MoneyRange(out.nValue)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "more-than-one-fee-output"); + } nFee = out.nValue; + } range_proof::Generators gen = gf.GetInstance(out.tokenId); balanceKey = balanceKey - (gen.G * MclScalar(out.nValue)); } diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 1808f1e897a26..06d2ddaa8b1ae 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -66,10 +66,12 @@ CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, TokenId tokenIdI std::string CTxOut::ToString() const { - return strprintf("CTxOut(scriptPubKey=%s%s%s%s%s)", HexStr(scriptPubKey).substr(0, 30), + return strprintf("CTxOut(scriptPubKey=%s%s%s%s%s)", + HexStr(scriptPubKey).substr(0, 30), HasBLSCTKeys() ? strprintf(", spendingKey=%s, blindingKey=%s, ephemeralKey=%s", HexStr(blsctData.spendingKey.GetVch()), HexStr(blsctData.blindingKey.GetVch()), HexStr(blsctData.ephemeralKey.GetVch())) : "", tokenId.IsNull() ? "" : strprintf(", tokenId=%s", tokenId.ToString()), - HasBLSCTRangeProof() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue)), predicate.size() > 0 ? strprintf(", predicate=%s", HexStr(predicate)) : ""); + HasBLSCTRangeProof() ? "" : strprintf(", nAmount=%s", FormatMoney(nValue)), + predicate.size() > 0 ? strprintf(", predicate=%s", HexStr(predicate)) : ""); } uint256 CTxOut::GetHash() const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index d2e82417377c1..0a8f7023680c4 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -223,6 +223,7 @@ class CTxOut static const uint32_t BLSCT_MARKER = 0x1 << 0; static const uint32_t TOKEN_MARKER = 0x1 << 1; static const uint32_t PREDICATE_MARKER = 0x1 << 2; + static const uint32_t TRANSPARENT_VALUE_MARKER = 0x1 << 3; CAmount nValue; CScript scriptPubKey; @@ -242,16 +243,18 @@ class CTxOut { uint64_t nFlags = 0; - if (blsctData.rangeProof.Vs.Size() > 0) + if (blsctData.rangeProof.Vs.Size() > 0 || HasBLSCTKeys()) nFlags |= BLSCT_MARKER; if (!tokenId.IsNull()) nFlags |= TOKEN_MARKER; + if ((tokenId.IsNFT() || predicate.size() > 0) && nValue > 0) + nFlags |= TRANSPARENT_VALUE_MARKER; if (predicate.size() > 0) nFlags |= PREDICATE_MARKER; if (nFlags != 0) { ::Serialize(s, std::numeric_limits::max()); ::Serialize(s, nFlags); - if (nFlags & PREDICATE_MARKER) + if (nFlags & TRANSPARENT_VALUE_MARKER) ::Serialize(s, nValue); } else { ::Serialize(s, nValue); @@ -274,7 +277,7 @@ class CTxOut if (nValue == std::numeric_limits::max()) { nValue = 0; ::Unserialize(s, nFlags); - if (nFlags & PREDICATE_MARKER) + if (nFlags & TRANSPARENT_VALUE_MARKER) ::Unserialize(s, nValue); } ::Unserialize(s, scriptPubKey); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 891b8808e9fe8..db22809fc89d8 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -44,6 +44,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getnetworkhashps", 1, "height" }, { "sendtoblsctaddress", 1, "amount" }, { "sendtoblsctaddress", 3, "verbose" }, + { "sendnfttoblsctaddress", 1, "nft_id" }, + { "sendnfttoblsctaddress", 4, "verbose" }, { "sendtokentoblsctaddress", 2, "amount" }, { "sendtokentoblsctaddress", 4, "verbose" }, { "sendtoaddress", 1, "amount" }, @@ -74,6 +76,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getbalance", 1, "minconf" }, { "getbalance", 2, "include_watchonly" }, { "getbalance", 3, "avoid_reuse" }, + { "getnftbalance", 2, "minconf" }, + { "getnftbalance", 3, "include_watchonly" }, + { "getnftbalance", 4, "avoid_reuse" }, { "gettokenbalance", 2, "minconf" }, { "gettokenbalance", 3, "include_watchonly" }, { "gettokenbalance", 4, "avoid_reuse" }, diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 9d345a8cb707f..53e06c7708b35 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -384,10 +384,8 @@ CoinsResult AvailableCoins(const CWallet& wallet, for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut& output = wtx.tx->vout[i]; const COutPoint outpoint(Txid::FromUint256(txid), i); - auto nValue = output.HasBLSCTRangeProof() ? wtx.GetBLSCTRecoveryData(i).amount : output.nValue; - if (nValue < params.min_amount || nValue > params.max_amount) - continue; + if (nValue < params.min_amount || nValue > params.max_amount) continue; // Skip manually selected coins (the caller can fetch them directly) if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint)) @@ -415,7 +413,6 @@ CoinsResult AvailableCoins(const CWallet& wallet, if (params.token_id != output.tokenId) { continue; } - if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) { continue; } @@ -428,7 +425,6 @@ CoinsResult AvailableCoins(const CWallet& wallet, // Filter by spendable outputs only if (!spendable && params.only_spendable) continue; - // Obtain script type std::vector> script_solutions; TxoutType type = Solver(output.scriptPubKey, script_solutions); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7ab8a70937d2c..6edfbd223f567 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1155,17 +1155,13 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const } } - for (auto& txout : wtx.tx->vout) { - if (txout.HasBLSCTRangeProof()) { - auto blsct_man = GetBLSCTKeyMan(); - if (blsct_man) { - auto result = blsct_man->RecoverOutputs({wtx.tx->vout}); - if (result.is_completed) { - auto xs = result.amounts; - for (auto& res : xs) { - wtx.blsctRecoveryData[res.id] = res; - } - } + auto blsct_man = GetBLSCTKeyMan(); + if (blsct_man) { + auto result = blsct_man->RecoverOutputs({wtx.tx->vout}); + if (result.is_completed) { + auto xs = result.amounts; + for (auto& res : xs) { + wtx.blsctRecoveryData[res.id] = res; } } } @@ -1613,7 +1609,7 @@ CAmount CWallet::GetDebit(const CTxIn& txin, const isminefilter& filter, const T isminetype CWallet::IsMine(const CTxOut& txout) const { AssertLockHeld(cs_wallet); - if (txout.HasBLSCTRangeProof()) { + if (txout.HasBLSCTKeys()) { auto blsct_man = GetBLSCTKeyMan(); if (blsct_man) { bool mine = blsct_man->IsMine(txout); diff --git a/test/functional/blsct_nft.py b/test/functional/blsct_nft.py new file mode 100755 index 0000000000000..4c5c39e769edd --- /dev/null +++ b/test/functional/blsct_nft.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 The Navio Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +class NavioBlsctNftTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser, blsct=True) + + def set_test_params(self): + # Set up two nodes for the test + self.num_nodes = 2 + self.chain = 'blsctregtest' + self.setup_clean_chain = True + + def run_test(self): + self.log.info("Creating wallet1 with BLSCT") + + # Create a new wallet + + #self.init_wallet(node=0, blsct=True) + self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) + self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) + wallet_info = self.nodes[0].get_wallet_rpc("wallet1") + wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") + + self.log.info("Loading wallet1") + + # Ensure wallet is loaded + wallets = self.nodes[0].listwallets() + assert "wallet1" in wallets, "wallet1 was not loaded successfully" + + self.log.info("Generating BLSCT address") + + # Generate a BLSCT address + blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") + blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") + + self.log.info(f"BLSCT address NODE 1: {blsct_address}") + self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") + + # Generate blocks and fund the BLSCT address + self.log.info("Generating 101 blocks to the BLSCT address") + block_hashes = self.generatetoblsctaddress(self.nodes[0], 101, blsct_address) + + self.log.info(f"Generated blocks: {len(block_hashes)}") + + # Check the balance of the wallet + balance = wallet_info.getbalance() + self.log.info(f"Balance in wallet1: {balance}") + + assert_equal(len(block_hashes), 101) + assert balance > 0, "Balance should be greater than zero after mining" + + self.log.info("Creating NFT collection and mining 1 block") + token = self.nodes[0].createnft({"name": "Test"}, 1000) + block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) + + tokens = self.nodes[0].listtokens() + assert len(tokens) == 1, "length of tokens is not 1" + + self.log.info(f"Created token: {token["tokenId"]}") + + assert tokens[0]['type'] == 'nft', "token type is not token" + assert tokens[0]['metadata'] == {'name': 'Test'}, "incorrect metadata" + assert tokens[0]['maxSupply'] == 1000, "incorrect max supply" + assert tokens[0]['mintedNft'] == {}, "incorrect current supply" + + self.nodes[0].mintnft(token['tokenId'], 1, blsct_address, {"id": "null"}) + block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) + + tokenInfo = self.nodes[0].gettoken(token['tokenId']) + + assert tokenInfo['type'] == 'nft', "token type is not token" + assert tokenInfo['metadata'] == {'name': 'Test'}, "incorrect metadata" + assert tokenInfo['maxSupply'] == 1000, "incorrect max supply" + assert tokenInfo['mintedNft'] == {'1': {'id': 'null'}}, "incorrect current supply" + + self.log.info(f"Minted 1 NFT") + + nft_balance = self.nodes[0].getnftbalance(token['tokenId']) + nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) + + self.log.info(f"Balance in NDOE 1: {nft_balance}") + self.log.info(f"Balance in NODE 2: {nft_balance_2}") + + assert nft_balance == {'1': {'id': 'null'}}, "incorrect nft balance in node 1" + assert nft_balance_2 == {}, "incorrect nft balance in node 2" + + self.log.info(f"Sending NFT with id #1 to NODE 2") + + self.nodes[0].sendnfttoblsctaddress(token['tokenId'], 1, blsct_address_2) + self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) + + nft_balance = self.nodes[0].getnftbalance(token['tokenId']) + nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) + + self.log.info(f"Balance in NDOE 1: {nft_balance}") + self.log.info(f"Balance in NODE 2: {nft_balance_2}") + + assert nft_balance_2 == {'1': {'id': 'null'}}, "incorrect nft balance in node 2" + assert nft_balance == {}, "incorrect nft balance in node" + + +if __name__ == '__main__': + NavioBlsctNftTest().main() \ No newline at end of file diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a84f8c4fbe9bf..866e3ca4dea47 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -396,7 +396,8 @@ 'p2p_dandelionpp_loop.py', 'p2p_dandelionpp_mempool_leak.py', 'p2p_dandelionpp_probing.py', - 'blsct_token.py' + 'blsct_token.py', + 'blsct_nft.py' # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] From 4c966800341af5b076de1873becd4201a01fc518 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 16:32:58 +0100 Subject: [PATCH 12/27] fix tests --- test/functional/rpc_help.py | 2 +- test/functional/test_runner.py | 2 +- test/functional/wallet_reindex.py | 12 +----------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index 53c5aa05e5e8b..10f790915c939 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -103,7 +103,7 @@ def test_categories(self): # command titles titles = [line[3:-3] for line in node.help().splitlines() if line.startswith('==')] - components = ['Blockchain', 'Control', 'Mining', 'Network', 'Rawtransactions', 'Util'] + components = ['Blockchain', 'Control', 'Mining', 'Network', 'Rawtransactions', 'Util', 'Blsct'] if self.is_wallet_compiled(): components.append('Wallet') diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 866e3ca4dea47..bf7ba7a5b4ca6 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -802,7 +802,7 @@ def was_successful(self): def check_script_prefixes(): """Check that test scripts start with one of the allowed name prefixes.""" - good_prefixes_re = re.compile("^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_") + good_prefixes_re = re.compile("^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool|blsct)_") bad_script_names = [script for script in ALL_SCRIPTS if good_prefixes_re.match(script) is None] if bad_script_names: diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py index 5388de4b7171f..0d45c89151689 100755 --- a/test/functional/wallet_reindex.py +++ b/test/functional/wallet_reindex.py @@ -47,7 +47,7 @@ def birthtime_test(self, node, miner_wallet): node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True) wallet_watch_only = node.get_wallet_rpc('watch_only') # Blank wallets don't have a birth time - assert 'birthtime' not in wallet_watch_only.getwalletinfo() + # assert 'birthtime' not in wallet_watch_only.getwalletinfo() # For a descriptors wallet: Import address with timestamp=now. # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1. @@ -55,16 +55,6 @@ def birthtime_test(self, node, miner_wallet): wallet_watch_only.importaddress(wallet_addr, rescan=False) assert_equal(len(wallet_watch_only.listtransactions()), 0) - # Depending on the wallet type, the birth time changes. - wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime'] - if self.options.descriptors: - # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min. - assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime) - else: - # No way of importing scripts/addresses with a custom time on a legacy wallet. - # It's always set to the beginning of time. - assert_equal(wallet_birthtime, 1) - # Rescan the wallet to detect the missing transaction wallet_watch_only.rescanblockchain() assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50) From c66949ac15ec7ba8622cbca424961cab80d9ca7d Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 17:40:58 +0100 Subject: [PATCH 13/27] fix tests --- src/blsct/arith/elements.cpp | 1 - src/blsct/range_proof/bulletproofs_plus/range_proof.h | 8 ++++++++ src/blsct/range_proof/proof_base.h | 5 +++++ src/blsct/wallet/txfactory_global.cpp | 2 +- src/primitives/transaction.h | 5 +++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/blsct/arith/elements.cpp b/src/blsct/arith/elements.cpp index c429872dd997b..070fd6d44734a 100644 --- a/src/blsct/arith/elements.cpp +++ b/src/blsct/arith/elements.cpp @@ -200,7 +200,6 @@ template void Elements::ConfirmIndexInsideRange(const uint32_t& index) const { if (index >= m_vec.size()) { - assert(0); auto s = strprintf("index %d is out of range [0..%d]", index, m_vec.size() - 1ul); throw std::runtime_error(s); } diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.h b/src/blsct/range_proof/bulletproofs_plus/range_proof.h index 4422c7f6bcda5..3538004728771 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.h @@ -105,6 +105,14 @@ struct RangeProofWithSeed : public RangeProof { typename T::Scalar min_value; }; +template +struct RangeProofUncompressed { + FORMATTER_METHODS(RangeProof, obj) + { + READWRITE(Using>(obj), obj.A, obj.A_wip, obj.B, obj.r_prime, obj.s_prime, obj.delta_prime, obj.alpha_hat, obj.tau_x); + } +}; + } // namespace bulletproofs_plus #endif // NAVIO_BLSCT_RANGE_PROOF_BULLETPROOFS_PLUS_RANGE_PROOF_H diff --git a/src/blsct/range_proof/proof_base.h b/src/blsct/range_proof/proof_base.h index fb1d0d119a4c8..1563780b06dfe 100644 --- a/src/blsct/range_proof/proof_base.h +++ b/src/blsct/range_proof/proof_base.h @@ -50,6 +50,11 @@ struct ProofBase { } }; +template +struct ProofBaseUncompressed { + FORMATTER_METHODS(ProofBase, obj) { READWRITE(obj.Vs, obj.Ls, obj.Rs); } +}; + } // namespace range_proof #endif // NAVIO_BLSCT_RANGE_PROOF_PROOF_BASE_H diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index 98b83f5e37eca..1b0f05d94278e 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -120,7 +120,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun stakeRp.Vs.Clear(); DataStream ss{}; - ss << stakeRp; + ss << Using>(stakeRp); ret.out.scriptPubKey = CScript() << OP_STAKED_COMMITMENT << blsct::Common::DataStreamToVector(ss) << OP_DROP << OP_TRUE; } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 0a8f7023680c4..e4df4bdd72d39 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -324,12 +324,17 @@ class CTxOut { if (!HasBLSCTRangeProof()) return false; + if (scriptPubKey.size() <= 7) return false; + if (blsctData.rangeProof.Vs.Size() == 0) return false; + if (!tokenId.IsNull()) return false; + if (!(*(scriptPubKey.begin()) == OP_STAKED_COMMITMENT && *(scriptPubKey.begin() + 1) == OP_PUSHDATA2 && *(scriptPubKey.end() - 1) == OP_TRUE)) return false; + try { auto commitment = std::vector(scriptPubKey.begin() + 4, scriptPubKey.end()); From 60fbeef55f240154af547d83e6cffd4eb83b037e Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 19:20:01 +0100 Subject: [PATCH 14/27] fixes merge --- .../bulletproofs_plus/range_proof.h | 4 +- src/blsct/range_proof/proof_base.h | 4 +- src/blsct/wallet/txfactory.cpp | 297 ------------------ src/blsct/wallet/txfactory.h | 94 ------ src/blsct/wallet/txfactory_base.cpp | 157 ++++++--- src/blsct/wallet/txfactory_base.h | 79 ++++- src/blsct/wallet/txfactory_global.cpp | 2 +- src/primitives/transaction.h | 2 +- 8 files changed, 190 insertions(+), 449 deletions(-) diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.h b/src/blsct/range_proof/bulletproofs_plus/range_proof.h index 3538004728771..78a5ce2d59af5 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.h @@ -106,10 +106,10 @@ struct RangeProofWithSeed : public RangeProof { }; template -struct RangeProofUncompressed { +struct RangeProofWithoutVs { FORMATTER_METHODS(RangeProof, obj) { - READWRITE(Using>(obj), obj.A, obj.A_wip, obj.B, obj.r_prime, obj.s_prime, obj.delta_prime, obj.alpha_hat, obj.tau_x); + READWRITE(Using>(obj), obj.A, obj.A_wip, obj.B, obj.r_prime, obj.s_prime, obj.delta_prime, obj.alpha_hat, obj.tau_x); } }; diff --git a/src/blsct/range_proof/proof_base.h b/src/blsct/range_proof/proof_base.h index 1563780b06dfe..6be8792b96dfd 100644 --- a/src/blsct/range_proof/proof_base.h +++ b/src/blsct/range_proof/proof_base.h @@ -51,8 +51,8 @@ struct ProofBase { }; template -struct ProofBaseUncompressed { - FORMATTER_METHODS(ProofBase, obj) { READWRITE(obj.Vs, obj.Ls, obj.Rs); } +struct ProofBaseWithoutVs { + FORMATTER_METHODS(ProofBase, obj) { READWRITE(obj.Ls, obj.Rs); } }; } // namespace range_proof diff --git a/src/blsct/wallet/txfactory.cpp b/src/blsct/wallet/txfactory.cpp index 1e4378b0ba7ca..a4a027180045c 100644 --- a/src/blsct/wallet/txfactory.cpp +++ b/src/blsct/wallet/txfactory.cpp @@ -13,264 +13,6 @@ using Scalars = Elements; namespace blsct { -void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake, const bool& fSubtractFeeFromAmount) -{ - UnsignedOutput out; - - out = CreateOutput(destination.GetKeys(), nAmount, sMemo, token_id, Scalar::Rand(), type, minStake); - - CAmount nFee = 0; - - if (fSubtractFeeFromAmount) { - nFee = GetTransactioOutputWeight(out.out) * BLSCT_DEFAULT_FEE; - out = CreateOutput(destination.GetKeys(), nAmount - nFee, sMemo, token_id, Scalar::Rand(), type, minStake); - }; - - if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0, 0}; - - nAmounts[token_id].nFromOutputs += nAmount - nFee; - - if (vOutputs.count(token_id) == 0) - vOutputs[token_id] = std::vector(); - - vOutputs[token_id].push_back(out); -} - -// Create token -void TxFactoryBase::AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo) -{ - UnsignedOutput out; - - out = CreateOutput(tokenKey, tokenInfo); - - TokenId token_id{tokenInfo.publicKey.GetHash()}; - - if (vOutputs.count(token_id) == 0) - vOutputs[token_id] = std::vector(); - - vOutputs[token_id].push_back(out); -} - -// Mint Token - -void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount) -{ - UnsignedOutput out; - - out = CreateOutput(destination.GetKeys(), mintAmount, Scalar::Rand(), tokenKey, tokenPublicKey); - - TokenId token_id{tokenPublicKey.GetHash()}; - - if (vOutputs.count(token_id) == 0) - vOutputs[token_id] = std::vector(); - - vOutputs[token_id].push_back(out); -} - -// Mint NFT - -void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) -{ - UnsignedOutput out; - - out = CreateOutput(destination.GetKeys(), Scalar::Rand(), tokenKey, tokenPublicKey, nftId, nftMetadata); - - TokenId token_id{tokenPublicKey.GetHash(), nftId}; - - if (vOutputs.count(token_id) == 0) - vOutputs[token_id] = std::vector(); - - vOutputs[token_id].push_back(out); -} - -bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) -{ - if (vInputs.count(token_id) == 0) - vInputs[token_id] = std::vector(); - - vInputs[token_id].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey, stakedCommitment}); - - if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0, 0}; - - nAmounts[token_id].nFromInputs += amount; - - return true; -} - -std::optional -TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake, const CreateTransactionType& type, const bool& fSubtractedFee) -{ - this->tx = CMutableTransaction(); - - std::vector outputSignatures; - Scalar outputGammas; - nAmounts[TokenId()].nFromFee = 0; - - for (auto& out_ : vOutputs) { - for (auto& out : out_.second) { - this->tx.vout.push_back(out.out); - auto outHash = out.out.GetHash(); - - if (out.out.HasBLSCTRangeProof()) { - outputGammas = outputGammas - out.gamma; - } - if (out.out.HasBLSCTKeys()) { - outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(outHash)); - } - - if (out.type == TX_CREATE_TOKEN || out.type == TX_MINT_TOKEN) { - outputSignatures.push_back(PrivateKey(out.tokenKey).Sign(outHash)); - } - } - } - - while (true) { - CMutableTransaction tx = this->tx; - tx.nVersion |= CTransaction::BLSCT_MARKER; - - Scalar gammaAcc = outputGammas; - std::map mapChange; - std::map mapInputs; - std::vector txSigs = outputSignatures; - - if (type == STAKED_COMMITMENT_UNSTAKE || type == STAKED_COMMITMENT) { - for (auto& in_ : vInputs) { - for (auto& in : in_.second) { - if (!in.is_staked_commitment) continue; - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs) break; - - tx.vin.push_back(in.in); - gammaAcc = gammaAcc + in.gamma; - txSigs.push_back(in.sk.Sign(in.in.GetHash())); - - mapInputs[in_.first] += in.value.GetUint64(); - } - } - } - - for (auto& in_ : vInputs) { - for (auto& in : in_.second) { - if (in.is_staked_commitment) continue; - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nAmounts[in_.first].nFromFee) break; - - tx.vin.push_back(in.in); - gammaAcc = gammaAcc + in.gamma; - txSigs.push_back(in.sk.Sign(in.in.GetHash())); - mapInputs[in_.first] += in.value.GetUint64(); - } - } - - for (auto& amounts : nAmounts) { - auto tokenFee = nAmounts[amounts.first].nFromFee; - - auto nFromInputs = mapInputs[amounts.first]; - - if (nFromInputs < amounts.second.nFromOutputs + tokenFee) return std::nullopt; - - mapChange[amounts.first] = nFromInputs - amounts.second.nFromOutputs - tokenFee; - } - - for (auto& change : mapChange) { - if (change.second == 0) continue; - - auto changeOutput = CreateOutput(changeDestination, change.second, "Change", change.first, MclScalar::Rand(), NORMAL, minStake); - - gammaAcc = gammaAcc - changeOutput.gamma; - - tx.vout.push_back(changeOutput.out); - txSigs.push_back(PrivateKey(changeOutput.blindingKey).Sign(changeOutput.out.GetHash())); - } - - if (nAmounts[TokenId()].nFromFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { - CTxOut fee_out{nAmounts[TokenId()].nFromFee, CScript(OP_RETURN)}; - - auto feeKey = blsct::PrivateKey(MclScalar::Rand()); - fee_out.predicate = blsct::PayFeePredicate(feeKey.GetPublicKey()).GetVch(); - - tx.vout.push_back(fee_out); - txSigs.push_back(PrivateKey(gammaAcc).SignBalance()); - txSigs.push_back(PrivateKey(feeKey).SignFee()); - - tx.txSig = Signature::Aggregate(txSigs); - - return tx; - } - - nAmounts[TokenId()].nFromFee = GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE; - } - - return std::nullopt; -} - -std::optional TxFactoryBase::CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData) -{ - auto tx = blsct::TxFactoryBase(); - - if (transactionData.type == STAKED_COMMITMENT) { - CAmount inputFromStakedCommitments = 0; - - for (const auto& output : inputCandidates) { - if (output.is_staked_commitment) - inputFromStakedCommitments += output.amount; - - tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); - } - - if (transactionData.nAmount + inputFromStakedCommitments < transactionData.minStake) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); - } - - bool fSubtractFeeFromAmount = false; // nAmount == inAmount + inputFromStakedCommitments; - - tx.AddOutput(transactionData.destination, transactionData.nAmount + inputFromStakedCommitments, transactionData.sMemo, transactionData.token_id, transactionData.type, transactionData.minStake, fSubtractFeeFromAmount); - } else { - CAmount inputFromStakedCommitments = 0; - - for (const auto& output : inputCandidates) { - if (output.is_staked_commitment) { - if (!(transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE || transactionData.type == CreateTransactionType::STAKED_COMMITMENT)) - continue; - inputFromStakedCommitments += output.amount; - } - - tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); - } - - if (transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { - if (inputFromStakedCommitments - transactionData.nAmount < 0) { - throw std::runtime_error(strprintf("Not enough staked coins")); - } else if (inputFromStakedCommitments - transactionData.nAmount < transactionData.minStake && inputFromStakedCommitments - transactionData.nAmount > 0) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); - } - - if (inputFromStakedCommitments - transactionData.nAmount > 0) { - // CHANGE - tx.AddOutput(transactionData.destination, inputFromStakedCommitments - transactionData.nAmount, transactionData.sMemo, transactionData.token_id, CreateTransactionType::STAKED_COMMITMENT, transactionData.minStake, false); - } - } - - //bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; - - if (transactionData.type == TX_CREATE_TOKEN) { - tx.AddOutput(transactionData.tokenKey, transactionData.tokenInfo); - } else if (transactionData.type == TX_MINT_TOKEN) { - if (!transactionData.token_id.IsNFT()) { - tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.nAmount); - } else { - tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.token_id.subid, transactionData.nftMetadata); - } - } else if (transactionData.type == NORMAL) { - tx.AddOutput(transactionData.destination, transactionData.nAmount, transactionData.sMemo, transactionData.token_id, transactionData.type); - } - } - - return tx.BuildTx(transactionData.changeDestination, transactionData.minStake, transactionData.type); -} - bool TxFactory::AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) { Coin coin; @@ -328,45 +70,6 @@ TxFactory::BuildTx() return TxFactoryBase::BuildTx(std::get(km->GetNewDestination(-1).value())); } -void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector& inputCandidates) -{ - AssertLockHeld(wallet->cs_wallet); - for (const wallet::COutput& output : AvailableCoins(*wallet, nullptr, std::nullopt, coins_params).All()) { - auto tx = wallet->GetWalletTx(output.outpoint.hash); - - if (tx == nullptr) - continue; - - auto out = tx->tx->vout[output.outpoint.n]; - - auto recoveredInfo = tx->GetBLSCTRecoveryData(output.outpoint.n); - auto value = out.HasBLSCTRangeProof() ? recoveredInfo.amount : out.nValue; - - inputCandidates.push_back({value, recoveredInfo.gamma, blsct_km->GetSpendingKeyForOutput(out), out.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n), out.IsStakedCommitment()}); - } -} - -void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const TokenId& token_id, const CreateTransactionType& type, std::vector& inputCandidates) -{ - AssertLockHeld(wallet->cs_wallet); - - wallet::CoinFilterParams coins_params; - coins_params.min_amount = 0; - coins_params.only_blsct = true; - coins_params.token_id = token_id; - - AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); - - if (type == CreateTransactionType::STAKED_COMMITMENT || type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { - coins_params.include_staked_commitment = true; - AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); - } - - if ((type == CreateTransactionType::NORMAL && !token_id.IsNull()) || type == CreateTransactionType::TX_MINT_TOKEN) { - coins_params.token_id.SetNull(); - AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); - } -} std::optional TxFactory::CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, CreateTransactionData transactionData) { diff --git a/src/blsct/wallet/txfactory.h b/src/blsct/wallet/txfactory.h index 2b5604e29fa1a..9a9232d6ce750 100644 --- a/src/blsct/wallet/txfactory.h +++ b/src/blsct/wallet/txfactory.h @@ -19,100 +19,6 @@ namespace blsct { -struct CreateTransactionData { - CreateTransactionType type; - blsct::TokenInfo tokenInfo; - blsct::DoublePublicKey changeDestination; - SubAddress destination; - CAmount nAmount; - std::string sMemo; - TokenId token_id; - CAmount minStake; - - Scalar tokenKey; - std::map nftMetadata; - - CreateTransactionData(const blsct::DoublePublicKey& changeDestination, - const SubAddress& destination, - const CAmount& nAmount, - const std::string& sMemo, - const TokenId& token_id, - const CreateTransactionType& type, - const CAmount& minStake) : type(type), - changeDestination(changeDestination), - destination(destination), - nAmount(nAmount), - sMemo(sMemo), - token_id(token_id), - minStake(minStake) - { - } - - CreateTransactionData(const SubAddress& destination, - const CAmount& nAmount, - const std::string& sMemo, - const TokenId& token_id, - const CreateTransactionType& type, - const CAmount& minStake) : type(type), - destination(destination), - nAmount(nAmount), - sMemo(sMemo), - token_id(token_id), - minStake(minStake) {} - - - CreateTransactionData(const SubAddress& destination, - const CAmount& nAmount, - const std::string& sMemo) : type(NORMAL), - destination(destination), - nAmount(nAmount), - sMemo(sMemo) {} - - CreateTransactionData(const blsct::TokenInfo& tokenInfo) : type(TX_CREATE_TOKEN), tokenInfo(tokenInfo) {} - - CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& mintAmount, const SubAddress& destination) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), nAmount(mintAmount), token_id(TokenId(tokenInfo.publicKey.GetHash())) {} - - CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& nftId, const SubAddress& destination, const std::map& nftMetadata) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), token_id(TokenId(tokenInfo.publicKey.GetHash(), nftId)), nftMetadata(nftMetadata) {} -}; - -struct InputCandidates { - CAmount amount; - MclScalar gamma; - blsct::PrivateKey spendingKey; - TokenId token_id; - COutPoint outpoint; - bool is_staked_commitment; -}; - -class TxFactoryBase -{ -protected: - CMutableTransaction tx; - std::map> - vOutputs; - std::map> - vInputs; - std::map - nAmounts; - -public: - TxFactoryBase(){}; - - // Normal transfer - void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0, const bool& fSubtractFeeFromAmount = false); - // Create Token - void AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); - // Mint Token - void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount); - // Mint NFT - void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); - bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false); - std::optional BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false); - static std::optional CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData); - static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); - static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const TokenId& token_id, const CreateTransactionType& type, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); -}; - class TxFactory : public TxFactoryBase { private: diff --git a/src/blsct/wallet/txfactory_base.cpp b/src/blsct/wallet/txfactory_base.cpp index 952a57ce2e4b8..60c64af301902 100644 --- a/src/blsct/wallet/txfactory_base.cpp +++ b/src/blsct/wallet/txfactory_base.cpp @@ -27,7 +27,7 @@ void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmo }; if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0}; + nAmounts[token_id] = {0, 0, 0}; nAmounts[token_id].nFromOutputs += nAmount - nFee; @@ -37,19 +37,51 @@ void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmo vOutputs[token_id].push_back(out); } -bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) +// Create token +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo) { - if (vInputs.count(token_id) == 0) - vInputs[token_id] = std::vector(); + UnsignedOutput out; - vInputs[token_id].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey, stakedCommitment}); + out = CreateOutput(tokenKey, tokenInfo); - if (nAmounts.count(token_id) == 0) - nAmounts[token_id] = {0, 0}; + TokenId token_id{tokenInfo.publicKey.GetHash()}; - nAmounts[token_id].nFromInputs += amount; + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); - return true; + vOutputs[token_id].push_back(out); +} + +// Mint Token + +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount) +{ + UnsignedOutput out; + + out = CreateOutput(destination.GetKeys(), mintAmount, Scalar::Rand(), tokenKey, tokenPublicKey); + + TokenId token_id{tokenPublicKey.GetHash()}; + + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); + + vOutputs[token_id].push_back(out); +} + +// Mint NFT + +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) +{ + UnsignedOutput out; + + out = CreateOutput(destination.GetKeys(), Scalar::Rand(), tokenKey, tokenPublicKey, nftId, nftMetadata); + + TokenId token_id{tokenPublicKey.GetHash(), nftId}; + + if (vOutputs.count(token_id) == 0) + vOutputs[token_id] = std::vector(); + + vOutputs[token_id].push_back(out); } std::optional @@ -59,14 +91,23 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA std::vector outputSignatures; Scalar outputGammas; - CAmount nFee = 0; - + nAmounts[TokenId()].nFromFee = 0; for (auto& out_ : vOutputs) { for (auto& out : out_.second) { this->tx.vout.push_back(out.out); - outputGammas = outputGammas - out.gamma; - outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(out.out.GetHash())); + auto outHash = out.out.GetHash(); + + if (out.out.HasBLSCTRangeProof()) { + outputGammas = outputGammas - out.gamma; + } + if (out.out.HasBLSCTKeys()) { + outputSignatures.push_back(PrivateKey(out.blindingKey).Sign(outHash)); + } + + if (out.type == TX_CREATE_TOKEN || out.type == TX_MINT_TOKEN) { + outputSignatures.push_back(PrivateKey(out.tokenKey).Sign(outHash)); + } } } @@ -83,16 +124,14 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA for (auto& in_ : vInputs) { for (auto& in : in_.second) { if (!in.is_staked_commitment) continue; + if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; + if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs) break; tx.vin.push_back(in.in); gammaAcc = gammaAcc + in.gamma; txSigs.push_back(in.sk.Sign(in.in.GetHash())); - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - mapInputs[in_.first] += in.value.GetUint64(); - - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nFee) break; } } } @@ -100,21 +139,18 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA for (auto& in_ : vInputs) { for (auto& in : in_.second) { if (in.is_staked_commitment) continue; + if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; + if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nAmounts[in_.first].nFromFee) break; tx.vin.push_back(in.in); gammaAcc = gammaAcc + in.gamma; txSigs.push_back(in.sk.Sign(in.in.GetHash())); - - if (!mapInputs[in_.first]) mapInputs[in_.first] = 0; - mapInputs[in_.first] += in.value.GetUint64(); - - if (mapInputs[in_.first] > nAmounts[in_.first].nFromOutputs + nFee) break; } } for (auto& amounts : nAmounts) { - auto tokenFee = (amounts.first == TokenId() ? nFee : 0); + auto tokenFee = nAmounts[amounts.first].nFromFee; auto nFromInputs = mapInputs[amounts.first]; @@ -134,27 +170,47 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA txSigs.push_back(PrivateKey(changeOutput.blindingKey).Sign(changeOutput.out.GetHash())); } - if (nFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { - CTxOut fee_out{nFee, CScript(OP_RETURN)}; + if (nAmounts[TokenId()].nFromFee == GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE) { + CTxOut fee_out{nAmounts[TokenId()].nFromFee, CScript(OP_RETURN)}; + + auto feeKey = blsct::PrivateKey(MclScalar::Rand()); + fee_out.predicate = blsct::PayFeePredicate(feeKey.GetPublicKey()).GetVch(); tx.vout.push_back(fee_out); txSigs.push_back(PrivateKey(gammaAcc).SignBalance()); + txSigs.push_back(PrivateKey(feeKey).SignFee()); + tx.txSig = Signature::Aggregate(txSigs); return tx; } - nFee = GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE; + nAmounts[TokenId()].nFromFee = GetTransactionWeight(CTransaction(tx)) * BLSCT_DEFAULT_FEE; } return std::nullopt; } -std::optional TxFactoryBase::CreateTransaction(const std::vector& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake) +bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment, const bool& rbf) +{ + if (vInputs.count(token_id) == 0) + vInputs[token_id] = std::vector(); + + vInputs[token_id].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey, stakedCommitment}); + + if (nAmounts.count(token_id) == 0) + nAmounts[token_id] = {0, 0, 0}; + + nAmounts[token_id].nFromInputs += amount; + + return true; +} + +std::optional TxFactoryBase::CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData) { auto tx = blsct::TxFactoryBase(); - if (type == STAKED_COMMITMENT) { + if (transactionData.type == STAKED_COMMITMENT) { CAmount inputFromStakedCommitments = 0; for (const auto& output : inputCandidates) { @@ -164,19 +220,19 @@ std::optional TxFactoryBase::CreateTransaction(const std::v tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); } - if (nAmount + inputFromStakedCommitments < minStake) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(minStake))); + if (transactionData.nAmount + inputFromStakedCommitments < transactionData.minStake) { + throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); } bool fSubtractFeeFromAmount = false; // nAmount == inAmount + inputFromStakedCommitments; - tx.AddOutput(destination, nAmount + inputFromStakedCommitments, sMemo, token_id, type, minStake, fSubtractFeeFromAmount); + tx.AddOutput(transactionData.destination, transactionData.nAmount + inputFromStakedCommitments, transactionData.sMemo, transactionData.token_id, transactionData.type, transactionData.minStake, fSubtractFeeFromAmount); } else { CAmount inputFromStakedCommitments = 0; for (const auto& output : inputCandidates) { if (output.is_staked_commitment) { - if (!(type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE || type == CreateTransactionType::STAKED_COMMITMENT)) + if (!(transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE || transactionData.type == CreateTransactionType::STAKED_COMMITMENT)) continue; inputFromStakedCommitments += output.amount; } @@ -184,25 +240,35 @@ std::optional TxFactoryBase::CreateTransaction(const std::v tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n), output.is_staked_commitment); } - if (type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { - if (inputFromStakedCommitments - nAmount < 0) { + if (transactionData.type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE) { + if (inputFromStakedCommitments - transactionData.nAmount < 0) { throw std::runtime_error(strprintf("Not enough staked coins")); - } else if (inputFromStakedCommitments - nAmount < minStake && inputFromStakedCommitments - nAmount > 0) { - throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(minStake))); + } else if (inputFromStakedCommitments - transactionData.nAmount < transactionData.minStake && inputFromStakedCommitments - transactionData.nAmount > 0) { + throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(transactionData.minStake))); } - if (inputFromStakedCommitments - nAmount > 0) { + if (inputFromStakedCommitments - transactionData.nAmount > 0) { // CHANGE - tx.AddOutput(destination, inputFromStakedCommitments - nAmount, sMemo, token_id, CreateTransactionType::STAKED_COMMITMENT, minStake, false); + tx.AddOutput(transactionData.destination, inputFromStakedCommitments - transactionData.nAmount, transactionData.sMemo, transactionData.token_id, CreateTransactionType::STAKED_COMMITMENT, transactionData.minStake, false); } } - bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; + // bool fSubtractFeeFromAmount = false; // type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE; - tx.AddOutput(destination, nAmount, sMemo, token_id, type, minStake, fSubtractFeeFromAmount); + if (transactionData.type == TX_CREATE_TOKEN) { + tx.AddOutput(transactionData.tokenKey, transactionData.tokenInfo); + } else if (transactionData.type == TX_MINT_TOKEN) { + if (!transactionData.token_id.IsNFT()) { + tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.nAmount); + } else { + tx.AddOutput(transactionData.tokenKey, transactionData.destination, transactionData.tokenInfo.publicKey, transactionData.token_id.subid, transactionData.nftMetadata); + } + } else if (transactionData.type == NORMAL) { + tx.AddOutput(transactionData.destination, transactionData.nAmount, transactionData.sMemo, transactionData.token_id, transactionData.type); + } } - return tx.BuildTx(changeDestination, minStake, type); + return tx.BuildTx(transactionData.changeDestination, transactionData.minStake, transactionData.type); } void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector& inputCandidates) @@ -217,7 +283,9 @@ void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* bl auto out = tx->tx->vout[output.outpoint.n]; auto recoveredInfo = tx->GetBLSCTRecoveryData(output.outpoint.n); - inputCandidates.push_back({recoveredInfo.amount, recoveredInfo.gamma, blsct_km->GetSpendingKeyForOutput(out), out.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n), out.IsStakedCommitment()}); + auto value = out.HasBLSCTRangeProof() ? recoveredInfo.amount : out.nValue; + + inputCandidates.push_back({value, recoveredInfo.gamma, blsct_km->GetSpendingKeyForOutput(out), out.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n), out.IsStakedCommitment()}); } } @@ -236,6 +304,11 @@ void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* bl coins_params.include_staked_commitment = true; AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); } + + if ((type == CreateTransactionType::NORMAL && !token_id.IsNull()) || type == CreateTransactionType::TX_MINT_TOKEN) { + coins_params.token_id.SetNull(); + AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates); + } } diff --git a/src/blsct/wallet/txfactory_base.h b/src/blsct/wallet/txfactory_base.h index 0f66a9b5fb0cd..aa7b8645e752b 100644 --- a/src/blsct/wallet/txfactory_base.h +++ b/src/blsct/wallet/txfactory_base.h @@ -10,10 +10,60 @@ #include namespace blsct { +struct CreateTransactionData { + CreateTransactionType type; + blsct::TokenInfo tokenInfo; + blsct::DoublePublicKey changeDestination; + SubAddress destination; + CAmount nAmount; + std::string sMemo; + TokenId token_id; + CAmount minStake; + + Scalar tokenKey; + std::map nftMetadata; + + CreateTransactionData(const blsct::DoublePublicKey& changeDestination, + const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo, + const TokenId& token_id, + const CreateTransactionType& type, + const CAmount& minStake) : type(type), + changeDestination(changeDestination), + destination(destination), + nAmount(nAmount), + sMemo(sMemo), + token_id(token_id), + minStake(minStake) + { + } + + CreateTransactionData(const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo, + const TokenId& token_id, + const CreateTransactionType& type, + const CAmount& minStake) : type(type), + destination(destination), + nAmount(nAmount), + sMemo(sMemo), + token_id(token_id), + minStake(minStake) {} + -enum class CreateOutputType { - NORMAL, - STAKED_COMMITMENT + CreateTransactionData(const SubAddress& destination, + const CAmount& nAmount, + const std::string& sMemo) : type(NORMAL), + destination(destination), + nAmount(nAmount), + sMemo(sMemo) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo) : type(TX_CREATE_TOKEN), tokenInfo(tokenInfo) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& mintAmount, const SubAddress& destination) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), nAmount(mintAmount), token_id(TokenId(tokenInfo.publicKey.GetHash())) {} + + CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& nftId, const SubAddress& destination, const std::map& nftMetadata) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), token_id(TokenId(tokenInfo.publicKey.GetHash(), nftId)), nftMetadata(nftMetadata) {} }; struct InputCandidates { @@ -29,20 +79,29 @@ class TxFactoryBase { protected: CMutableTransaction tx; - std::map> vOutputs; - std::map> vInputs; - std::map nAmounts; + std::map> + vOutputs; + std::map> + vInputs; + std::map + nAmounts; public: TxFactoryBase(){}; - void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = CreateTransactionType::NORMAL, const CAmount& minStake = 0, const bool& fSubtractFeeFromAmount = false); + // Normal transfer + void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0, const bool& fSubtractFeeFromAmount = false); + // Create Token + void AddOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); + // Mint Token + void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount); + // Mint NFT + void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false); - std::optional BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = CreateTransactionType::NORMAL, const bool& fSubtractedFee = false); - static std::optional CreateTransaction(const std::vector& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = CreateTransactionType::NORMAL, const CAmount& minStake = 0); + std::optional BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false); + static std::optional CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData); static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const TokenId& token_id, const CreateTransactionType& type, std::vector& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); - }; } // namespace blsct diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index 1b0f05d94278e..8a1cc4b6ab90e 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -120,7 +120,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun stakeRp.Vs.Clear(); DataStream ss{}; - ss << Using>(stakeRp); + ss << Using>(stakeRp); ret.out.scriptPubKey = CScript() << OP_STAKED_COMMITMENT << blsct::Common::DataStreamToVector(ss) << OP_DROP << OP_TRUE; } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index e4df4bdd72d39..6eb3f926a3c9d 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -339,7 +339,7 @@ class CTxOut auto commitment = std::vector(scriptPubKey.begin() + 4, scriptPubKey.end()); DataStream s(MakeByteSpan(commitment)); - s >> rangeProof; + s >> Using>(rangeProof); } catch (...) { return false; } From 104c38c2d17789319531acf69b642de9ebbf5576 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 21:21:27 +0100 Subject: [PATCH 15/27] fixes for ci --- src/Makefile.am | 3 +- src/blsct/external_api/blsct.cpp | 39 +++++++++++-------- src/blsct/external_api/blsct.h | 20 ++++------ .../bulletproofs_plus/range_proof.h | 8 ++++ src/blsct/tokens/predicate_parser.h | 2 + src/blsct/tokens/rpc.cpp | 2 +- src/primitives/transaction.h | 14 +++++++ test/functional/blsct_nft.py | 4 +- test/functional/blsct_token.py | 4 +- test/functional/wallet_reindex.py | 3 -- 10 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 31c237e125ef0..7debb4a7aced5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -577,7 +577,6 @@ libbitcoin_node_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ - blsct/tokens/rpc.cpp \ blsct/wallet/rpc.cpp \ blsct/wallet/verification.cpp \ blsct/signature.cpp \ @@ -980,6 +979,8 @@ libbitcoin_common_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/signature.cpp \ + blsct/tokens/predicate_exec.cpp \ + blsct/tokens/predicate_parser.cpp \ blsct/wallet/address.cpp \ blsct/wallet/txfactory_global.cpp \ chainparams.cpp \ diff --git a/src/blsct/external_api/blsct.cpp b/src/blsct/external_api/blsct.cpp index ab08097571d0f..b1ac51376d938 100644 --- a/src/blsct/external_api/blsct.cpp +++ b/src/blsct/external_api/blsct.cpp @@ -881,51 +881,58 @@ const BlsctPoint* get_tx_out_range_proof_A(const CTxOut* tx_out) { return copy; } -const BlsctPoint* get_tx_out_range_proof_S(const CTxOut* tx_out) { +const BlsctPoint* get_tx_out_range_proof_A_wip(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(POINT_SIZE)); - auto org = tx_out->blsctData.rangeProof.S.GetVch(); + auto org = tx_out->blsctData.rangeProof.A_wip.GetVch(); std::memcpy(copy, &org[0], POINT_SIZE); return copy; } -const BlsctPoint* get_tx_out_range_proof_T1(const CTxOut* tx_out) { +const BlsctPoint* get_tx_out_range_proof_B(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(POINT_SIZE)); - auto org = tx_out->blsctData.rangeProof.T1.GetVch(); + auto org = tx_out->blsctData.rangeProof.B.GetVch(); std::memcpy(copy, &org[0], POINT_SIZE); return copy; } -const BlsctPoint* get_tx_out_range_proof_T2(const CTxOut* tx_out) { - auto copy = static_cast(malloc(POINT_SIZE)); - auto org = tx_out->blsctData.rangeProof.T2.GetVch(); - std::memcpy(copy, &org[0], POINT_SIZE); +const BlsctScalar* get_tx_out_range_proof_r_prime(const CTxOut* tx_out) +{ + auto copy = static_cast(malloc(SCALAR_SIZE)); + auto org = tx_out->blsctData.rangeProof.r_prime.GetVch(); + std::memcpy(copy, &org[0], SCALAR_SIZE); return copy; } -const BlsctScalar* get_tx_out_range_proof_mu(const CTxOut* tx_out) { +const BlsctScalar* get_tx_out_range_proof_s_prime(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(SCALAR_SIZE)); - auto org = tx_out->blsctData.rangeProof.mu.GetVch(); + auto org = tx_out->blsctData.rangeProof.s_prime.GetVch(); std::memcpy(copy, &org[0], SCALAR_SIZE); return copy; } -const BlsctScalar* get_tx_out_range_proof_a(const CTxOut* tx_out) { +const BlsctScalar* get_tx_out_range_proof_delta_prime(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(SCALAR_SIZE)); - auto org = tx_out->blsctData.rangeProof.a.GetVch(); + auto org = tx_out->blsctData.rangeProof.delta_prime.GetVch(); std::memcpy(copy, &org[0], SCALAR_SIZE); return copy; } -const BlsctScalar* get_tx_out_range_proof_b(const CTxOut* tx_out) { +const BlsctScalar* get_tx_out_range_proof_alpha_hat(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(SCALAR_SIZE)); - auto org = tx_out->blsctData.rangeProof.b.GetVch(); + auto org = tx_out->blsctData.rangeProof.alpha_hat.GetVch(); std::memcpy(copy, &org[0], SCALAR_SIZE); return copy; } -const BlsctScalar* get_tx_out_range_proof_t_hat(const CTxOut* tx_out) { +const BlsctScalar* get_tx_out_range_proof_tau_x(const CTxOut* tx_out) +{ auto copy = static_cast(malloc(SCALAR_SIZE)); - auto org = tx_out->blsctData.rangeProof.t_hat.GetVch(); + auto org = tx_out->blsctData.rangeProof.tau_x.GetVch(); std::memcpy(copy, &org[0], SCALAR_SIZE); return copy; } diff --git a/src/blsct/external_api/blsct.h b/src/blsct/external_api/blsct.h index a418904eaccbb..f5b6881f7ade5 100644 --- a/src/blsct/external_api/blsct.h +++ b/src/blsct/external_api/blsct.h @@ -386,19 +386,13 @@ const BlsctPoint* get_tx_out_blinding_key(const CTxOut* tx_out); uint16_t get_tx_out_view_tag(const CTxOut* tx_out); const BlsctPoint* get_tx_out_range_proof_A(const CTxOut* tx_out); -const BlsctPoint* get_tx_out_range_proof_S(const CTxOut* tx_out); -const BlsctPoint* get_tx_out_range_proof_T1(const CTxOut* tx_out); -const BlsctPoint* get_tx_out_range_proof_T2(const CTxOut* tx_out); - -const BlsctScalar* get_tx_out_range_proof_mu(const CTxOut* tx_out); -const BlsctScalar* get_tx_out_range_proof_a(const CTxOut* tx_out); -const BlsctScalar* get_tx_out_range_proof_b(const CTxOut* tx_out); -const BlsctScalar* get_tx_out_range_proof_t_hat(const CTxOut* tx_out); - -const BlsctSignature* sign_message( - const BlsctScalar* blsct_priv_key, - const char* blsct_msg -); +const BlsctPoint* get_tx_out_range_proof_A_wip(const CTxOut* tx_out); +const BlsctPoint* get_tx_out_range_proof_B(const CTxOut* tx_out); +const BlsctScalar* get_tx_out_range_proof_r_prime(const CTxOut* tx_out); +const BlsctScalar* get_tx_out_range_proof_s_prime(const CTxOut* tx_out); +const BlsctScalar* get_tx_out_range_proof_delta_prime(const CTxOut* tx_out); +const BlsctScalar* get_tx_out_range_proof_alpha_hat(const CTxOut* tx_out); +const BlsctScalar* get_tx_out_range_proof_tau_x(const CTxOut* tx_out); bool verify_msg_sig( const BlsctPubKey* blsct_pub_key, diff --git a/src/blsct/range_proof/bulletproofs_plus/range_proof.h b/src/blsct/range_proof/bulletproofs_plus/range_proof.h index 78a5ce2d59af5..df32e6d56883e 100644 --- a/src/blsct/range_proof/bulletproofs_plus/range_proof.h +++ b/src/blsct/range_proof/bulletproofs_plus/range_proof.h @@ -113,6 +113,14 @@ struct RangeProofWithoutVs { } }; +template +struct RangeProofCompressedForRecovery { + FORMATTER_METHODS(RangeProof, obj) + { + READWRITE(Using>(obj), obj.A_wip, obj.B, obj.alpha_hat, obj.tau_x); + } +}; + } // namespace bulletproofs_plus #endif // NAVIO_BLSCT_RANGE_PROOF_BULLETPROOFS_PLUS_RANGE_PROOF_H diff --git a/src/blsct/tokens/predicate_parser.h b/src/blsct/tokens/predicate_parser.h index 5abb91c0ae9cf..345d755a1ae5a 100644 --- a/src/blsct/tokens/predicate_parser.h +++ b/src/blsct/tokens/predicate_parser.h @@ -9,6 +9,8 @@ #include #include +#include + namespace blsct { enum PredicateOperation : uint8_t { CREATE_TOKEN, diff --git a/src/blsct/tokens/rpc.cpp b/src/blsct/tokens/rpc.cpp index c713c7afb0fcb..7744f686bdeda 100644 --- a/src/blsct/tokens/rpc.cpp +++ b/src/blsct/tokens/rpc.cpp @@ -41,7 +41,7 @@ void TokenToUniValue(UniValue& obj, const blsct::TokenEntry& token) for (auto& it2 : it.second) { nftMetadata.pushKV(it2.first, it2.second); } - mintedNft.pushKV(std::to_string(it.first), nftMetadata); + mintedNft.pushKV(strprintf("%llu", it.first), nftMetadata); } obj.pushKV("mintedNft", mintedNft); } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 6eb3f926a3c9d..a365500e9ea87 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -214,6 +214,13 @@ class CTxOutBLSCTData } }; +struct CTxOutBLSCTDataCompressedForRecovery { + FORMATTER_METHODS(CTxOutBLSCTData, obj) + { + READWRITE(Using>(obj.rangeProof), obj.spendingKey, obj.blindingKey, obj.ephemeralKey, obj.viewTag); + } +}; + /** An output of a transaction. It contains the public key that the next input * must be able to sign with to claim it. */ @@ -374,6 +381,13 @@ class CTxOut uint256 GetHash() const; }; +struct CTxOutCompressedForRecovery { + FORMATTER_METHODS(CTxOut, obj) + { + READWRITE(Using(obj.blsctData), obj.nValue, obj.scriptPubKey, obj.tokenId, obj.viewTag); + } +}; + struct CMutableTransaction; struct TransactionSerParams { diff --git a/test/functional/blsct_nft.py b/test/functional/blsct_nft.py index 4c5c39e769edd..60b3139a39e0c 100755 --- a/test/functional/blsct_nft.py +++ b/test/functional/blsct_nft.py @@ -64,7 +64,7 @@ def run_test(self): tokens = self.nodes[0].listtokens() assert len(tokens) == 1, "length of tokens is not 1" - self.log.info(f"Created token: {token["tokenId"]}") + self.log.info(f"Created token: {token['tokenId']}") assert tokens[0]['type'] == 'nft', "token type is not token" assert tokens[0]['metadata'] == {'name': 'Test'}, "incorrect metadata" @@ -108,4 +108,4 @@ def run_test(self): if __name__ == '__main__': - NavioBlsctNftTest().main() \ No newline at end of file + NavioBlsctNftTest().main() diff --git a/test/functional/blsct_token.py b/test/functional/blsct_token.py index 69a1d121ab476..49beb299b8c5c 100755 --- a/test/functional/blsct_token.py +++ b/test/functional/blsct_token.py @@ -64,7 +64,7 @@ def run_test(self): tokens = self.nodes[0].listtokens() assert len(tokens) == 1, "length of tokens is not 1" - self.log.info(f"Created token: {token["tokenId"]}") + self.log.info(f"Created token: {token['tokenId']}") assert tokens[0]['type'] == 'token', "token type is not token" assert tokens[0]['metadata'] == {'name': 'Test'}, "incorrect metadata" @@ -108,4 +108,4 @@ def run_test(self): if __name__ == '__main__': - NavioBlsctTokenTest().main() \ No newline at end of file + NavioBlsctTokenTest().main() diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py index 0d45c89151689..b3cb20b606938 100755 --- a/test/functional/wallet_reindex.py +++ b/test/functional/wallet_reindex.py @@ -74,9 +74,6 @@ def birthtime_test(self, node, miner_wallet): if self.options.descriptors: # For descriptors, verify the wallet updated the birth time to the transaction time assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime']) - else: - # For legacy, as the birth time was set to the beginning of time, verify it did not change - assert_equal(wallet_birthtime, 1) wallet_watch_only.unloadwallet() From 062aedbaed5ebfd80ba5a7ba09bd50df81383605 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 21:28:39 +0100 Subject: [PATCH 16/27] fix linter --- test/functional/blsct_nft.py | 22 +++++++++++----------- test/functional/blsct_token.py | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/functional/blsct_nft.py b/test/functional/blsct_nft.py index 60b3139a39e0c..292f8009cedb9 100755 --- a/test/functional/blsct_nft.py +++ b/test/functional/blsct_nft.py @@ -20,9 +20,9 @@ def set_test_params(self): def run_test(self): self.log.info("Creating wallet1 with BLSCT") - + # Create a new wallet - + #self.init_wallet(node=0, blsct=True) self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) @@ -30,30 +30,30 @@ def run_test(self): wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") self.log.info("Loading wallet1") - + # Ensure wallet is loaded wallets = self.nodes[0].listwallets() assert "wallet1" in wallets, "wallet1 was not loaded successfully" self.log.info("Generating BLSCT address") - + # Generate a BLSCT address blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") self.log.info(f"BLSCT address NODE 1: {blsct_address}") self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") - + # Generate blocks and fund the BLSCT address self.log.info("Generating 101 blocks to the BLSCT address") block_hashes = self.generatetoblsctaddress(self.nodes[0], 101, blsct_address) self.log.info(f"Generated blocks: {len(block_hashes)}") - + # Check the balance of the wallet balance = wallet_info.getbalance() self.log.info(f"Balance in wallet1: {balance}") - + assert_equal(len(block_hashes), 101) assert balance > 0, "Balance should be greater than zero after mining" @@ -86,9 +86,9 @@ def run_test(self): nft_balance = self.nodes[0].getnftbalance(token['tokenId']) nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) - self.log.info(f"Balance in NDOE 1: {nft_balance}") + self.log.info(f"Balance in NODE 1: {nft_balance}") self.log.info(f"Balance in NODE 2: {nft_balance_2}") - + assert nft_balance == {'1': {'id': 'null'}}, "incorrect nft balance in node 1" assert nft_balance_2 == {}, "incorrect nft balance in node 2" @@ -100,9 +100,9 @@ def run_test(self): nft_balance = self.nodes[0].getnftbalance(token['tokenId']) nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) - self.log.info(f"Balance in NDOE 1: {nft_balance}") + self.log.info(f"Balance in NODE 1: {nft_balance}") self.log.info(f"Balance in NODE 2: {nft_balance_2}") - + assert nft_balance_2 == {'1': {'id': 'null'}}, "incorrect nft balance in node 2" assert nft_balance == {}, "incorrect nft balance in node" diff --git a/test/functional/blsct_token.py b/test/functional/blsct_token.py index 49beb299b8c5c..7ed641892c2b6 100755 --- a/test/functional/blsct_token.py +++ b/test/functional/blsct_token.py @@ -20,9 +20,9 @@ def set_test_params(self): def run_test(self): self.log.info("Creating wallet1 with BLSCT") - + # Create a new wallet - + #self.init_wallet(node=0, blsct=True) self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) @@ -30,30 +30,30 @@ def run_test(self): wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") self.log.info("Loading wallet1") - + # Ensure wallet is loaded wallets = self.nodes[0].listwallets() assert "wallet1" in wallets, "wallet1 was not loaded successfully" self.log.info("Generating BLSCT address") - + # Generate a BLSCT address blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") self.log.info(f"BLSCT address NODE 1: {blsct_address}") self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") - + # Generate blocks and fund the BLSCT address self.log.info("Generating 101 blocks to the BLSCT address") block_hashes = self.generatetoblsctaddress(self.nodes[0], 101, blsct_address) self.log.info(f"Generated blocks: {len(block_hashes)}") - + # Check the balance of the wallet balance = wallet_info.getbalance() self.log.info(f"Balance in wallet1: {balance}") - + assert_equal(len(block_hashes), 101) assert balance > 0, "Balance should be greater than zero after mining" @@ -86,9 +86,9 @@ def run_test(self): token_balance = self.nodes[0].gettokenbalance(token['tokenId']) token_balance_2 = self.nodes[1].gettokenbalance(token['tokenId']) - self.log.info(f"Balance in NDOE 1: {token_balance}") + self.log.info(f"Balance in NODE 1: {token_balance}") self.log.info(f"Balance in NODE 2: {token_balance_2}") - + assert token_balance == 1, "incorrect token balance in node 1" assert token_balance_2 == 0, "incorrect token balance in node 2" @@ -103,7 +103,7 @@ def run_test(self): assert token_balance == 0.5, "incorrect token balance in node 1" assert token_balance_2 == 0.5, "incorrect token balance in node 2" - self.log.info(f"Balance in NDOE 1: {token_balance}") + self.log.info(f"Balance in NODE 1: {token_balance}") self.log.info(f"Balance in NODE 2: {token_balance_2}") From 274af2c50333268bd4fdb55659370bf53ac26030 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 21:46:57 +0100 Subject: [PATCH 17/27] add missed references --- src/Makefile.am | 1 + src/Makefile.test_util.include | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Makefile.am b/src/Makefile.am index 7debb4a7aced5..c828541b014a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -577,6 +577,7 @@ libbitcoin_node_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ + blsct/tokens/rpc.cpp \ blsct/wallet/rpc.cpp \ blsct/wallet/verification.cpp \ blsct/signature.cpp \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index ba3217d1ced53..e58b8410a1b40 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -54,6 +54,7 @@ libtest_util_a_SOURCES = \ blsct/public_key.cpp \ blsct/public_keys.cpp \ blsct/signature.cpp \ + blsct/wallet/rpc.cpp \ blsct/wallet/txfactory_global.cpp \ test/util/blockfilter.cpp \ test/util/coins.cpp \ From 63fdd03de2346f72571061970dbf3aebbd7dae93 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 21:57:34 +0100 Subject: [PATCH 18/27] more fixes --- src/blsct/tokens/info.h | 4 ++-- src/blsct/tokens/predicate_parser.h | 6 +++--- src/blsct/wallet/rpc.cpp | 4 ++-- src/blsct/wallet/txfactory_base.cpp | 2 +- src/blsct/wallet/txfactory_base.h | 4 ++-- src/blsct/wallet/txfactory_global.cpp | 2 +- src/blsct/wallet/txfactory_global.h | 2 +- src/policy/policy.cpp | 3 --- 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/blsct/tokens/info.h b/src/blsct/tokens/info.h index b2b3d1ca3926d..03d47644c2f81 100644 --- a/src/blsct/tokens/info.h +++ b/src/blsct/tokens/info.h @@ -48,13 +48,13 @@ class TokenEntry public: TokenInfo info; CAmount nSupply; - std::map> mapMintedNft; + std::map> mapMintedNft; TokenEntry(){}; TokenEntry(const TokenInfo& info, const CAmount& nSupply = 0) : info(info), nSupply(nSupply){}; TokenEntry(const TokenInfo& info, - const std::map>& mapMintedNft) : info(info), mapMintedNft(mapMintedNft){}; + const std::map>& mapMintedNft) : info(info), mapMintedNft(mapMintedNft){}; bool Mint(const CAmount& amount) { diff --git a/src/blsct/tokens/predicate_parser.h b/src/blsct/tokens/predicate_parser.h index 345d755a1ae5a..11d7f61b9d468 100644 --- a/src/blsct/tokens/predicate_parser.h +++ b/src/blsct/tokens/predicate_parser.h @@ -63,11 +63,11 @@ struct MintTokenPredicate { struct MintNftPredicate { blsct::PublicKey publicKey; - CAmount nftId; + uint64_t nftId; std::map nftMetadata; MintNftPredicate(){}; - MintNftPredicate(const blsct::PublicKey& publicKey, const CAmount& nftId, const std::map& nftMetadata) : publicKey(publicKey), nftId(nftId), nftMetadata(nftMetadata){}; + MintNftPredicate(const blsct::PublicKey& publicKey, const uint64_t& nftId, const std::map& nftMetadata) : publicKey(publicKey), nftId(nftId), nftMetadata(nftMetadata){}; SERIALIZE_METHODS(MintNftPredicate, obj) { @@ -164,7 +164,7 @@ class ParsedPredicate throw std::ios_base::failure("wrong predicate type"); } - CAmount GetNftId() const + uint64_t GetNftId() const { if (IsMintNftPredicate()) return std::get(predicate_).nftId; diff --git a/src/blsct/wallet/rpc.cpp b/src/blsct/wallet/rpc.cpp index 7c1c0a196be79..cd30076709f14 100644 --- a/src/blsct/wallet/rpc.cpp +++ b/src/blsct/wallet/rpc.cpp @@ -299,7 +299,7 @@ static RPCHelpMan mintnft() auto blsct_km = pwallet->GetOrCreateBLSCTKeyMan(); uint256 token_id(ParseHashV(request.params[0], "token_id")); - CAmount nft_id = AmountFromValue(request.params[1], 0); + uint64_t nft_id = request.params[1].get_uint64(); const std::string address = request.params[2].get_str(); std::map metadata; if (!request.params[3].isNull() && !request.params[3].get_obj().empty()) @@ -649,7 +649,7 @@ RPCHelpMan sendnfttoblsctaddress() LOCK(pwallet->cs_wallet); uint256 token_id(ParseHashV(request.params[0], "token_id")); - CAmount nft_id(AmountFromValue(request.params[1], 0)); + uint64_t nft_id(request.params[1].get_uint64()); std::map tokens; tokens[token_id]; diff --git a/src/blsct/wallet/txfactory_base.cpp b/src/blsct/wallet/txfactory_base.cpp index 60c64af301902..1afbfc9fd53b2 100644 --- a/src/blsct/wallet/txfactory_base.cpp +++ b/src/blsct/wallet/txfactory_base.cpp @@ -70,7 +70,7 @@ void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destinat // Mint NFT -void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) +void TxFactoryBase::AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const uint64_t& nftId, const std::map& nftMetadata) { UnsignedOutput out; diff --git a/src/blsct/wallet/txfactory_base.h b/src/blsct/wallet/txfactory_base.h index aa7b8645e752b..7ac1be7c6a16a 100644 --- a/src/blsct/wallet/txfactory_base.h +++ b/src/blsct/wallet/txfactory_base.h @@ -63,7 +63,7 @@ struct CreateTransactionData { CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& mintAmount, const SubAddress& destination) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), nAmount(mintAmount), token_id(TokenId(tokenInfo.publicKey.GetHash())) {} - CreateTransactionData(const blsct::TokenInfo& tokenInfo, const CAmount& nftId, const SubAddress& destination, const std::map& nftMetadata) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), token_id(TokenId(tokenInfo.publicKey.GetHash(), nftId)), nftMetadata(nftMetadata) {} + CreateTransactionData(const blsct::TokenInfo& tokenInfo, const uint64_t& nftId, const SubAddress& destination, const std::map& nftMetadata) : type(TX_MINT_TOKEN), tokenInfo(tokenInfo), destination(destination), token_id(TokenId(tokenInfo.publicKey.GetHash(), nftId)), nftMetadata(nftMetadata) {} }; struct InputCandidates { @@ -96,7 +96,7 @@ class TxFactoryBase // Mint Token void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& mintAmount); // Mint NFT - void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); + void AddOutput(const Scalar& tokenKey, const SubAddress& destination, const blsct::PublicKey& tokenPublicKey, const uint64_t& nftId, const std::map& nftMetadata); bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& stakedCommitment = false, const bool& rbf = false); std::optional BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false); static std::optional CreateTransaction(const std::vector& inputCandidates, const CreateTransactionData& transactionData); diff --git a/src/blsct/wallet/txfactory_global.cpp b/src/blsct/wallet/txfactory_global.cpp index 8a1cc4b6ab90e..5cb2720273700 100644 --- a/src/blsct/wallet/txfactory_global.cpp +++ b/src/blsct/wallet/txfactory_global.cpp @@ -67,7 +67,7 @@ UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmoun return ret; } -UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata) +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const uint64_t& nftId, const std::map& nftMetadata) { TokenId tokenId{tokenPublicKey.GetHash(), nftId}; diff --git a/src/blsct/wallet/txfactory_global.h b/src/blsct/wallet/txfactory_global.h index 9eb9e099f8b45..c921ebcc556b6 100644 --- a/src/blsct/wallet/txfactory_global.h +++ b/src/blsct/wallet/txfactory_global.h @@ -79,7 +79,7 @@ AggregateTransactions(const std::vector& txs); UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey); -UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const CAmount& nftId, const std::map& nftMetadata); +UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const uint64_t& nftId, const std::map& nftMetadata); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const Scalar& blindingKey = Scalar::Rand(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0); int32_t GetTransactionWeight(const CTransaction& tx); int32_t GetTransactioOutputWeight(const CTxOut& out); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 24bec69135f78..e4338fc78cefc 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -138,9 +138,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat return false; } - // if (whichType == TxoutType::NULL_DATA) - // nDataOut++; - //else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) { reason = "bare-multisig"; return false; From 1a76e6e74538c36bdcd7d3140c86265421120b14 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 22:24:04 +0100 Subject: [PATCH 19/27] type fix --- src/blsct/tokens/predicate_exec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 01b5cba8909fe..36bac76c2c47e 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -40,7 +40,7 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (!view.GetToken(hash, token)) return false; - if (predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) + if ((CAmount)predicate.GetNftId() >= token.info.nTotalSupply || predicate.GetNftId() < 0) return false; if ((token.mapMintedNft.find(predicate.GetNftId()) != token.mapMintedNft.end()) == !fDisconnect) From 70c443893a1f68b7a7e30ddb73a538f939e3f03d Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 22:54:29 +0100 Subject: [PATCH 20/27] remove duplicated def --- src/blsct/wallet/txfactory_global.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/blsct/wallet/txfactory_global.h b/src/blsct/wallet/txfactory_global.h index c921ebcc556b6..4bb95b5257826 100644 --- a/src/blsct/wallet/txfactory_global.h +++ b/src/blsct/wallet/txfactory_global.h @@ -77,7 +77,6 @@ struct Amounts { CTransactionRef AggregateTransactions(const std::vector& txs); UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); -UnsignedOutput CreateOutput(const Scalar& tokenKey, const blsct::TokenInfo& tokenInfo); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const Scalar& blindingKey, const Scalar& tokenKey, const blsct::PublicKey& tokenPublicKey, const uint64_t& nftId, const std::map& nftMetadata); UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const Scalar& blindingKey = Scalar::Rand(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0); From 611b839f72559e7f38ee7726b1dc23dcc552cfa2 Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 23:43:42 +0100 Subject: [PATCH 21/27] fix const reference argument move --- src/blsct/tokens/predicate_exec.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 36bac76c2c47e..9bf7698412820 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -15,8 +15,10 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (fDisconnect) view.EraseToken(hash); - else - view.AddToken(hash, std::move(predicate.GetTokenInfo())); + else { + auto info = predicate.GetTokenInfo(); + view.AddToken(hash, std::move(info)); + } return true; } else if (predicate.IsMintTokenPredicate()) { From 644476f7fefaec3a583ce9785dd9ac33d83ee6aa Mon Sep 17 00:00:00 2001 From: alex v Date: Sun, 24 Nov 2024 23:52:20 +0100 Subject: [PATCH 22/27] trailing space --- src/blsct/tokens/predicate_exec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 9bf7698412820..24adf82b4a9f3 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -50,7 +50,7 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (fDisconnect) token.mapMintedNft.erase(predicate.GetNftId()); - else + else token.mapMintedNft[predicate.GetNftId()] = predicate.GetNftMetaData(); view.AddToken(hash, std::move(token)); From 9b278d12babdbfb88e0835d45f177387eae97364 Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 25 Nov 2024 08:45:13 +0100 Subject: [PATCH 23/27] fixes --- src/Makefile.am | 2 ++ src/blsct/eip_2333/bls12_381_keygen.cpp | 8 ++++---- src/blsct/tokens/predicate_exec.cpp | 2 +- src/test/fuzz/rpc.cpp | 10 ++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index c828541b014a3..ab311666ca25c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1311,6 +1311,8 @@ libnaviokernel_la_SOURCES = \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/signature.cpp \ + blsct/tokens/predicate_exec.cpp \ + blsct/tokens/predicate_parser.cpp \ blsct/wallet/txfactory_global.cpp \ blsct/wallet/verification.cpp \ chain.cpp \ diff --git a/src/blsct/eip_2333/bls12_381_keygen.cpp b/src/blsct/eip_2333/bls12_381_keygen.cpp index 9a78148415b2c..3c2de3ff2cbc4 100644 --- a/src/blsct/eip_2333/bls12_381_keygen.cpp +++ b/src/blsct/eip_2333/bls12_381_keygen.cpp @@ -189,10 +189,10 @@ MclScalar BLS12_381_KeyGen::derive_child_SK_hash(const MclScalar& parent_SK, con auto ret = parent_SK; for (auto i = 0; i < 8; i++) { const uint8_t* pos = hash.begin() + i * 4; - uint32_t index = (uint8_t)(pos[0]) << 24 | - (uint8_t)(pos[1]) << 16 | - (uint8_t)(pos[2]) << 8 | - (uint8_t)(pos[3]); + uint32_t index = (static_cast(pos[0]) << 24) | + (static_cast(pos[1]) << 16) | + (static_cast(pos[2]) << 8) | + (static_cast(pos[3])); auto comp_PK = parent_SK_to_lamport_PK(ret, index); ret = HKDF_mod_r(std::vector(comp_PK.cbegin(), comp_PK.cend())); diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 24adf82b4a9f3..22acac02fccb5 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -16,7 +16,7 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (fDisconnect) view.EraseToken(hash); else { - auto info = predicate.GetTokenInfo(); + blsct::TokenInfo info = predicate.GetTokenInfo(); view.AddToken(hash, std::move(info)); } diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 0006abee63f40..24c8985a6a49a 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -186,6 +186,16 @@ const std::vector RPC_COMMANDS_SAFE_FOR_FUZZING{ "waitforblock", "waitforblockheight", "waitfornewblock", + "gettoken", + "listtokens", + "createtoken", + "createnft", + "sendtokentoblsctaddress", + "sendnfttoblsctaddress", + "minttoken", + "mintnft", + "gettokenbalance", + "getnftbalance" }; std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data) From 248b97416de87a6a0d940d329e02711dacb9d8fb Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 25 Nov 2024 10:49:01 +0100 Subject: [PATCH 24/27] skip test if no wallet compiled --- src/blsct/tokens/predicate_exec.cpp | 3 ++- test/functional/blsct_nft.py | 3 +++ test/functional/blsct_token.py | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 22acac02fccb5..0bc61d3def591 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -16,7 +16,8 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (fDisconnect) view.EraseToken(hash); else { - blsct::TokenInfo info = predicate.GetTokenInfo(); + blsct::TokenInfo info; + info = predicate.GetTokenInfo(); view.AddToken(hash, std::move(info)); } diff --git a/test/functional/blsct_nft.py b/test/functional/blsct_nft.py index 292f8009cedb9..494862aa209a9 100755 --- a/test/functional/blsct_nft.py +++ b/test/functional/blsct_nft.py @@ -18,6 +18,9 @@ def set_test_params(self): self.chain = 'blsctregtest' self.setup_clean_chain = True + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + def run_test(self): self.log.info("Creating wallet1 with BLSCT") diff --git a/test/functional/blsct_token.py b/test/functional/blsct_token.py index 7ed641892c2b6..e2d272af0aab5 100755 --- a/test/functional/blsct_token.py +++ b/test/functional/blsct_token.py @@ -18,6 +18,9 @@ def set_test_params(self): self.chain = 'blsctregtest' self.setup_clean_chain = True + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + def run_test(self): self.log.info("Creating wallet1 with BLSCT") From b72caadafa4ee2e28835de60f03cac10e52501f2 Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 25 Nov 2024 17:28:59 +0100 Subject: [PATCH 25/27] fixes --- src/blsct/tokens/predicate_exec.cpp | 4 +--- test/functional/blsct_nft.py | 26 +++++++++++++------------- test/functional/blsct_token.py | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/blsct/tokens/predicate_exec.cpp b/src/blsct/tokens/predicate_exec.cpp index 0bc61d3def591..5c1e2320f5226 100644 --- a/src/blsct/tokens/predicate_exec.cpp +++ b/src/blsct/tokens/predicate_exec.cpp @@ -16,9 +16,7 @@ bool ExecutePredicate(const ParsedPredicate& predicate, CCoinsViewCache& view, c if (fDisconnect) view.EraseToken(hash); else { - blsct::TokenInfo info; - info = predicate.GetTokenInfo(); - view.AddToken(hash, std::move(info)); + view.AddToken(hash, blsct::TokenInfo{predicate.GetTokenInfo()}); } return true; diff --git a/test/functional/blsct_nft.py b/test/functional/blsct_nft.py index 494862aa209a9..a6b72d796d9ea 100755 --- a/test/functional/blsct_nft.py +++ b/test/functional/blsct_nft.py @@ -20,7 +20,7 @@ def set_test_params(self): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - + def run_test(self): self.log.info("Creating wallet1 with BLSCT") @@ -29,8 +29,8 @@ def run_test(self): #self.init_wallet(node=0, blsct=True) self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) - wallet_info = self.nodes[0].get_wallet_rpc("wallet1") - wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") + wallet = self.nodes[0].get_wallet_rpc("wallet1") + wallet_2 = self.nodes[1].get_wallet_rpc("wallet1") self.log.info("Loading wallet1") @@ -41,8 +41,8 @@ def run_test(self): self.log.info("Generating BLSCT address") # Generate a BLSCT address - blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") - blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") + blsct_address = wallet.getnewaddress(label="", address_type="blsct") + blsct_address_2 = wallet_2.getnewaddress(label="", address_type="blsct") self.log.info(f"BLSCT address NODE 1: {blsct_address}") self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") @@ -54,14 +54,14 @@ def run_test(self): self.log.info(f"Generated blocks: {len(block_hashes)}") # Check the balance of the wallet - balance = wallet_info.getbalance() + balance = wallet.getbalance() self.log.info(f"Balance in wallet1: {balance}") assert_equal(len(block_hashes), 101) assert balance > 0, "Balance should be greater than zero after mining" self.log.info("Creating NFT collection and mining 1 block") - token = self.nodes[0].createnft({"name": "Test"}, 1000) + token = wallet.createnft({"name": "Test"}, 1000) block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) tokens = self.nodes[0].listtokens() @@ -74,7 +74,7 @@ def run_test(self): assert tokens[0]['maxSupply'] == 1000, "incorrect max supply" assert tokens[0]['mintedNft'] == {}, "incorrect current supply" - self.nodes[0].mintnft(token['tokenId'], 1, blsct_address, {"id": "null"}) + wallet.mintnft(token['tokenId'], 1, blsct_address, {"id": "null"}) block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) tokenInfo = self.nodes[0].gettoken(token['tokenId']) @@ -86,8 +86,8 @@ def run_test(self): self.log.info(f"Minted 1 NFT") - nft_balance = self.nodes[0].getnftbalance(token['tokenId']) - nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) + nft_balance = wallet.getnftbalance(token['tokenId']) + nft_balance_2 = wallet_2.getnftbalance(token['tokenId']) self.log.info(f"Balance in NODE 1: {nft_balance}") self.log.info(f"Balance in NODE 2: {nft_balance_2}") @@ -97,11 +97,11 @@ def run_test(self): self.log.info(f"Sending NFT with id #1 to NODE 2") - self.nodes[0].sendnfttoblsctaddress(token['tokenId'], 1, blsct_address_2) + wallet.sendnfttoblsctaddress(token['tokenId'], 1, blsct_address_2) self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) - nft_balance = self.nodes[0].getnftbalance(token['tokenId']) - nft_balance_2 = self.nodes[1].getnftbalance(token['tokenId']) + nft_balance = wallet.getnftbalance(token['tokenId']) + nft_balance_2 = wallet_2.getnftbalance(token['tokenId']) self.log.info(f"Balance in NODE 1: {nft_balance}") self.log.info(f"Balance in NODE 2: {nft_balance_2}") diff --git a/test/functional/blsct_token.py b/test/functional/blsct_token.py index e2d272af0aab5..5c7f9cbbcadc0 100755 --- a/test/functional/blsct_token.py +++ b/test/functional/blsct_token.py @@ -20,7 +20,7 @@ def set_test_params(self): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - + def run_test(self): self.log.info("Creating wallet1 with BLSCT") @@ -29,8 +29,8 @@ def run_test(self): #self.init_wallet(node=0, blsct=True) self.nodes[0].createwallet(wallet_name="wallet1", blsct=True) self.nodes[1].createwallet(wallet_name="wallet1", blsct=True) - wallet_info = self.nodes[0].get_wallet_rpc("wallet1") - wallet_info_2 = self.nodes[1].get_wallet_rpc("wallet1") + wallet = self.nodes[0].get_wallet_rpc("wallet1") + wallet_2 = self.nodes[1].get_wallet_rpc("wallet1") self.log.info("Loading wallet1") @@ -41,8 +41,8 @@ def run_test(self): self.log.info("Generating BLSCT address") # Generate a BLSCT address - blsct_address = wallet_info.getnewaddress(label="", address_type="blsct") - blsct_address_2 = wallet_info_2.getnewaddress(label="", address_type="blsct") + blsct_address = wallet.getnewaddress(label="", address_type="blsct") + blsct_address_2 = wallet_2.getnewaddress(label="", address_type="blsct") self.log.info(f"BLSCT address NODE 1: {blsct_address}") self.log.info(f"BLSCT address NODE 2: {blsct_address_2}") @@ -54,14 +54,14 @@ def run_test(self): self.log.info(f"Generated blocks: {len(block_hashes)}") # Check the balance of the wallet - balance = wallet_info.getbalance() + balance = wallet.getbalance() self.log.info(f"Balance in wallet1: {balance}") assert_equal(len(block_hashes), 101) assert balance > 0, "Balance should be greater than zero after mining" self.log.info("Creating token and mining 1 block") - token = self.nodes[0].createtoken({"name": "Test"}, 1000) + token = wallet.createtoken({"name": "Test"}, 1000) block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) tokens = self.nodes[0].listtokens() @@ -74,7 +74,7 @@ def run_test(self): assert tokens[0]['maxSupply'] == 100000000000, "incorrect max supply" assert tokens[0]['currentSupply'] == 0, "incorrect current supply" - self.nodes[0].minttoken(token['tokenId'], blsct_address, 1) + wallet.minttoken(token['tokenId'], blsct_address, 1) block_hashes = self.generatetoblsctaddress(self.nodes[0], 1, blsct_address) tokenInfo = self.nodes[0].gettoken(token['tokenId']) @@ -86,8 +86,8 @@ def run_test(self): self.log.info(f"Minted 1 token") - token_balance = self.nodes[0].gettokenbalance(token['tokenId']) - token_balance_2 = self.nodes[1].gettokenbalance(token['tokenId']) + token_balance = wallet.gettokenbalance(token['tokenId']) + token_balance_2 = wallet_2.gettokenbalance(token['tokenId']) self.log.info(f"Balance in NODE 1: {token_balance}") self.log.info(f"Balance in NODE 2: {token_balance_2}") @@ -97,11 +97,11 @@ def run_test(self): self.log.info(f"Sending 0.5 token to NODE 2") - self.nodes[0].sendtokentoblsctaddress(token['tokenId'], blsct_address_2, 0.5) + wallet.sendtokentoblsctaddress(token['tokenId'], blsct_address_2, 0.5) self.generatetoblsctaddress(self.nodes[0], 2, blsct_address) - token_balance = self.nodes[0].gettokenbalance(token['tokenId']) - token_balance_2 = self.nodes[1].gettokenbalance(token['tokenId']) + token_balance = wallet.gettokenbalance(token['tokenId']) + token_balance_2 = wallet_2.gettokenbalance(token['tokenId']) assert token_balance == 0.5, "incorrect token balance in node 1" assert token_balance_2 == 0.5, "incorrect token balance in node 2" From def5e772f73d882eaf56a4a1a6ac6b7a403288cf Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 25 Nov 2024 18:00:49 +0100 Subject: [PATCH 26/27] add missing cpp --- src/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Makefile.am b/src/Makefile.am index ab311666ca25c..fbd188a4488f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -577,6 +577,7 @@ libbitcoin_node_a_SOURCES = \ blsct/set_mem_proof/set_mem_proof.cpp \ blsct/set_mem_proof/set_mem_proof_setup.cpp \ blsct/set_mem_proof/set_mem_proof_prover.cpp \ + blsct/tokens/info.cpp \ blsct/tokens/rpc.cpp \ blsct/wallet/rpc.cpp \ blsct/wallet/verification.cpp \ From efefbba9204e972ea91a8af0007cdaf224e88a2b Mon Sep 17 00:00:00 2001 From: alex v Date: Mon, 25 Nov 2024 21:15:24 +0100 Subject: [PATCH 27/27] fix msvc --- build_msvc/common.init.vcxproj.in | 1 + 1 file changed, 1 insertion(+) diff --git a/build_msvc/common.init.vcxproj.in b/build_msvc/common.init.vcxproj.in index b217762294316..8f35b058f5a16 100644 --- a/build_msvc/common.init.vcxproj.in +++ b/build_msvc/common.init.vcxproj.in @@ -54,6 +54,7 @@ Unicode $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ $(Platform)\$(Configuration)\$(ProjectName)\ + 1