Skip to content

Commit

Permalink
allow flexible staking
Browse files Browse the repository at this point in the history
  • Loading branch information
alex v committed Aug 1, 2024
1 parent 611db77 commit 01b4f32
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/blsct/wallet/keyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ bool KeyMan::OutputIsChange(const CTxOut& out) const
blsct::SubAddressIdentifier subAddId;

if (GetSubAddressId(id, subAddId)) {
return subAddId.account == -1;
return subAddId.account == CHANGE_ACCOUNT;
}

return false;
Expand Down
4 changes: 4 additions & 0 deletions src/blsct/wallet/keyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include <wallet/walletdb.h>

namespace blsct {

const int64_t CHANGE_ACCOUNT = -1;
const int64_t STAKING_ACCOUNT = -2;

class Manager
{
protected:
Expand Down
127 changes: 87 additions & 40 deletions src/blsct/wallet/txfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,39 @@ using Scalars = Elements<Scalar>;

namespace blsct {

void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const CreateOutputType& type, const CAmount& minStake)
void TxFactoryBase::AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake)
{
UnsignedOutput out;
out = CreateOutput(destination.GetKeys(), nAmount, sMemo, tokenId, Scalar::Rand(), type, minStake);
out = CreateOutput(destination.GetKeys(), nAmount, sMemo, token_id, Scalar::Rand(), type, minStake);

if (nAmounts.count(tokenId) == 0)
nAmounts[tokenId] = {0, 0};
if (nAmounts.count(token_id) == 0)
nAmounts[token_id] = {0, 0};

nAmounts[tokenId].nFromOutputs += nAmount;
nAmounts[token_id].nFromOutputs += nAmount;

if (vOutputs.count(tokenId) == 0)
vOutputs[tokenId] = std::vector<UnsignedOutput>();
if (vOutputs.count(token_id) == 0)
vOutputs[token_id] = std::vector<UnsignedOutput>();

vOutputs[tokenId].push_back(out);
vOutputs[token_id].push_back(out);
}

bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& tokenId, const COutPoint& outpoint, const bool& rbf)
bool TxFactoryBase::AddInput(const CAmount& amount, const MclScalar& gamma, const PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& rbf)
{
if (vInputs.count(tokenId) == 0)
vInputs[tokenId] = std::vector<UnsignedInput>();
if (vInputs.count(token_id) == 0)
vInputs[token_id] = std::vector<UnsignedInput>();

vInputs[tokenId].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey});
vInputs[token_id].push_back({CTxIn(outpoint, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL), amount, gamma, spendingKey});

if (nAmounts.count(tokenId) == 0)
nAmounts[tokenId] = {0, 0};
if (nAmounts.count(token_id) == 0)
nAmounts[token_id] = {0, 0};

nAmounts[tokenId].nFromInputs += amount;
nAmounts[token_id].nFromInputs += amount;

return true;
}

std::optional<CMutableTransaction>
TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake, const bool& fUnstake, const bool& fSubtractedFee)
TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake, const CreateTransactionType& type, const bool& fSubtractedFee)
{
CAmount nFee = BLSCT_DEFAULT_FEE * (vInputs.size() + vOutputs.size());

Expand Down Expand Up @@ -81,7 +81,7 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA

for (auto& change : mapChange) {
if (change.second == 0) continue;
auto changeOutput = CreateOutput(changeDestination, change.second, "Change", change.first, MclScalar::Rand(), fUnstake ? STAKED_COMMITMENT : NORMAL, minStake);
auto changeOutput = CreateOutput(changeDestination, change.second, "Change", change.first, MclScalar::Rand(), type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE ? STAKED_COMMITMENT : NORMAL, minStake);
tx.vout.push_back(changeOutput.out);
gammaAcc = gammaAcc - changeOutput.gamma;
txSigs.push_back(PrivateKey(changeOutput.blindingKey).Sign(changeOutput.out.GetHash()));
Expand All @@ -101,26 +101,55 @@ TxFactoryBase::BuildTx(const blsct::DoublePublicKey& changeDestination, const CA
return std::nullopt;
}

std::optional<CMutableTransaction> TxFactoryBase::CreateTransaction(const std::vector<InputCandidates>& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const CreateOutputType& type, const CAmount& minStake, const bool& fUnstake)
std::optional<CMutableTransaction> TxFactoryBase::CreateTransaction(const std::vector<InputCandidates>& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id, const CreateTransactionType& type, const CAmount& minStake)
{
auto tx = blsct::TxFactoryBase();
CAmount inAmount = 0;

for (const auto& output : inputCandidates) {
tx.AddInput(output.amount, output.gamma, output.spendingKey, output.tokenId, COutPoint(output.outpoint.hash, output.outpoint.n));
inAmount += output.amount;
if (tx.nAmounts[tokenId].nFromInputs > nAmount + (long long)(BLSCT_DEFAULT_FEE * (tx.vInputs.size() + 2))) break;
}
if (type == STAKED_COMMITMENT) {
CAmount inputFromStakedCommitments = 0;
for (const auto& output : inputCandidates) {
if (!output.is_staked_commitment)
continue;

tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n));

CAmount subtract = 0;
bool fChangeNeeded = inAmount > nAmount;
inputFromStakedCommitments += output.amount;
}
for (const auto& output : inputCandidates) {
if (output.is_staked_commitment)
continue;

if (fUnstake)
subtract = (BLSCT_DEFAULT_FEE * (tx.vInputs.size() + 1 + fChangeNeeded));
tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n));

tx.AddOutput(destination, nAmount - subtract, sMemo, tokenId, type, minStake);
inAmount += output.amount;

if (tx.nAmounts[token_id].nFromInputs - inputFromStakedCommitments > nAmount + (long long)(BLSCT_DEFAULT_FEE * (tx.vInputs.size() + 2)))
break;
}

