diff --git a/configure.ac b/configure.ac index a90ec9bbf..1ee410b6a 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 21) define(_CLIENT_VERSION_REVISION, 2) -define(_CLIENT_VERSION_BUILD, 0) +define(_CLIENT_VERSION_BUILD, 1) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2022) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index 257474e00..44345e398 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2017 Wladimir J. van der Laan +# Copyright (c) 2014-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' @@ -13,19 +13,14 @@ argument: These files must consist of lines in the format - : - [] []: - .onion - 0xDDBBCCAA (IPv4 little-endian old pnSeeds format) + .onion: + .b32.i2p: The output will be two data structures with the peers in binary format: - static SeedSpec6 pnSeed6_main[]={ - ... - } - static SeedSpec6 pnSeed6_test[]={ + static const uint8_t chainparams_seed_{main,test}[]={ ... } @@ -33,25 +28,40 @@ These should be pasted into `src/chainparamsseeds.h`. ''' from base64 import b32decode -from binascii import a2b_hex +from enum import Enum +import struct import sys import os import re -# ipv4 in ipv6 prefix -pchIPv4 = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff]) -# tor-specific ipv6 prefix -pchOnionCat = bytearray([0xFD,0x87,0xD8,0x7E,0xEB,0x43]) +class BIP155Network(Enum): + IPV4 = 1 + IPV6 = 2 + TORV2 = 3 # no longer supported + TORV3 = 4 + I2P = 5 + CJDNS = 6 -def name_to_ipv6(addr): - if len(addr)>6 and addr.endswith('.onion'): +def name_to_bip155(addr): + '''Convert address string to BIP155 (networkID, addr) tuple.''' + if addr.endswith('.onion'): vchAddr = b32decode(addr[0:-6], True) - if len(vchAddr) != 16-len(pchOnionCat): + if len(vchAddr) == 35: + assert vchAddr[34] == 3 + return (BIP155Network.TORV3, vchAddr[:32]) + elif len(vchAddr) == 10: + return (BIP155Network.TORV2, vchAddr) + else: raise ValueError('Invalid onion %s' % vchAddr) - return pchOnionCat + vchAddr + elif addr.endswith('.b32.i2p'): + vchAddr = b32decode(addr[0:-8] + '====', True) + if len(vchAddr) == 32: + return (BIP155Network.I2P, vchAddr) + else: + raise ValueError(f'Invalid I2P {vchAddr}') elif '.' in addr: # IPv4 - return pchIPv4 + bytearray((int(x) for x in addr.split('.'))) - elif ':' in addr: # IPv6 + return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) + elif ':' in addr: # IPv6 or CJDNS sub = [[], []] # prefix, suffix x = 0 addr = addr.split(':') @@ -67,13 +77,19 @@ def name_to_ipv6(addr): sub[x].append(val & 0xff) nullbytes = 16 - len(sub[0]) - len(sub[1]) assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)) - return bytearray(sub[0] + ([0] * nullbytes) + sub[1]) - elif addr.startswith('0x'): # IPv4-in-little-endian - return pchIPv4 + bytearray(reversed(a2b_hex(addr[2:]))) + addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1]) + if addr_bytes[0] == 0xfc: + # Assume that seeds with fc00::/8 addresses belong to CJDNS, + # not to the publicly unroutable "Unique Local Unicast" network, see + # RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8 + return (BIP155Network.CJDNS, addr_bytes) + else: + return (BIP155Network.IPV6, addr_bytes) else: raise ValueError('Could not parse address %s' % addr) -def parse_spec(s, defaultport): +def parse_spec(s): + '''Convert endpoint string to BIP155 (networkID, addr, port) tuple.''' match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) if match: # ipv6 host = match.group(1) @@ -85,17 +101,42 @@ def parse_spec(s, defaultport): (host,_,port) = s.partition(':') if not port: - port = defaultport + port = 0 else: port = int(port) - host = name_to_ipv6(host) + host = name_to_bip155(host) - return (host,port) + if host[0] == BIP155Network.TORV2: + return None # TORV2 is no longer supported, so we ignore it + else: + return host + (port, ) -def process_nodes(g, f, structname, defaultport): - g.write('static SeedSpec6 %s[] = {\n' % structname) - first = True +def ser_compact_size(l): + r = b"" + if l < 253: + r = struct.pack("B", l) + elif l < 0x10000: + r = struct.pack("H', spec[2]) + return r + +def process_nodes(g, f, structname): + g.write('static const uint8_t %s[] = {\n' % structname) for line in f: comment = line.find('#') if comment != -1: @@ -103,14 +144,14 @@ def process_nodes(g, f, structname, defaultport): line = line.strip() if not line: continue - if not first: - g.write(',\n') - first = False - (host,port) = parse_spec(line, defaultport) - hoststr = ','.join(('0x%02x' % b) for b in host) - g.write(' {{%s}, %i}' % (hoststr, port)) - g.write('\n};\n') + spec = parse_spec(line) + if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2) + continue + blob = bip155_serialize(spec) + hoststr = ','.join(('0x%02x' % b) for b in blob) + g.write(f' {hoststr},\n') + g.write('};\n') def main(): if len(sys.argv)<2: @@ -121,17 +162,16 @@ def main(): g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n') g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n') g.write('/**\n') - g.write(' * List of fixed seed nodes for the litecoin network\n') + g.write(' * List of fixed seed nodes for the bitcoin network\n') g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n') g.write(' *\n') - g.write(' * Each line contains a 16-byte IPv6 address and a port.\n') - g.write(' * IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly.\n') + g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n') g.write(' */\n') with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: - process_nodes(g, f, 'pnSeed6_main', 9333) + process_nodes(g, f, 'chainparams_seed_main') g.write('\n') with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: - process_nodes(g, f, 'pnSeed6_test', 19335) + process_nodes(g, f, 'chainparams_seed_test') g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n') if __name__ == '__main__': diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index 26c7aec77..a7b1cd9ad 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -12,4 +12,6 @@ 154.22.123.138:9333 174.62.70.234:9333 178.62.46.195:9333 -213.244.192.149:9333 \ No newline at end of file +213.244.192.149:9333 +xtmsopiy2ssu2vumfmnjq3nyflnq2pkpcxndi6rxgwnkljmzijafozqd.onion:9333 +37am23y5vro3iorqlmjjty7ctsgz6myoaushxrfqct4ngakl7d7idgid.onion:9333 \ No newline at end of file diff --git a/contrib/seeds/nodes_test.txt b/contrib/seeds/nodes_test.txt index e8d57265d..b1d909ef2 100644 --- a/contrib/seeds/nodes_test.txt +++ b/contrib/seeds/nodes_test.txt @@ -27,3 +27,5 @@ 202.238.193.15:19335 203.216.0.105:19335 212.83.174.255:19335 +mxk7vkfxvtap2aikbjqcx7b4eex2yiag5v5vhuljnvurpybv4a6q5cqd.onion:19335 +octnr6yzgosiupuuivgla67p3ml6pc5qpozqn7zpmpbxge6pq5iq4had.onion:19335 \ No newline at end of file diff --git a/doc/build-osx.md b/doc/build-osx.md index 7c90cc52a..4143c46f2 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -19,7 +19,7 @@ Then install [Homebrew](https://brew.sh). ## Dependencies ```shell -brew install automake libtool boost miniupnpc pkg-config python qt libevent qrencode +brew install automake libtool boost miniupnpc pkg-config python qt libevent qrencode fmt ``` If you run into issues, check [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting). diff --git a/doc/build-unix.md b/doc/build-unix.md index 652b3dc0a..b40edb7d2 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -81,7 +81,7 @@ Build requirements: Now, you can either build from self-compiled [depends](/depends/README.md) or install the required dependencies: - sudo apt-get install libevent-dev libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev + sudo apt-get install libevent-dev libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libfmt-dev BerkeleyDB is required for the wallet. @@ -131,7 +131,7 @@ built by default. Build requirements: - sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3 + sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3 fmt Optional (see `--with-miniupnpc` and `--enable-upnp-default`): diff --git a/doc/litecoin-release-notes/release-notes-0.21.2.1.md b/doc/litecoin-release-notes/release-notes-0.21.2.1.md new file mode 100644 index 000000000..7723ceb23 --- /dev/null +++ b/doc/litecoin-release-notes/release-notes-0.21.2.1.md @@ -0,0 +1,21 @@ +Litecoin Core version 0.21.2.1 is now available from: + + . + +This includes a critical bug fix for upgraded wallets to receive via MWEB. + +Please report bugs using the issue tracker at GitHub: + + + +To receive security and update notifications, please subscribe to: + + + +Notable changes +=============== + +An issue with MWEB key generation for older wallets that were upgraded was solved. +Keys are now generated from the appropriate keypools, and coins sent to previously generated stealth addresses are recoverable. +Use `rescanblockchain` after upgrading to recover any missing MWEB coins. + diff --git a/doc/release-notes-litecoin.md b/doc/release-notes-litecoin.md index e41add219..7723ceb23 100644 --- a/doc/release-notes-litecoin.md +++ b/doc/release-notes-litecoin.md @@ -1,8 +1,8 @@ -Litecoin Core version 0.21.2 is now available from: +Litecoin Core version 0.21.2.1 is now available from: - . + . -This is the largest update ever, providing full node, wallet, and mining support for MWEB. +This includes a critical bug fix for upgraded wallets to receive via MWEB. Please report bugs using the issue tracker at GitHub: @@ -12,109 +12,10 @@ To receive security and update notifications, please subscribe to: - -How to upgrade: -============== - -Firstly, thank you for running Litecoin Core and helping secure the network! - -As you’re running an older version of Litecoin Core, shut it down. Wait until it’s completely shut down - which might take a few minutes for older versions - then follow these simple steps: -For Windows: simply run the installer -For Mac: copy over to `/Applications/Litecoin-Qt` -For Linux: copy cover `litecoind`/`litecoin-qt`. - -NB: upgrading directly from an ‘end of life’ version of Litecoin Core is possible, but it might take a while if the data directory needs to be migrated. Old wallet versions of Litecoin Core are generally supported. - - -Compatibility: -============== - -Litecoin Core is supported and extensively tested on operating systems using the Linux kernel, macOS 10.10+, Windows 7 and newer. It’s not recommended to use Litecoin Core on unsupported systems. - -Litecoin Core should also work on most other Unix-like systems, but is not as frequently tested on them. - -MWEB fields added to BlockIndex, and block serialization format has changed. Downgrading to older versions is unsafe. - -If upgrading to 0.21.2 *after* MWEB has activated, you must resync to download MWEB blocks. - Notable changes =============== -Consensus changes ------------------ +An issue with MWEB key generation for older wallets that were upgraded was solved. +Keys are now generated from the appropriate keypools, and coins sent to previously generated stealth addresses are recoverable. +Use `rescanblockchain` after upgrading to recover any missing MWEB coins. -- This release implements the proposed MWEB consensus rules - ([LIP002](https://github.com/litecoin-project/lips/blob/master/lip-0002.mediawiki), - [LIP003](https://github.com/litecoin-project/lips/blob/master/lip-0003.mediawiki), and - [LIP004](https://github.com/litecoin-project/lips/blob/master/lip-0004.mediawiki)) - -P2P and network changes ------------------------ - -- A new service flag, NODE_MWEB (1 << 24), was added to signal to peers that the node supports MWEB. - When connected peers both advertise this capability, they are expected to provide all MWEB data when - sharing transactions, blocks, and compact blocks with each other. - -- Nodes now announce compact block version 3 support, informing peers that they can provide MWEB data - in compact blocks. - - -Updated RPCs ------------- - -- `getblockheader` now returns an additional `mweb_header` field containing - all of the MWEB header data, and an `mweb_amount` field containing the total - number of coins pegged-in to the MWEB after applying the block. - -- `getblock` now returns an additional `mweb` field containing MWEB header info, - and all of the inputs, outputs, and kernels in the MWEB block. - -- Added `mwebweight`, `descendantmwebweight`, `ancestormwebweight`, and `mweb` - fields to `getrawmempool`, `getmempoolancestors`, `getmempooldescendants`, - and `getmempoolentry`. - -- Added new fields to describe MWEB transaction inputs, outputs, and kernels - for `getrawtransaction`. - -Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. - -New settings ------------- - -- Added "fMWEBFeatures" option for enabling the new "Advanced MWEB Features" - control. - -Wallet Database ---------------- - -- Added "mweb_coin" type which stores MWEB coins and their derived keys. - -- Added CHDChain version 4 which includes an MWEB key index counter and - the stealth address scan key. - -- Added CKeyMetadata version 14 which includes the MWEB key index. - -- Added FEATURE_MWEB = 210000 minimum database version. - -Wallet RPC changes ------------------- - -- Added 'listwallettransactions' which matches the transaction list display values. - -GUI changes ------------ - -- Added an "Advanced MWEB Features" control for testing. It’s only available - when the "-debug" argument is supplied, and the option is turned on in the - settings dialog. - - -Credits -======= - -Thanks to everyone who directly contributed to this release: - -- [The Bitcoin Core Developers](https://github.com/bitcoin/bitcoin/tree/master/doc/release-notes) -- DavidBurkett -- hectorchu -- losh11 \ No newline at end of file diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 9892ae0f7..cd4290154 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -139,7 +139,7 @@ public: bech32_hrp = "ltc"; mweb_hrp = "ltcmweb"; - vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); + vFixedSeeds = std::vector(std::begin(chainparams_seed_main), std::end(chainparams_seed_main)); fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -249,7 +249,7 @@ public: bech32_hrp = "tltc"; mweb_hrp = "tmweb"; - vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); + vFixedSeeds = std::vector(std::begin(chainparams_seed_test), std::end(chainparams_seed_test)); fDefaultConsistencyChecks = false; fRequireStandard = false; @@ -260,6 +260,7 @@ public: { {300, uint256S("54e6075affe658d6574e04c9245a7920ad94dc5af8f5b37fd9a094e317769740")}, {2056, uint256S("17748a31ba97afdc9a4f86837a39d287e3e7c7290a08a1d816c5969c78a83289")}, + {2352616, uint256S("7540437e7bf7831fa872ba8cfae85951a1e5dbb04c201b6f5def934d9299f3c2")} } }; diff --git a/src/chainparams.h b/src/chainparams.h index a48fbd853..ffeb1f30a 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -90,7 +90,7 @@ public: const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP() const { return bech32_hrp; } const std::string& MWEB_HRP() const { return mweb_hrp; } - const std::vector& FixedSeeds() const { return vFixedSeeds; } + const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } const ChainTxData& TxData() const { return chainTxData; } protected: @@ -108,7 +108,7 @@ protected: std::string mweb_hrp; std::string strNetworkID; CBlock genesis; - std::vector vFixedSeeds; + std::vector vFixedSeeds; bool fDefaultConsistencyChecks; bool fRequireStandard; bool m_is_test_chain; diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 47f019fad..143f2f8ca 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -1,59 +1,62 @@ #ifndef BITCOIN_CHAINPARAMSSEEDS_H #define BITCOIN_CHAINPARAMSSEEDS_H /** - * List of fixed seed nodes for the litecoin network + * List of fixed seed nodes for the bitcoin network * AUTOGENERATED by contrib/seeds/generate-seeds.py * - * Each line contains a 16-byte IPv6 address and a port. - * IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly. + * Each line contains a BIP155 serialized (networkID, addr, port) tuple. */ -static SeedSpec6 pnSeed6_main[] = { - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x08,0xd6,0x9a,0x19}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x27,0x68,0xa3,0xc8}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x2f,0x5a,0x89,0x79}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x2f,0xfd,0x39,0xd7}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3e,0xd7,0x7f,0x49}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x45,0xa4,0xc4,0xef}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x46,0x3f,0xaa,0x56}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x55,0x02,0x44,0xfa}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x56,0x0a,0x6e,0x8f}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x68,0x03,0x26,0xf7}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x8f,0xb2,0x39,0x03}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x9a,0x16,0x7b,0x8a}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xae,0x3e,0x46,0xea}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb2,0x3e,0x2e,0xc3}, 9333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xd5,0xf4,0xc0,0x95}, 9333} +static const uint8_t chainparams_seed_main[] = { + 0x01,0x04,0x08,0xd6,0x9a,0x19,0x24,0x75, + 0x01,0x04,0x27,0x68,0xa3,0xc8,0x24,0x75, + 0x01,0x04,0x2f,0x5a,0x89,0x79,0x24,0x75, + 0x01,0x04,0x2f,0xfd,0x39,0xd7,0x24,0x75, + 0x01,0x04,0x3e,0xd7,0x7f,0x49,0x24,0x75, + 0x01,0x04,0x45,0xa4,0xc4,0xef,0x24,0x75, + 0x01,0x04,0x46,0x3f,0xaa,0x56,0x24,0x75, + 0x01,0x04,0x55,0x02,0x44,0xfa,0x24,0x75, + 0x01,0x04,0x56,0x0a,0x6e,0x8f,0x24,0x75, + 0x01,0x04,0x68,0x03,0x26,0xf7,0x24,0x75, + 0x01,0x04,0x8f,0xb2,0x39,0x03,0x24,0x75, + 0x01,0x04,0x9a,0x16,0x7b,0x8a,0x24,0x75, + 0x01,0x04,0xae,0x3e,0x46,0xea,0x24,0x75, + 0x01,0x04,0xb2,0x3e,0x2e,0xc3,0x24,0x75, + 0x01,0x04,0xd5,0xf4,0xc0,0x95,0x24,0x75, + 0x04,0x20,0xbc,0xd9,0x27,0x3d,0x18,0xd4,0xa5,0x4d,0x56,0x8c,0x2b,0x1a,0x98,0x6d,0xb8,0x2a,0xdb,0x0d,0x3d,0x4f,0x15,0xda,0x34,0x7a,0x37,0x35,0x9a,0xa5,0xa5,0x99,0x42,0x40,0x24,0x75, + 0x04,0x20,0xdf,0xc0,0xcd,0x6f,0x1d,0xac,0x5d,0xb4,0x3a,0x30,0x5b,0x12,0x99,0xe3,0xe2,0x9c,0x8d,0x9f,0x33,0x0e,0x05,0x24,0x7b,0xc4,0xb0,0x14,0xf8,0xd3,0x01,0x4b,0xf8,0xfe,0x24,0x75, }; -static SeedSpec6 pnSeed6_test[] = { - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x03,0x89,0x44,0x8c}, 49333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x05,0x09,0x96,0x70}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x25,0x3b,0x39,0x60}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x27,0x64,0x63,0x74}, 30008}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x33,0x0f,0x73,0x61}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x34,0x0d,0x2a,0x7d}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x36,0xff,0x80,0x6e}, 19333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x3e,0xab,0xa1,0xcb}, 19333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x40,0xe3,0x13,0x5c}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x4e,0x1c,0xe1,0xa0}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x4f,0x62,0x9f,0x07}, 19333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x54,0x26,0x03,0xc7}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x59,0x27,0x68,0xa7}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x59,0xa0,0x9f,0x2d}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x5e,0x4f,0x37,0x1c}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x5f,0xd8,0x4c,0xe0}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x64,0x19,0x78,0x9a}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x67,0xe7,0xbf,0x07}, 19333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x68,0xed,0x83,0x8a}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x8e,0x5d,0xc6,0x68}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x93,0x87,0x0b,0x7c}, 49333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xad,0xd1,0x28,0x3d}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xad,0xd1,0x2a,0x07}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb2,0x3e,0x2e,0xc3}, 19333}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0xb4,0xdd,0xc9}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xc6,0x3a,0x66,0x12}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xca,0xee,0xc1,0x0f}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xcb,0xd8,0x00,0x69}, 19335}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xd4,0x53,0xae,0xff}, 19335} +static const uint8_t chainparams_seed_test[] = { + 0x01,0x04,0x03,0x89,0x44,0x8c,0xc0,0xb5, + 0x01,0x04,0x05,0x09,0x96,0x70,0x4b,0x87, + 0x01,0x04,0x25,0x3b,0x39,0x60,0x4b,0x87, + 0x01,0x04,0x27,0x64,0x63,0x74,0x75,0x38, + 0x01,0x04,0x33,0x0f,0x73,0x61,0x4b,0x87, + 0x01,0x04,0x34,0x0d,0x2a,0x7d,0x4b,0x87, + 0x01,0x04,0x36,0xff,0x80,0x6e,0x4b,0x85, + 0x01,0x04,0x3e,0xab,0xa1,0xcb,0x4b,0x85, + 0x01,0x04,0x40,0xe3,0x13,0x5c,0x4b,0x87, + 0x01,0x04,0x4e,0x1c,0xe1,0xa0,0x4b,0x87, + 0x01,0x04,0x4f,0x62,0x9f,0x07,0x4b,0x85, + 0x01,0x04,0x54,0x26,0x03,0xc7,0x4b,0x87, + 0x01,0x04,0x59,0x27,0x68,0xa7,0x4b,0x87, + 0x01,0x04,0x59,0xa0,0x9f,0x2d,0x4b,0x87, + 0x01,0x04,0x5e,0x4f,0x37,0x1c,0x4b,0x87, + 0x01,0x04,0x5f,0xd8,0x4c,0xe0,0x4b,0x87, + 0x01,0x04,0x64,0x19,0x78,0x9a,0x4b,0x87, + 0x01,0x04,0x67,0xe7,0xbf,0x07,0x4b,0x85, + 0x01,0x04,0x68,0xed,0x83,0x8a,0x4b,0x87, + 0x01,0x04,0x8e,0x5d,0xc6,0x68,0x4b,0x87, + 0x01,0x04,0x93,0x87,0x0b,0x7c,0xc0,0xb5, + 0x01,0x04,0xad,0xd1,0x28,0x3d,0x4b,0x87, + 0x01,0x04,0xad,0xd1,0x2a,0x07,0x4b,0x87, + 0x01,0x04,0xb2,0x3e,0x2e,0xc3,0x4b,0x85, + 0x01,0x04,0xb9,0xb4,0xdd,0xc9,0x4b,0x87, + 0x01,0x04,0xc6,0x3a,0x66,0x12,0x4b,0x87, + 0x01,0x04,0xca,0xee,0xc1,0x0f,0x4b,0x87, + 0x01,0x04,0xcb,0xd8,0x00,0x69,0x4b,0x87, + 0x01,0x04,0xd4,0x53,0xae,0xff,0x4b,0x87, + 0x04,0x20,0x65,0xd5,0xfa,0xa8,0xb7,0xac,0xc0,0xfd,0x01,0x0a,0x0a,0x60,0x2b,0xfc,0x3c,0x21,0x2f,0xac,0x20,0x06,0xed,0x7b,0x53,0xd1,0x69,0x6d,0x69,0x17,0xe0,0x35,0xe0,0x3d,0x4b,0x87, + 0x04,0x20,0x70,0xa6,0xd8,0xfb,0x19,0x33,0xa4,0x8a,0x3e,0x94,0x45,0x4c,0xb0,0x7b,0xef,0xdb,0x17,0xe7,0x8b,0xb0,0x7b,0xb3,0x06,0xff,0x2f,0x63,0xc3,0x73,0x13,0xcf,0x87,0x51,0x4b,0x87, }; #endif // BITCOIN_CHAINPARAMSSEEDS_H diff --git a/src/libmw/include/mw/models/crypto/PublicKey.h b/src/libmw/include/mw/models/crypto/PublicKey.h index 28d1c33de..ea8750b63 100644 --- a/src/libmw/include/mw/models/crypto/PublicKey.h +++ b/src/libmw/include/mw/models/crypto/PublicKey.h @@ -3,6 +3,8 @@ #include #include #include +#include + #include class PublicKey : @@ -45,6 +47,7 @@ public: static PublicKey Random(); const BigInt<33>& GetBigInt() const { return m_compressed; } + CKeyID GetID() const { return CPubKey(vec()).GetID(); } std::array array() const { return m_compressed.ToArray(); } const std::vector& vec() const { return m_compressed.vec(); } const uint8_t& operator[](const size_t x) const { return m_compressed[x]; } diff --git a/src/libmw/include/mw/models/wallet/Coin.h b/src/libmw/include/mw/models/wallet/Coin.h index 8b3249639..b006425ea 100644 --- a/src/libmw/include/mw/models/wallet/Coin.h +++ b/src/libmw/include/mw/models/wallet/Coin.h @@ -20,6 +20,13 @@ static constexpr uint32_t CHANGE_INDEX{0}; /// static constexpr uint32_t PEGIN_INDEX{1}; +/// +/// Outputs sent to a stealth address whose spend key was not generated using the MWEB +/// keychain won't have an address index. We use 0xfffffffe to represent this. +/// In that case, we must lookup the secret key in the wallet DB, rather than the MWEB keychain. +/// +static constexpr uint32_t CUSTOM_KEY{std::numeric_limits::max() - 1}; + /// /// Outputs sent to others will be marked with an address_index of 0xffffffff. /// @@ -35,7 +42,8 @@ struct Coin : public Traits::ISerializable { uint32_t address_index{UNKNOWN_INDEX}; // The private key needed in order to spend the coin. - // May be empty for watch-only wallets. + // Will be empty for watch-only wallets. + // May be empty for locked wallets. Upon unlock, spend_key will get populated. boost::optional spend_key; // The blinding factor of the coin's output. diff --git a/src/libmw/include/mw/wallet/Keychain.h b/src/libmw/include/mw/wallet/Keychain.h index 28e2b945b..074fac27e 100644 --- a/src/libmw/include/mw/wallet/Keychain.h +++ b/src/libmw/include/mw/wallet/Keychain.h @@ -7,7 +7,7 @@ #include // Forward Declarations -class ScriptPubKeyMan; +class LegacyScriptPubKeyMan; MW_NAMESPACE @@ -16,7 +16,7 @@ class Keychain public: using Ptr = std::shared_ptr; - Keychain(const ScriptPubKeyMan& spk_man, SecretKey scan_secret, SecretKey spend_secret) + Keychain(const LegacyScriptPubKeyMan& spk_man, SecretKey scan_secret, SecretKey spend_secret) : m_spk_man(spk_man), m_scanSecret(std::move(scan_secret)), m_spendSecret(std::move(spend_secret)) { } @@ -28,9 +28,11 @@ public: // used to calculate the spend key when the wallet becomes unlocked. bool RewindOutput(const Output& output, mw::Coin& coin) const; - // Calculates the output spend key for the address index and shared secret. - // Requires that keychain be unlocked and not watch-only. - SecretKey CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const; + // Calculates the output secret key for the given coin. + // If the address index is known, it calculates from the keychain's master spend key. + // If not, it attempts to lookup the spend key in the database. + // Returns boost::empty when keychain is locked or watch-only. + boost::optional CalculateOutputKey(const mw::Coin& coin) const; // Calculates the StealthAddress at the given index. // Requires that keychain be unlocked and not watch-only. @@ -42,6 +44,8 @@ public: const SecretKey& GetScanSecret() const noexcept { return m_scanSecret; } const SecretKey& GetSpendSecret() const noexcept { return m_spendSecret; } + bool HasSpendSecret() const noexcept { return !m_spendSecret.IsNull(); } + // Clears the spend secret from memory, effectively making this a watch-only keychain. void Lock() { m_spendSecret = SecretKey::Null(); } @@ -49,7 +53,7 @@ public: void Unlock(const SecretKey& spend_secret) { m_spendSecret = spend_secret; } private: - const ScriptPubKeyMan& m_spk_man; + const LegacyScriptPubKeyMan& m_spk_man; SecretKey m_scanSecret; SecretKey m_spendSecret; }; diff --git a/src/libmw/src/wallet/Keychain.cpp b/src/libmw/src/wallet/Keychain.cpp index a959a7656..e05b1353b 100644 --- a/src/libmw/src/wallet/Keychain.cpp +++ b/src/libmw/src/wallet/Keychain.cpp @@ -3,6 +3,7 @@ #include #include #include +#include MW_NAMESPACE @@ -25,12 +26,10 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const // Check if B_i belongs to wallet StealthAddress address(B_i.Mul(m_scanSecret), B_i); auto pMetadata = m_spk_man.GetMetadata(address); - if (!pMetadata || !pMetadata->mweb_index) { + if (!pMetadata) { return false; } - uint32_t index = *pMetadata->mweb_index; - // Calc blinding factor and unmask nonce and amount. OutputMask mask = OutputMask::FromShared(t); uint64_t value = mask.MaskValue(output.GetMaskedValue()); @@ -51,28 +50,53 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const return false; } - // Spend secret will be null for locked or watch-only wallets. - if (!GetSpendSecret().IsNull()) { - coin.spend_key = boost::make_optional(CalculateOutputKey(index, t)); - } - - coin.address_index = index; + // v0.21.2 incorrectly generated MWEB keys from the pre-split keypool for upgraded wallets. + // These keys will not have an mweb_index, so we set the address_index as CUSTOM_KEY. + coin.address_index = pMetadata->mweb_index.get_value_or(CUSTOM_KEY); coin.blind = boost::make_optional(mask.GetRawBlind()); coin.amount = value; coin.output_id = output.GetOutputID(); coin.address = address; coin.shared_secret = boost::make_optional(std::move(t)); + coin.spend_key = CalculateOutputKey(coin); return true; } -SecretKey Keychain::CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const +boost::optional Keychain::CalculateOutputKey(const mw::Coin& coin) const { - assert(!m_spendSecret.IsNull()); + // If we already calculated the spend key, there's no need to calculate it again. + if (coin.HasSpendKey()) { + return coin.spend_key; + } - return SecretKeys::From(GetSpendKey(index)) - .Mul(Hashed(EHashTag::OUT_KEY, shared_secret)) - .Total(); + // Watch-only or locked wallets will not have the spend secret. + if (!HasSpendSecret() || !coin.HasSharedSecret() || !coin.IsMine()) { + return boost::none; + } + + auto derive_output_key = [](const SecretKey& spend_key, const SecretKey& shared_secret) -> SecretKey { + return SecretKeys::From(spend_key) + .Mul(Hashed(EHashTag::OUT_KEY, shared_secret)) + .Total(); + }; + + // An address_index of CUSTOM_KEY means the spend key was not generated from the MWEB keychain. + // We should lookup the secret key in the wallet DB, instead of calculating by index. + if (coin.address_index == CUSTOM_KEY) { + if (!coin.HasAddress()) { + return boost::none; + } + + CKey key; + if (!m_spk_man.GetKey(coin.address->B().GetID(), key)) { + return boost::none; + } + + return derive_output_key(SecretKey(key.begin()), *coin.shared_secret); + } + + return derive_output_key(GetSpendKey(coin.address_index), *coin.shared_secret); } StealthAddress Keychain::GetStealthAddress(const uint32_t index) const diff --git a/src/mweb/mweb_models.h b/src/mweb/mweb_models.h index 9d472d9bc..2c136e0a1 100644 --- a/src/mweb/mweb_models.h +++ b/src/mweb/mweb_models.h @@ -36,6 +36,11 @@ struct Block { return IsNull() ? 0 : m_block->GetSupplyChange(); } + mw::Hash GetHash() const noexcept + { + return IsNull() ? mw::Hash{} : m_block->GetHeader()->GetHash(); + } + mw::Header::CPtr GetMWEBHeader() const noexcept { return IsNull() ? mw::Header::CPtr{nullptr} : m_block->GetHeader(); diff --git a/src/mweb/mweb_wallet.cpp b/src/mweb/mweb_wallet.cpp index 6743b22be..95cb14d65 100644 --- a/src/mweb/mweb_wallet.cpp +++ b/src/mweb/mweb_wallet.cpp @@ -8,7 +8,7 @@ using namespace MWEB; bool Wallet::UpgradeCoins() { mw::Keychain::Ptr keychain = GetKeychain(); - if (!keychain || keychain->GetSpendSecret().IsNull()) { + if (!keychain || !keychain->HasSpendSecret()) { return false; } @@ -19,14 +19,17 @@ bool Wallet::UpgradeCoins() if (wtx->mweb_wtx_info && wtx->mweb_wtx_info->received_coin) { mw::Coin& coin = *wtx->mweb_wtx_info->received_coin; - if (!coin.HasSpendKey() && coin.HasSharedSecret()) { - coin.spend_key = keychain->CalculateOutputKey(coin.address_index, *coin.shared_secret); + if (!coin.HasSpendKey()) { + coin.spend_key = keychain->CalculateOutputKey(coin); - m_coins[coin.output_id] = coin; + // If spend key was populated, update the database and m_coins map. + if (coin.HasSpendKey()) { + m_coins[coin.output_id] = coin; - WalletBatch batch(m_pWallet->GetDatabase()); - batch.WriteMWEBCoin(coin); - batch.WriteTx(*wtx); + WalletBatch batch(m_pWallet->GetDatabase()); + batch.WriteMWEBCoin(coin); + batch.WriteTx(*wtx); + } } } } @@ -57,7 +60,7 @@ bool Wallet::RewindOutput(const Output& output, mw::Coin& coin) if (GetCoin(output.GetOutputID(), coin) && coin.IsMine()) { // If the coin has the spend key, it's fully rewound. // If not, try rewinding further if we have the master spend key (i.e. wallet is unlocked). - if (coin.HasSpendKey() || !keychain || keychain->GetSpendSecret().IsNull()) { + if (coin.HasSpendKey() || !keychain || !keychain->HasSpendSecret()) { return true; } } @@ -90,7 +93,7 @@ bool Wallet::GetStealthAddress(const mw::Coin& coin, StealthAddress& address) co bool Wallet::GetStealthAddress(const uint32_t index, StealthAddress& address) const { mw::Keychain::Ptr keychain = GetKeychain(); - if (!keychain || index == mw::UNKNOWN_INDEX) { + if (!keychain || index == mw::UNKNOWN_INDEX || index == mw::CUSTOM_KEY) { return false; } diff --git a/src/net.cpp b/src/net.cpp index b3d1d1da6..46e53924d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -157,22 +157,23 @@ bool GetLocal(CService& addr, const CNetAddr *paddrPeer) return nBestScore >= 0; } -//! Convert the pnSeed6 array into usable address objects. -static std::vector convertSeed6(const std::vector &vSeedsIn) +//! Convert the serialized seeds into usable address objects. +static std::vector ConvertSeeds(const std::vector& vSeedsIn) { // It'll only connect to one or two seed nodes because once it connects, // it'll get a pile of addresses with newer timestamps. // Seed nodes are given a random 'last seen time' of between one and two // weeks ago. - const int64_t nOneWeek = 7*24*60*60; + const int64_t nOneWeek = 7 * 24 * 60 * 60; std::vector vSeedsOut; - vSeedsOut.reserve(vSeedsIn.size()); FastRandomContext rng; - for (const auto& seed_in : vSeedsIn) { - struct in6_addr ip; - memcpy(&ip, seed_in.addr, sizeof(ip)); - CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE)); + CDataStream s(vSeedsIn, SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT); + while (!s.eof()) { + CService endpoint; + s >> endpoint; + CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)}; addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek; + LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToString()); vSeedsOut.push_back(addr); } return vSeedsOut; @@ -1906,7 +1907,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect) LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be available.\n"); CNetAddr local; local.SetInternal("fixedseeds"); - addrman.Add(convertSeed6(Params().FixedSeeds()), local); + addrman.Add(ConvertSeeds(Params().FixedSeeds()), local); done = true; } } diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 354aceff3..64a807085 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -196,7 +196,7 @@ CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& } } if (auto stealth_address = boost::get(&dest)) { - return CPubKey(stealth_address->B().vec()).GetID(); + return stealth_address->B().GetID(); } return CKeyID(); } diff --git a/src/wallet/reserve.cpp b/src/wallet/reserve.cpp index 030b3a39a..231031f81 100644 --- a/src/wallet/reserve.cpp +++ b/src/wallet/reserve.cpp @@ -13,9 +13,11 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter m_spk_man->TopUp(); CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { + int64_t reserved_index; + if (!m_spk_man->GetReservedDestination(type, internal, address, reserved_index, keypool)) { return false; } + nIndex = reserved_index; fInternal = keypool.fInternal; } dest = address; diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 0950f252f..b66e7086f 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -244,8 +244,7 @@ isminetype LegacyScriptPubKeyMan::IsMine(const DestinationAddr& script) const return ISMINE_NO; } - CPubKey pubkey(mweb_address.GetSpendPubKey().vec()); - return HaveKey(pubkey.GetID()) ? ISMINE_SPENDABLE : ISMINE_NO; + return HaveKey(mweb_address.GetSpendPubKey().GetID()) ? ISMINE_SPENDABLE : ISMINE_NO; } switch (IsMineInner(*this, script.GetScript(), IsMineSigVersion::TOP)) { @@ -1442,13 +1441,14 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key bool fReturningInternal = (purpose == KeyPurpose::INTERNAL); fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); bool fMWEB = (purpose == KeyPurpose::MWEB) && IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT); + bool use_pre_split = !fMWEB && !set_pre_split_keypool.empty(); auto fn_get_keypool = [this](const bool internal, const bool mweb) -> std::set& { - if (!set_pre_split_keypool.empty()) { - return set_pre_split_keypool; - } else if (mweb) { + if (mweb) { return set_mweb_keypool; - } else if (internal) { + } else if (!set_pre_split_keypool.empty()) { + return set_pre_split_keypool; + } else if (internal) { return setInternalKeyPool; } @@ -1474,8 +1474,11 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } + if (keypool.fMWEB != fMWEB) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); + } // If the key was pre-split keypool, we don't care about what type it is - if (set_pre_split_keypool.empty() && (keypool.fInternal != fReturningInternal || keypool.fMWEB != fMWEB)) { + if (!use_pre_split && keypool.fInternal != fReturningInternal) { throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); } if (!keypool.vchPubKey.IsValid()) { @@ -1548,8 +1551,7 @@ void LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id) std::vector GetAffectedKeys(const DestinationAddr& spk, const SigningProvider& provider) { if (spk.IsMWEB()) { - CPubKey spend_pubkey(spk.GetMWEBAddress().GetSpendPubKey().vec()); - return std::vector{spend_pubkey.GetID()}; + return std::vector{spk.GetMWEBAddress().GetSpendPubKey().GetID()}; } std::vector dummy; diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp index 757b5ced0..3bed3ee45 100644 --- a/src/wallet/test/scriptpubkeyman_tests.cpp +++ b/src/wallet/test/scriptpubkeyman_tests.cpp @@ -66,24 +66,21 @@ BOOST_AUTO_TEST_CASE(StealthAddresses) StealthAddress change_address = mweb_keychain->GetStealthAddress(0); BOOST_CHECK(EncodeDestination(change_address) == "ltcmweb1qq20e2arnhvxw97katjkmsd35agw3capxjkrkh7dk8d30rczm8ypxuq329nwh2twmchhqn3jqh7ua4ps539f6aazh79jy76urqht4qa59ts3at6gf"); BOOST_CHECK(keyman.IsMine(change_address) == ISMINE_SPENDABLE); - CPubKey change_pubkey(change_address.B().vec()); - BOOST_CHECK(keyman.GetAllReserveKeys().find(change_pubkey.GetID()) == keyman.GetAllReserveKeys().end()); + BOOST_CHECK(keyman.GetAllReserveKeys().find(change_address.B().GetID()) == keyman.GetAllReserveKeys().end()); BOOST_CHECK(*keyman.GetMetadata(change_address)->mweb_index == 0); // Check "peg-in" (idx=1) address is USED StealthAddress pegin_address = mweb_keychain->GetStealthAddress(1); BOOST_CHECK(EncodeDestination(pegin_address) == "ltcmweb1qqg5hddkl4uhspjwg9tkmatxa4s6gswdaq9swl8vsg5xxznmye7phcqatzc62mzkg788tsrfcuegxe9q3agf5cplw7ztqdusqf7x3n2tl55x4gvyt"); BOOST_CHECK(keyman.IsMine(pegin_address) == ISMINE_SPENDABLE); - CPubKey pegin_pubkey(pegin_address.B().vec()); - BOOST_CHECK(keyman.GetAllReserveKeys().find(pegin_pubkey.GetID()) == keyman.GetAllReserveKeys().end()); + BOOST_CHECK(keyman.GetAllReserveKeys().find(pegin_address.B().GetID()) == keyman.GetAllReserveKeys().end()); BOOST_CHECK(*keyman.GetMetadata(pegin_address)->mweb_index == 1); // Check first receive (idx=2) address is UNUSED StealthAddress receive_address = mweb_keychain->GetStealthAddress(2); BOOST_CHECK(EncodeDestination(receive_address) == "ltcmweb1qq0yq03ewm830ugmkkvrvjmyyeslcpwk8ayd7k27qx63sryy6kx3ksqm3k6jd24ld3r5dp5lzx7rm7uyxfujf8sn7v4nlxeqwrcq6k6xxwqdc6tl3"); BOOST_CHECK(keyman.IsMine(receive_address) == ISMINE_SPENDABLE); - CPubKey receive_pubkey(receive_address.B().vec()); - BOOST_CHECK(keyman.GetAllReserveKeys().find(receive_pubkey.GetID()) != keyman.GetAllReserveKeys().end()); + BOOST_CHECK(keyman.GetAllReserveKeys().find(receive_address.B().GetID()) != keyman.GetAllReserveKeys().end()); BOOST_CHECK(*keyman.GetMetadata(receive_address)->mweb_index == 2); BOOST_CHECK(keyman.GetHDChain().nMWEBIndexCounter == 1002); diff --git a/test/functional/mweb_wallet_upgrade.py b/test/functional/mweb_wallet_upgrade.py new file mode 100644 index 000000000..2efdadd4c --- /dev/null +++ b/test/functional/mweb_wallet_upgrade.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Litecoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Tests that non-HD wallets that are upgraded are able to receive via MWEB""" + +import os +import shutil + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.ltc_util import create_non_hd_wallet, setup_mweb_chain +from test_framework.util import assert_equal + +class MWEBWalletUpgradeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [['-whitelist=noban@127.0.0.1'],[]] # immediate tx relay + + def skip_test_if_missing_module(self): + self.skip_if_no_previous_releases() + self.skip_if_no_wallet() + + def run_test(self): + node0 = self.nodes[0] + node1 = self.nodes[1] + + # + # Mine until MWEB is activated + # + self.log.info("Setting up MWEB chain") + setup_mweb_chain(node0) + self.sync_all() + + # + # Create a non-HD wallet using an older litecoin core version + # + self.log.info("Creating non-hd wallet") + nonhd_wallet_dat = create_non_hd_wallet(self.chain, self.options) + + # + # Replace node1's wallet with the non-HD wallet.dat + # + self.log.info("Replacing wallet with non-hd wallet.dat") + node1.get_wallet_rpc(self.default_wallet_name).unloadwallet() + upgrade_wallet_dir = os.path.join(node1.datadir, "regtest", "wallets", self.default_wallet_name) + shutil.rmtree(upgrade_wallet_dir) + os.mkdir(upgrade_wallet_dir) + shutil.copy(nonhd_wallet_dat, upgrade_wallet_dir) + node1.loadwallet(self.default_wallet_name) + + # + # Upgrade node1's non-HD wallet to the latest version + # + self.log.info("Upgrading wallet") + node1.upgradewallet() + + # + # Send to MWEB address of upgraded wallet (node1) + # + self.log.info("Send to upgraded wallet's mweb address") + mweb_addr = node1.getnewaddress(address_type='mweb') + tx1_id = node0.sendtoaddress(mweb_addr, 25) + self.sync_mempools() + + # + # Verify transaction is received by upgraded wallet (node1) + # + self.log.info("Verify upgraded wallet lists the transaction") + tx1 = node1.gettransaction(txid=tx1_id) + assert_equal(tx1['confirmations'], 0) + assert_equal(tx1['amount'], 25) + assert_equal(tx1['details'][0]['address'], mweb_addr) + + node0.generate(1) + self.sync_all() + + # + # Verify that MWEB coins can be spent by upgraded wallet (node1) + # + self.log.info("Spending MWEB coins") + mining_mweb_addr = node0.getnewaddress(address_type='mweb') + tx2_id = node1.sendtoaddress(mining_mweb_addr, 10) + self.sync_mempools() + + # + # Mine 1 block and verify transaction confirms + # + self.log.info("Mining block to verify it confirms") + node0.generate(1) + tx2 = node0.gettransaction(txid=tx2_id) + assert_equal(tx2['confirmations'], 1) + assert_equal(tx2['amount'], 10) + assert_equal(tx2['details'][0]['address'], mining_mweb_addr) + +if __name__ == '__main__': + MWEBWalletUpgradeTest().main() diff --git a/test/functional/test_framework/ltc_util.py b/test/functional/test_framework/ltc_util.py index ebb00bd9d..9a01b7d9c 100644 --- a/test/functional/test_framework/ltc_util.py +++ b/test/functional/test_framework/ltc_util.py @@ -4,9 +4,12 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Random assortment of utility functions""" +import os + from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, MWEBHeader -from test_framework.util import satoshi_round +from test_framework.util import get_datadir_path, initialize_datadir, satoshi_round from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, hogaddr_script +from test_framework.test_node import TestNode """Create a txout with a given amount and scriptPubKey @@ -91,5 +94,43 @@ def create_hogex(node, mweb_hash): tx.vout = [CTxOut(int(hog_addr['value'] * COIN), hogaddr_script(mweb_hash))] tx.hogex = True tx.rehash() + return tx - return tx \ No newline at end of file +""" Create a non-HD wallet from a temporary v15.1.0 node. + +Returns the path of the wallet.dat. +""" +def create_non_hd_wallet(chain, options): + version = 150100 + bin_dir = os.path.join(options.previous_releases_path, 'v0.15.1', 'bin') + initialize_datadir(options.tmpdir, 10, chain) + data_dir = get_datadir_path(options.tmpdir, 10) + + # adjust conf for pre 17 + conf_file = os.path.join(data_dir, 'litecoin.conf') + with open(conf_file, 'r', encoding='utf8') as conf: + conf_data = conf.read() + with open(conf_file, 'w', encoding='utf8') as conf: + conf.write(conf_data.replace('[regtest]', '')) + + v15_node = TestNode( + i=10, + datadir=data_dir, + chain=chain, + rpchost=None, + timewait=60, + timeout_factor=1.0, + bitcoind=os.path.join(bin_dir, 'litecoind'), + bitcoin_cli=os.path.join(bin_dir, 'litecoin-cli'), + version=version, + coverage_dir=None, + cwd=options.tmpdir, + extra_args=["-usehd=0"], + ) + v15_node.start() + v15_node.wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition + v15_node.wait_for_rpc_connection() + v15_node.stop_node(wait=0) + v15_node.wait_until_stopped() + + return os.path.join(v15_node.datadir, chain, "wallet.dat") \ No newline at end of file diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ba39011ed..c43ebb33b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -252,6 +252,7 @@ BASE_SCRIPTS = [ 'mweb_node_compatibility.py', 'mweb_wallet_address.py', 'mweb_wallet_basic.py', + 'mweb_wallet_upgrade.py', 'wallet_listwallettransactions.py', 'rpc_uptime.py', 'wallet_resendwallettransactions.py',