Skip to content

Commit 0514398

Browse files
committed
Merge #13339: wallet: Replace %w by wallet name in -walletnotify script
4e9efac test: Check wallet name in -walletnotify script (João Barbosa) 9a5b5ee wallet: Replace %w by wallet name in -walletnotify script (João Barbosa) Pull request description: Fixes #13237. ACKs for top commit: laanwj: ACK 4e9efac Tree-SHA512: 189dd1c785485f2e974d7c12531851b2a977778b3b954aa95efd527322ba3345924cfd587fb9c90b0fa979202af0ab2d90e53d125fe266a36c94f757e4176203
2 parents 263f53e + 4e9efac commit 0514398

File tree

5 files changed

+37
-4
lines changed

5 files changed

+37
-4
lines changed

src/util/system.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include <malloc.h>
6464
#endif
6565

66+
#include <boost/algorithm/string/replace.hpp>
6667
#include <thread>
6768
#include <typeinfo>
6869
#include <univalue.h>
@@ -1047,6 +1048,15 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
10471048
}
10481049
#endif
10491050

1051+
#ifndef WIN32
1052+
std::string ShellEscape(const std::string& arg)
1053+
{
1054+
std::string escaped = arg;
1055+
boost::replace_all(escaped, "'", "'\"'\"'");
1056+
return "'" + escaped + "'";
1057+
}
1058+
#endif
1059+
10501060
#if HAVE_SYSTEM
10511061
void runCommand(const std::string& strCommand)
10521062
{

src/util/system.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ fs::path GetConfigFile(const std::string& confPath);
8181
#ifdef WIN32
8282
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
8383
#endif
84+
#ifndef WIN32
85+
std::string ShellEscape(const std::string& arg);
86+
#endif
8487
#if HAVE_SYSTEM
8588
void runCommand(const std::string& strCommand);
8689
#endif

src/wallet/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ void WalletInit::AddWalletOptions() const
6262
gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
6363
gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
6464
#if HAVE_SYSTEM
65-
gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
65+
gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID and %w is replaced by wallet name. %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
6666
#endif
6767
gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
6868
gArgs.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup"

src/wallet/wallet.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
847847
if (!strCmd.empty())
848848
{
849849
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
850+
#ifndef WIN32
851+
// Substituting the wallet name isn't currently supported on windows
852+
// because windows shell escaping has not been implemented yet:
853+
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
854+
// A few ways it could be implemented in the future are described in:
855+
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
856+
boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
857+
#endif
850858
std::thread t(runCommand, strCmd);
851859
t.detach(); // thread runs free
852860
}

test/functional/feature_notifications.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,24 @@
1313
connect_nodes,
1414
)
1515

16+
# Linux allow all characters other than \x00
17+
# Windows disallow control characters (0-31) and /\?%:|"<>
18+
FILE_CHAR_START = 32 if os.name == 'nt' else 1
19+
FILE_CHAR_END = 128
20+
FILE_CHAR_BLACKLIST = '/\\?%*:|"<>' if os.name == 'nt' else '/'
21+
22+
23+
def notify_outputname(walletname, txid):
24+
return txid if os.name == 'nt' else '{}_{}'.format(walletname, txid)
25+
1626

1727
class NotificationsTest(BitcoinTestFramework):
1828
def set_test_params(self):
1929
self.num_nodes = 2
2030
self.setup_clean_chain = True
2131

2232
def setup_network(self):
33+
self.wallet = ''.join(chr(i) for i in range(FILE_CHAR_START, FILE_CHAR_END) if chr(i) not in FILE_CHAR_BLACKLIST)
2334
self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify")
2435
self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify")
2536
self.walletnotify_dir = os.path.join(self.options.tmpdir, "walletnotify")
@@ -33,7 +44,8 @@ def setup_network(self):
3344
"-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))],
3445
["-blockversion=211",
3546
"-rescan",
36-
"-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, '%s'))]]
47+
"-wallet={}".format(self.wallet),
48+
"-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s')))]]
3749
super().setup_network()
3850

3951
def run_test(self):
@@ -53,7 +65,7 @@ def run_test(self):
5365
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
5466

5567
# directory content should equal the generated transaction hashes
56-
txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
68+
txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
5769
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
5870
self.stop_node(1)
5971
for tx_file in os.listdir(self.walletnotify_dir):
@@ -67,7 +79,7 @@ def run_test(self):
6779
wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
6880

6981
# directory content should equal the generated transaction hashes
70-
txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count)))
82+
txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count)))
7183
assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir)))
7284

7385
# TODO: add test for `-alertnotify` large fork notifications

0 commit comments

Comments
 (0)