if (nAmount + inputFromStakedCommitments < minStake) {
throw std::runtime_error(strprintf("A minimum of %s is required to stake", FormatMoney(minStake)));
}

tx.AddOutput(destination, nAmount + inputFromStakedCommitments, sMemo, token_id, type, minStake);
} else {
for (const auto& output : inputCandidates) {
tx.AddInput(output.amount, output.gamma, output.spendingKey, output.token_id, COutPoint(output.outpoint.hash, output.outpoint.n));
inAmount += output.amount;
if (tx.nAmounts[token_id].nFromInputs > nAmount + (long long)(BLSCT_DEFAULT_FEE * (tx.vInputs.size() + 2))) break;
}

return tx.BuildTx(changeDestination, minStake, fUnstake);
CAmount subtract = 0;
bool fChangeNeeded = inAmount > nAmount;

if (type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE)
subtract = (BLSCT_DEFAULT_FEE * (tx.vInputs.size() + 1 + fChangeNeeded));

tx.AddOutput(destination, nAmount - subtract, sMemo, token_id, type, minStake);
}

return tx.BuildTx(changeDestination, minStake, type);
}

bool TxFactory::AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint, const bool& rbf)
Expand Down Expand Up @@ -180,18 +209,10 @@ TxFactory::BuildTx()
return TxFactoryBase::BuildTx(std::get<blsct::DoublePublicKey>(km->GetNewDestination(-1).value()));
}

std::optional<CMutableTransaction> TxFactory::CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const CreateOutputType& type, const CAmount& minStake, const bool& fUnstake)
void TxFactoryBase::AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector<InputCandidates>& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet)
{
LOCK(wallet->cs_wallet);

wallet::CoinFilterParams coins_params;
coins_params.min_amount = 0;
coins_params.only_blsct = true;
coins_params.include_staked_commitment = (fUnstake == true);
coins_params.token_id = tokenId;

std::vector<InputCandidates> inputCandidates;

for (const wallet::COutput& output : AvailableCoins(*wallet, nullptr, std::nullopt, coins_params).All()) {
auto tx = wallet->GetWalletTx(output.outpoint.hash);

Expand All @@ -201,10 +222,36 @@ std::optional<CMutableTransaction> TxFactory::CreateTransaction(wallet::CWallet*
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)});
inputCandidates.push_back({recoveredInfo.amount, 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>& inputCandidates)
{
wallet::CoinFilterParams coins_params;
coins_params.min_amount = 0;
coins_params.only_blsct = true;
coins_params.include_staked_commitment = (type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE);
coins_params.token_id = token_id;

AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates);

if (type == CreateTransactionType::STAKED_COMMITMENT) {
coins_params.include_staked_commitment = true;
AddAvailableCoins(wallet, blsct_km, coins_params, inputCandidates);
}
}

std::optional<CMutableTransaction> 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::vector<InputCandidates> inputCandidates;

TxFactoryBase::AddAvailableCoins(wallet, blsct_km, token_id, type, inputCandidates);

auto changeType = type == CreateTransactionType::STAKED_COMMITMENT_UNSTAKE ? STAKING_ACCOUNT : CHANGE_ACCOUNT;
auto changeAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(changeType).value());

return TxFactoryBase::CreateTransaction(inputCandidates, std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(fUnstake ? -2 : -1).value()), destination, nAmount, sMemo, tokenId, type, minStake, fUnstake);
return TxFactoryBase::CreateTransaction(inputCandidates, changeAddress, destination, nAmount, sMemo, token_id, type, minStake);
}

} // namespace blsct
15 changes: 9 additions & 6 deletions src/blsct/wallet/txfactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ struct InputCandidates {
CAmount amount;
MclScalar gamma;
blsct::PrivateKey spendingKey;
TokenId tokenId;
TokenId token_id;
COutPoint outpoint;
bool is_staked_commitment;
};

class TxFactoryBase
Expand All @@ -34,10 +35,12 @@ class TxFactoryBase
public:
TxFactoryBase(){};

