Skip to content

Commit

Permalink
Parallel Sapling note trial decryption, can be toggled by -asyncnoted…
Browse files Browse the repository at this point in the history
…ecryption option, enabled by default
  • Loading branch information
miodragpop committed Sep 23, 2022
1 parent 53cb498 commit 5803ce2
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/init.cpp
Expand Up @@ -396,6 +396,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-forcebirthday", strprintf(_("Use alternative \"wallet birthday\" Unix timestamp (default: %u)"), 0));
strUsage += HelpMessageOpt("-ignorespam", strprintf(_("Ignore txes with more than or equal to -spamoutputsmin Sapling outputs (default: %u)"), DEFAULT_IGNORE_SPAM));
strUsage += HelpMessageOpt("-spamoutputsmin", strprintf(_("Minimum Sapling outputs count to consider tx a spam (default: %u)"), DEFAULT_SPAM_OUTPUTS_MIN));
strUsage += HelpMessageOpt("-asyncnotedecryption", strprintf(_("Option to toggle parallel Sapling note trial decryption (default: %u)"), DEFAULT_ASYNC_NOTE_DECRYPTION));
strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files on startup"));
#ifndef WIN32
strUsage += HelpMessageOpt("-sysperms", _("Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)"));
Expand Down Expand Up @@ -1130,6 +1131,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("Transactions with >= %i Sapling outputs would be considered a spam\n", nSpamOutputsMin);
}

fAsyncNoteDecryption = GetBoolArg("-asyncnotedecryption", DEFAULT_ASYNC_NOTE_DECRYPTION);
LogPrintf("Asynchronous Sapling note trial decryption is %s\n", fAsyncNoteDecryption ? "enabled" : "disabled");

LogPrintf("Using LevelDB version %i.%i\n", leveldb::kMajorVersion, leveldb::kMinorVersion);

RegisterAllCoreRPCCommands(tableRPC);
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Expand Up @@ -105,6 +105,8 @@ int64_t nForceBirthday = 0;
bool fIgnoreSpam = DEFAULT_IGNORE_SPAM;
int nSpamOutputsMin = DEFAULT_SPAM_OUTPUTS_MIN;

bool fAsyncNoteDecryption = DEFAULT_ASYNC_NOTE_DECRYPTION;

std::optional<unsigned int> expiryDeltaArg = std::nullopt;

CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
Expand Down
5 changes: 5 additions & 0 deletions src/main.h
Expand Up @@ -144,6 +144,9 @@ static const bool DEFAULT_IGNORE_SPAM = false;
/** Default for -spamoutputsmin */
static const int DEFAULT_SPAM_OUTPUTS_MIN = 5;

/** Default for -asyncnotedecryption */
static const bool DEFAULT_ASYNC_NOTE_DECRYPTION = true;

#define equihash_parameters_acceptable(N, K) \
((CBlockHeader::HEADER_SIZE + equihash_solution_size(N, K))*MAX_HEADERS_RESULTS < \
MAX_PROTOCOL_MESSAGE_LENGTH-1000)
Expand Down Expand Up @@ -242,6 +245,8 @@ extern int64_t nForceBirthday;
extern bool fIgnoreSpam;
extern int nSpamOutputsMin;

extern bool fAsyncNoteDecryption;

/** Register with a network node to receive its signals */
void RegisterNodeSignals(CNodeSignals& nodeSignals);
/** Unregister a network node */
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -2880,6 +2880,9 @@ UniValue zc_benchmark(const UniValue& params, bool fHelp)
} else if (benchmarktype == "trydecryptsaplingnotes") {
int nKeys = params[2].get_int();
sample_times.push_back(benchmark_try_decrypt_sapling_notes(nKeys));
} else if (benchmarktype == "tryasyncdecryptsaplingnotes") {
int nKeys = params[2].get_int();
sample_times.push_back(benchmark_try_async_decrypt_sapling_notes(nKeys));
} else if (benchmarktype == "incnotewitnesses") {
int nTxs = params[2].get_int();
sample_times.push_back(benchmark_increment_sprout_note_witnesses(nTxs));
Expand Down
100 changes: 99 additions & 1 deletion src/wallet/wallet.cpp
Expand Up @@ -2844,7 +2844,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
auto sproutNoteData = FindMySproutNotes(tx);
auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx, nHeight);
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> saplingNoteDataAndAddressesToAdd;
saplingNoteDataAndAddressesToAdd = fAsyncNoteDecryption ? FindMySaplingNotesAsync(tx, nHeight) : FindMySaplingNotes(tx, nHeight);
auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first;
auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second;
for (const auto &addressToAdd : addressesToAdd) {
Expand Down Expand Up @@ -3098,6 +3099,103 @@ std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySap
return std::make_pair(noteData, viewingKeysToAdd);
}

