Merge pull request #822 from litecoin-project/fix-upgraded-wallets

v0.21.2.1
This commit is contained in:
Loshan T 2022-06-07 20:17:27 +01:00 committed by GitHub
commit 32a108600d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 421 additions and 263 deletions

View File

@ -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)

View File

@ -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
<ip>
<ip>:<port>
[<ipv6>]
[<ipv6>]:<port>
<onion>.onion
0xDDBBCCAA (IPv4 little-endian old pnSeeds format)
<onion>.onion:<port>
<i2p>.b32.i2p:<port>
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("<BH", 253, l)
elif l < 0x100000000:
r = struct.pack("<BI", 254, l)
else:
r = struct.pack("<BQ", 255, l)
return r
def bip155_serialize(spec):
'''
Serialize (networkID, addr, port) tuple to BIP155 binary format.
'''
r = b""
r += struct.pack('B', spec[0].value)
r += ser_compact_size(len(spec[1]))
r += spec[1]
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__':

View File

@ -12,4 +12,6 @@
154.22.123.138:9333
174.62.70.234:9333
178.62.46.195:9333
213.244.192.149:9333
213.244.192.149:9333
xtmsopiy2ssu2vumfmnjq3nyflnq2pkpcxndi6rxgwnkljmzijafozqd.onion:9333
37am23y5vro3iorqlmjjty7ctsgz6myoaushxrfqct4ngakl7d7idgid.onion:9333

View File

@ -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

View File

@ -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).

View File

@ -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`):

View File

@ -0,0 +1,21 @@
Litecoin Core version 0.21.2.1 is now available from:
<https://download.litecoin.org/litecoin-0.21.2.1/>.
This includes a critical bug fix for upgraded wallets to receive via MWEB.
Please report bugs using the issue tracker at GitHub:
<https://github.com/litecoin-project/litecoin/issues>
To receive security and update notifications, please subscribe to:
<https://groups.google.com/forum/#!forum/litecoin-dev>
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.

View File

@ -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:
<https://download.litecoin.org/litecoin-0.21.2/>.
<https://download.litecoin.org/litecoin-0.21.2.1/>.
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:
<https://groups.google.com/forum/#!forum/litecoin-dev>
How to upgrade:
==============
Firstly, thank you for running Litecoin Core and helping secure the network!
As youre running an older version of Litecoin Core, shut it down. Wait until its 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. Its 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. Its 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

View File

@ -139,7 +139,7 @@ public:
bech32_hrp = "ltc";
mweb_hrp = "ltcmweb";
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
vFixedSeeds = std::vector<uint8_t>(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<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
vFixedSeeds = std::vector<uint8_t>(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")}
}
};

View File

@ -90,7 +90,7 @@ public:
const std::vector<unsigned char>& 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<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
const std::vector<uint8_t>& 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<SeedSpec6> vFixedSeeds;
std::vector<uint8_t> vFixedSeeds;
bool fDefaultConsistencyChecks;
bool fRequireStandard;
bool m_is_test_chain;

View File

@ -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

View File

@ -3,6 +3,8 @@
#include <mw/common/Traits.h>
#include <mw/models/crypto/BigInteger.h>
#include <mw/models/crypto/SecretKey.h>
#include <pubkey.h>
#include <boost/functional/hash.hpp>
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<uint8_t, 33> array() const { return m_compressed.ToArray(); }
const std::vector<uint8_t>& vec() const { return m_compressed.vec(); }
const uint8_t& operator[](const size_t x) const { return m_compressed[x]; }

View File

@ -20,6 +20,13 @@ static constexpr uint32_t CHANGE_INDEX{0};
/// </summary>
static constexpr uint32_t PEGIN_INDEX{1};
/// <summary>
/// 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.
/// </summary>
static constexpr uint32_t CUSTOM_KEY{std::numeric_limits<uint32_t>::max() - 1};
/// <summary>
/// Outputs sent to others will be marked with an address_index of 0xffffffff.
/// </summary>
@ -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<SecretKey> spend_key;
// The blinding factor of the coin's output.

View File

@ -7,7 +7,7 @@
#include <memory>
// Forward Declarations
class ScriptPubKeyMan;
class LegacyScriptPubKeyMan;
MW_NAMESPACE
@ -16,7 +16,7 @@ class Keychain
public:
using Ptr = std::shared_ptr<Keychain>;
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<SecretKey> 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;
};

View File

@ -3,6 +3,7 @@
#include <mw/crypto/SecretKeys.h>
#include <mw/models/tx/OutputMask.h>
#include <wallet/scriptpubkeyman.h>
#include <key_io.h>
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<SecretKey> 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

View File

@ -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();

View File

@ -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;
}

View File

@ -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<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn)
//! Convert the serialized seeds into usable address objects.
static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t>& 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<CAddress> 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<std::string> 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;
}
}

View File

@ -196,7 +196,7 @@ CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination&
}
}
if (auto stealth_address = boost::get<StealthAddress>(&dest)) {
return CPubKey(stealth_address->B().vec()).GetID();
return stealth_address->B().GetID();
}
return CKeyID();
}

View File

@ -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;

View File

@ -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<int64_t>& {
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<CKeyID> GetAffectedKeys(const DestinationAddr& spk, const SigningProvider& provider)
{
if (spk.IsMWEB()) {
CPubKey spend_pubkey(spk.GetMWEBAddress().GetSpendPubKey().vec());
return std::vector<CKeyID>{spend_pubkey.GetID()};
return std::vector<CKeyID>{spk.GetMWEBAddress().GetSpendPubKey().GetID()};
}
std::vector<DestinationAddr> dummy;

View File

@ -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);

View File

@ -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()

View File

@ -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
""" 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")

View File

@ -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',