void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const CreateOutputType& type = NORMAL, const CAmount& minStake = 0);
bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& tokenId, const COutPoint& outpoint, const bool& rbf = false);
std::optional<CMutableTransaction> BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const bool& fUnstake = false, const bool& fSubtractedFee = false);
static std::optional<CMutableTransaction> CreateTransaction(const std::vector<InputCandidates>& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const CreateOutputType& type = NORMAL, const CAmount& minStake = 0, const bool& fUnstake = false);
void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0);
bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& rbf = false);
std::optional<CMutableTransaction> BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false);
static std::optional<CMutableTransaction> CreateTransaction(const std::vector<InputCandidates>& 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 void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector<InputCandidates>& 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>& inputCandidates);
};

class TxFactory : public TxFactoryBase
Expand All @@ -51,7 +54,7 @@ class TxFactory : public TxFactoryBase
bool AddInput(wallet::CWallet* wallet, const COutPoint& outpoint, const bool& rbf = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
bool AddInput(const CCoinsViewCache& cache, const COutPoint& outpoint, const bool& rbf = false);
std::optional<CMutableTransaction> BuildTx();
static std::optional<CMutableTransaction> CreateTransaction(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const CreateOutputType& type = NORMAL, const CAmount& minStake = 0, const bool& fUnstake = false);
static std::optional<CMutableTransaction> 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);
};
} // namespace blsct

Expand Down
2 changes: 1 addition & 1 deletion src/blsct/wallet/txfactory_global.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Signature UnsignedOutput::GetSignature() const
return Signature::Aggregate(txSigs);
}

UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destKeys, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId, const Scalar& blindingKey, const CreateOutputType& type, const CAmount& minStake)
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<T> rp;
auto ret = UnsignedOutput();
Expand Down
7 changes: 4 additions & 3 deletions src/blsct/wallet/txfactory_global.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ struct Amounts {
CAmount nFromOutputs;
};

enum CreateOutputType {
enum CreateTransactionType {
NORMAL,
STAKED_COMMITMENT
STAKED_COMMITMENT,
STAKED_COMMITMENT_UNSTAKE
};

CTransactionRef
AggregateTransactions(const std::vector<CTransactionRef>& txs);
UnsignedOutput CreateOutput(const blsct::DoublePublicKey& destination, const CAmount& nAmount, std::string sMemo, const TokenId& tokenId = TokenId(), const Scalar& blindingKey = Scalar::Rand(), const CreateOutputType& type = NORMAL, const CAmount& minStake = 0);
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);
} // namespace blsct

#endif // TXFACTORY_GLOBAL_H
4 changes: 2 additions & 2 deletions src/test/blsct/pos/pos_chain_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ BOOST_FIXTURE_TEST_SUITE(pos_chain_tests, WalletTestingSetup)
Coin CreateCoin(const blsct::DoublePublicKey& recvAddress)
{
Coin coin;
auto out = blsct::CreateOutput(recvAddress, 1000 * COIN, "test", TokenId(), Scalar::Rand(), blsct::CreateOutputType::STAKED_COMMITMENT, 1000 * COIN);
auto out = blsct::CreateOutput(recvAddress, 1000 * COIN, "test", TokenId(), Scalar::Rand(), blsct::CreateTransactionType::STAKED_COMMITMENT, 1000 * COIN);
coin.nHeight = 1;
coin.out = out.out;
return coin;
Expand Down Expand Up @@ -58,7 +58,7 @@ BOOST_FIXTURE_TEST_CASE(StakedCommitment, TestBLSCTChain100Setup)
Coin coin2 = CreateCoin(recvAddress);
Coin coin3;

auto out3 = blsct::CreateOutput(recvAddress, 1000 * COIN, "test", TokenId(), Scalar::Rand(), blsct::CreateOutputType::STAKED_COMMITMENT, 999 * COIN);
auto out3 = blsct::CreateOutput(recvAddress, 1000 * COIN, "test", TokenId(), Scalar::Rand(), blsct::CreateTransactionType::STAKED_COMMITMENT, 999 * COIN);
coin3.nHeight = 1;
coin3.out = out3.out;

Expand Down
6 changes: 3 additions & 3 deletions src/wallet/rpc/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ UniValue SendBLSCTMoney(CWallet& wallet, std::vector<CBLSCTRecipient>& recipient
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());

// Send
auto outputType = recipients[0].fStakeCommitment ? blsct::CreateOutputType::STAKED_COMMITMENT : blsct::CreateOutputType::NORMAL;
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);
Expand Down Expand Up @@ -255,10 +255,10 @@ UniValue UnstakeBLSCT(CWallet& wallet, std::vector<CBLSCTRecipient>& recipients,
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());

// Send
auto outputType = blsct::CreateOutputType::NORMAL;
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, true);
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");
Expand Down
Loading

0 comments on commit 01b4f32

Please sign in to comment.