static std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> DecryptSaplingNoteWorker(const Consensus::Params &consensus_params, const SaplingIncomingViewingKey &ivk, const OutputDescription &outdesc, const int &height, const uint256 &hash, const uint32_t &i)
{
mapSaplingNoteData_t noteData;
SaplingIncomingViewingKeyMap viewingKeysToAdd;

auto result = SaplingNotePlaintext::decrypt(consensus_params, height, outdesc.encCiphertext, ivk, outdesc.ephemeralKey, outdesc.cmu);
if (result)
{
// We don't cache the nullifier here as computing it requires knowledge of the note position
// in the commitment tree, which can only be determined when the transaction has been mined.
SaplingOutPoint op {hash, i};
SaplingNoteData nd;
nd.ivk = ivk;
noteData.insert(std::make_pair(op, nd));

auto address = ivk.address(result.value().d);
if (address)
{
viewingKeysToAdd.insert(make_pair(address.value(), ivk));
}
}

return std::make_pair(noteData, viewingKeysToAdd);
}

/**
* Finds all output notes in the given transaction that have been sent to
* SaplingPaymentAddresses in this wallet.
*
* It should never be necessary to call this method with a CWalletTx, because
* the result of FindMySaplingNotes (for the addresses available at the time) will
* already have been cached in CWalletTx.mapSaplingNoteData.
*/
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySaplingNotesAsync(const CTransaction &tx, int height) const
{
mapSaplingNoteData_t noteData;
SaplingIncomingViewingKeyMap viewingKeysToAdd;

if (tx.vShieldedOutput.empty())
{
return std::make_pair(noteData, viewingKeysToAdd);
}

LOCK(cs_KeyStore);

std::set<SaplingIncomingViewingKey> setSaplingIvkToTest;

for (const auto& [ivk, fvk] : mapSaplingFullViewingKeys)
{
setSaplingIvkToTest.insert(ivk);
}

for (const auto& [address, ivk] : mapSaplingIncomingViewingKeys)
{
setSaplingIvkToTest.insert(ivk);
}

if (setSaplingIvkToTest.empty())
{
return std::make_pair(noteData, viewingKeysToAdd);
}

std::vector<std::future<std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap>>> vDecryptFutures;
const Consensus::Params consensus_params = Params().GetConsensus();
uint256 hash = tx.GetHash();

// Protocol Spec: 4.19 Block Chain Scanning (Sapling)
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); ++i)
{
const OutputDescription output = tx.vShieldedOutput[i];
for (SaplingIncomingViewingKey ivk : setSaplingIvkToTest)
{
vDecryptFutures.emplace_back(std::async(std::launch::async, DecryptSaplingNoteWorker, consensus_params, ivk, output, height, hash, i));
}
}

for (auto &fut : vDecryptFutures)
{
auto result_pair = fut.get();
if (!result_pair.first.empty())
{
noteData.insert(result_pair.first.begin(), result_pair.first.end());

for (auto [address, ivk] : result_pair.second)
{
if (mapSaplingIncomingViewingKeys.count(address) == 0)
{
viewingKeysToAdd[address] = ivk;
}
}

}
}

return std::make_pair(noteData, viewingKeysToAdd);
}

bool CWallet::IsSproutNullifierFromMe(const uint256& nullifier) const
{
{
Expand Down
1 change: 1 addition & 0 deletions src/wallet/wallet.h
Expand Up @@ -1382,6 +1382,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
uint8_t n) const;
mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const;
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx, int height) const;
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotesAsync(const CTransaction& tx, int height) const;
bool IsSproutNullifierFromMe(const uint256& nullifier) const;
bool IsSaplingNullifierFromMe(const uint256& nullifier) const;

Expand Down
25 changes: 25 additions & 0 deletions src/zcbenchmarks.cpp
Expand Up @@ -313,6 +313,31 @@ double benchmark_try_decrypt_sapling_notes(size_t nKeys)
return timer_stop(tv_start);
}

double benchmark_try_async_decrypt_sapling_notes(size_t nKeys)
{
// Set params
const Consensus::Params& consensusParams = Params().GetConsensus();

auto masterKey = GetTestMasterSaplingSpendingKey();

CWallet wallet;

for (int i = 0; i < nKeys; i++) {
auto sk = masterKey.Derive(i);
wallet.AddSaplingSpendingKey(sk);
}

// Generate a key that has not been added to the wallet
auto sk = masterKey.Derive(nKeys);
auto tx = GetValidSaplingReceive(consensusParams, wallet, sk, 10);

struct timeval tv_start;
timer_start(tv_start);
auto noteDataMapAndAddressesToAdd = wallet.FindMySaplingNotesAsync(tx, 1);
assert(noteDataMapAndAddressesToAdd.first.empty());
return timer_stop(tv_start);
}

CWalletTx CreateSproutTxWithNoteData(const libzcash::SproutSpendingKey& sk) {
auto wtx = GetValidSproutReceive(sk, 10, true);
auto note = GetSproutNote(sk, wtx, 0, 1);
Expand Down
1 change: 1 addition & 0 deletions src/zcbenchmarks.h
Expand Up @@ -14,6 +14,7 @@ extern double benchmark_verify_equihash();
extern double benchmark_large_tx(size_t nInputs);
extern double benchmark_try_decrypt_sprout_notes(size_t nAddrs);
extern double benchmark_try_decrypt_sapling_notes(size_t nAddrs);
extern double benchmark_try_async_decrypt_sapling_notes(size_t nAddrs);
extern double benchmark_increment_sprout_note_witnesses(size_t nTxs);
extern double benchmark_increment_sapling_note_witnesses(size_t nTxs);
extern double benchmark_connectblock_slow();
Expand Down

0 comments on commit 5803ce2

Please sign in to comment.