示例#1
0
    def test_calcTicketProfits(self):
        class FakeAccount(account.Account):
            def __init__(self):
                pass

        db = KeyValueDatabase(":memory:").child("tmp")

        acct = FakeAccount()
        tDB = acct.ticketDB = db.child("tickets",
                                       datatypes=("TEXT", "BLOB"),
                                       blobber=account.UTXO)

        def newTinfo(poolFee, purchaseTxFee, spendTxFee, stakebase):
            return account.TicketInfo(
                status="",
                purchaseBlock=account.TinyBlock(ByteArray(0), 0),
                maturityHeight=0,
                expirationHeight=0,
                lotteryBlock=None,
                vote=None,
                revocation=None,
                poolFee=poolFee,
                purchaseTxFee=purchaseTxFee,
                spendTxFee=spendTxFee,
                stakebase=stakebase,
            )

        utxo = account.UTXO(
            address="",
            txHash=reversed(ByteArray("aa")),
            vout=0,
            scriptPubKey=None,
            satoshis=5,
        )

        tinfo = newTinfo(0, 1, 1, 1)
        utxo.tinfo = tinfo
        tDB["aa"] = utxo
        tinfo = newTinfo(1, 1, 1, 1)
        utxo.tinfo = tinfo
        tDB["ab"] = utxo
        tinfo = newTinfo(0, 1, 0, 0)
        utxo.tinfo = tinfo
        tDB["ac"] = utxo

        stakebases, poolFees, txFees = acct.calcTicketProfits()

        s, p, t = 2, 1, 5

        assert stakebases == s
        assert poolFees == p
        assert txFees == t
示例#2
0
    def test_nextBranchAddress(self):
        class FakeExtendedKey:
            def child(self, i):
                raise crypto.CrazyKeyError

        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db)
        with pytest.raises(DecredError):
            acct.nextBranchAddress(FakeExtendedKey(), [], {})
示例#3
0
    def test_unlock(self, monkeypatch):
        def mock_privateKey(self):
            raise crypto.CrazyKeyError

        monkeypatch.setattr(crypto.ExtendedKey, "privateKey", mock_privateKey)
        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db)
        assert not acct.isUnlocked()
        with pytest.raises(DecredError):
            acct.unlock(self.cryptoKey)
示例#4
0
    def test_nextExternalInternalAddress(self):
        class FakeBlockchain:
            subscribeAddresses = lambda addr, receiver: None

        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db, FakeBlockchain)
        acct.unlock(self.cryptoKey)
        assert acct.nextExternalAddress(
        ) == "DseZQKiWhN3ceBwDJEgGhmwKD3fMbwF4ugf"
        assert acct.nextInternalAddress(
        ) == "DskHpgbEb6hqkuHchHhtyojpehFToEtjQSo"
示例#5
0
    def test_spentTicketInfo(self):
        class BlockchainFull:
            ticketInfo = lambda txid: account.TicketInfo(
                "mempool", None, 0, 0, None, True)

        class BlockchainEmpty:
            ticketInfo = lambda txid: account.TicketInfo("mempool", None, 0, 0)

        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db, BlockchainFull)
        assert acct.spentTicketInfo("some_txid")
        acct = self.newAccount(db, BlockchainEmpty)
        assert not acct.spentTicketInfo("some_txid")
示例#6
0
    def test_updateSpentTickets(self):
        class Dummy:
            pass

        class FakeAccount(account.Account):
            def __init__(self):
                self.signals = Dummy()
                pool = Dummy()
                pool.purchaseInfo = Dummy()
                pool.purchaseInfo.ticketAddress = "ticketaddress"
                self.stakePools = [pool]
                self.blockchain = Dummy()
                self.mempool = {}
                self.txs = {}
                self.utxos = {}
                self.netParams = nets.testnet

        db = KeyValueDatabase(":memory:").child("tmp")

        acct = FakeAccount()
        tDB = acct.ticketDB = db.child("tickets",
                                       datatypes=("TEXT", "BLOB"),
                                       blobber=account.UTXO)

        def fail():
            assert False

        acct.signals.spentTickets = fail

        # tickets and ticketDB empty, noop
        acct.updateSpentTickets()

        stakeSubmission = ByteArray(opcode.OP_SSTX)
        stakeSubmission += opcode.OP_HASH160
        stakeSubmission += opcode.OP_DATA_20
        stakeSubmission += 1 << (8 * 19)
        stakeSubmission += opcode.OP_EQUAL

        def newTinfo(status="live"):
            return account.TicketInfo(
                status=status,
                purchaseBlock=account.TinyBlock(ByteArray(0), 42),
                maturityHeight=0,
                expirationHeight=0,
                lotteryBlock=None,
                vote=None,
                revocation=None,
                poolFee=0,
                purchaseTxFee=1,
                spendTxFee=0,
                stakebase=0,
            )

        txid = "aa"

        def utxoWithTxid(txid):
            return account.UTXO(
                address="ticketaddress",
                txHash=reversed(ByteArray(txid)),
                vout=0,
                scriptPubKey=stakeSubmission,
                satoshis=2,
                tinfo=newTinfo(),
            )

        utxo = utxoWithTxid(txid)

        def txWithTxid(txid):
            txInOne = msgtx.TxIn(msgtx.OutPoint(ByteArray("ff"), 0, 0),
                                 valueIn=1)
            txInTwo = msgtx.TxIn(None, valueIn=3)
            txOutOne = msgtx.TxOut(pkScript=stakeSubmission, value=3)
            txOutTwo = msgtx.TxOut()
            txOutThree = msgtx.TxOut()
            txOutFour = msgtx.TxOut()
            txOutFive = msgtx.TxOut()
            txsIn = [txInOne, txInTwo]
            txsOut = [txOutOne, txOutTwo, txOutThree, txOutFour, txOutFive]
            return msgtx.MsgTx(reversed(ByteArray(txid)), None, None, txsIn,
                               txsOut, None, None)

        tx = txWithTxid(txid)
        utxo.tinfo.status = "mempool"
        tDB[txid] = utxo
        acct.mempool[txid] = tx

        # mempool and ticketDB have the same txid and status, noop
        acct.updateSpentTickets()

        called = False

        def ok():
            nonlocal called
            called = True

        acct.signals.spentTickets = ok

        tinfos = {
            "aa": utxo.tinfo,
            "ab": newTinfo(),
            "ac": newTinfo(),
            "ad": newTinfo(),
            "ae": newTinfo(status="unconfirmed"),
        }

        txs = {k: txWithTxid(k) for k in tinfos.keys() if k != "ab"}

        blockheader = Dummy()
        blockheader.height = 0
        blockheader.timestamp = 0

        vote = "ff"
        revocation = "fe"

        def setVoteOrRevoke(txid):
            if txid == vote:
                ticket = tDB["ac"]
                ticket.tinfo.vote = reversed(ByteArray(vote))
            if txid == revocation:
                ticket = tDB["ad"]
                ticket.tinfo.revocation = reversed(ByteArray(revocation))

        acct.spendTicket = lambda tx: setVoteOrRevoke(
            reversed(tx.cachedH).hex())
        acct.blockchain.tx = lambda txid: txs[txid]
        acct.blockchain.ticketInfo = lambda txid: tinfos[txid]
        acct.blockchain.blockForTx = lambda *args: blockheader
        acct.utxos = {k + "#0": utxoWithTxid(k) for k in txs.keys()}
        acct.mempool = {"ab": txWithTxid("ab")}

        txs[vote] = txWithTxid(vote)
        txs[revocation] = txWithTxid(revocation)

        # Live tickets are now different than database.
        acct.updateSpentTickets()
        assert called

        # The tickets are now stored in the database.
        assert "ab" in tDB and "ac" in tDB and "ad" in tDB
        # They are unspent tickets.
        ut = acct.unspentTickets()
        assert "ab" in ut and "ac" in ut and "ad" in ut

        called = False
        txid = "ac"
        tinfos["ac"].vote = reversed(ByteArray(vote))
        del acct.utxos[txid + "#0"]

        # A ticket has been voted.
        acct.updateSpentTickets()
        assert called
        # It is an voted ticket.
        assert txid in acct.votedTickets() and txid not in acct.unspentTickets(
        )

        called = False
        txid = "ad"
        tinfos["ad"].revocation = reversed(ByteArray(revocation))
        del acct.utxos[txid + "#0"]

        # A ticket has been revoked.
        acct.updateSpentTickets()
        assert called
        # It is a revoked ticket.
        assert txid in acct.revokedTickets(
        ) and txid not in acct.unspentTickets()

        txid = "af"
        called = False
        tDB[txid] = utxo

        # A txid is in the ticketDB but not in utxos or mempool or the
        # blockchain.
        acct.updateSpentTickets()
        assert called
        # It was removed.
        assert txid not in tDB
示例#7
0
    def test_spendTicket(self, monkeypatch):
        """
        Test updating spent tickets.
        """
        vote = ("010000000200000000000000000000000000000000000000000000000000"
                "00000000000000ffffffff00ffffffff571ce9fb0c52ae22c3a6480cbf6d"
                "30ff76bdffbf54b6e081eb218aa3a0ca2bc40000000001ffffffff040000"
                "0000000000000000266a2432c0c546b332f7abf51f3fc73f4482185f4c09"
                "61625763a766774237280000007f75050000000000000000000000086a06"
                "050008000000900102000000000000001abb76a914781f472a926da0bb7c"
                "e9eec7f4d434de21015cae88acd9b293890100000000001abb76a9149087"
                "5ba5fee5f35352e9171d40190e796b1b152788ac000000000000000002a6"
                "3895010000000000000000ffffffff020000c47b00880100000049700500"
                "0600000091483045022100c1ec49cb687fa2421e76b534ced49563b3de1e"
                "c6407b1bbfda26752fbdedc88302204988390ea3be77324909781322a46b"
                "463d00dd14718f0964b9536b5eef4e35570147512103af3c24d005ca8b75"
                "5e7167617f3a5b4c60a65f8318a7fcd1b0cacb1abd2a97fc21027b81bc16"
                "954e28adb832248140eb58bedb6078ae5f4dabf21fde5a8ab7135cb652ae")

        revocation = ("010000000139cb023fdcf6fcd18273c7395f51084721d0e4baf0ca"
                      "3ee9590ad63a411a4cb30000000001ffffffff02f0ff0100000000"
                      "0000001abc76a914781f472a926da0bb7ce9eec7f4d434de21015c"
                      "ae88ac8fcc07ba0100000000001abc76a914f90abbb67dc9257efa"
                      "6ab24eb88e2755f34b1f7f88ac0000000000000000018ad609ba01"
                      "000000296505001000000091483045022100bc9a694d0864df030e"
                      "6edea181a2e2385dfbf93396b5792899a65f19c9afd67a02206052"
                      "192b5631e062f4497706276ec514a14cdfa8035ef6c7dca0df7120"
                      "84618e0147512103af3c24d005ca8b755e7167617f3a5b4c60a65f"
                      "8318a7fcd1b0cacb1abd2a97fc21027b81bc16954e28adb8322481"
                      "40eb58bedb6078ae5f4dabf21fde5a8ab7135cb652ae")

        # Tickets can be found on testnet3.
        ticketVotedTxid = (
            "c42bcaa0a38a21eb81e0b654bfffbd76ff306dbf0c48a6c322ae520cfbe91c57")
        ticketRevokedTxid = (
            "b34c1a413ad60a59e93ecaf0bae4d0214708515f39c77382d1fcf6dc3f02cb39")

        voteTxid = "aa19094e404a1ee056760bdb1b7ed1b6c8e5f1d97752335eddbfdfa19e76c262"
        revocationTxid = (
            "d85694ba7aae060667b393558cd96c2df2926426f80db16a18bf4fc9102b0953")

        class Dummy:
            pass

        class FakeAccount(account.Account):
            def __init__(self):
                self.signals = Dummy()
                self.blockchain = Dummy()
                self.netParams = nets.testnet

        db = KeyValueDatabase(":memory:").child("tmp")

        acct = FakeAccount()
        tDB = acct.ticketDB = db.child("tickets",
                                       datatypes=("TEXT", "BLOB"),
                                       blobber=account.UTXO)

        def newTinfo(status):
            return account.TicketInfo(
                status=status,
                purchaseBlock=account.TinyBlock(ByteArray(0), 0),
                maturityHeight=0,
                expirationHeight=0,
            )

        utxo = account.UTXO(
            address="",
            txHash=reversed(ByteArray("aa")),
            vout=0,
            scriptPubKey=None,
            satoshis=5,
        )

        txidToTinfo = {
            voteTxid: newTinfo("vote"),
            revocationTxid: newTinfo("revocation"),
        }
        acct.blockchain.ticketInfoForSpendingTx = lambda txid, netParams: txidToTinfo[
            txid]

        tDB[ticketVotedTxid] = utxo
        tDB[ticketRevokedTxid] = utxo

        v = msgtx.MsgTx.deserialize(ByteArray(vote))

        acct.spendTicket(v)
        tinfo = tDB[ticketVotedTxid].tinfo
        assert tinfo.status == "vote"

        rev = msgtx.MsgTx.deserialize(ByteArray(revocation))

        acct.spendTicket(rev)
        tinfo = tDB[ticketRevokedTxid].tinfo
        assert tinfo.status == "revocation"

        # Test the error case.
        def mock_isSSGen(tx):
            return False

        monkeypatch.setattr(txscript, "isSSGen", mock_isSSGen)
        with pytest.raises(DecredError):
            acct.spendTicket(v)
示例#8
0
    def test_main(self, monkeypatch):
        """
        Test account functionality.
        """
        # Set up globals for test.
        monkeypatch.setattr(account, "DefaultGapLimit", 2)

        db = KeyValueDatabase(":memory:").child("tmp")
        acct = self.newAccount(db)
        acct.unlock(self.cryptoKey)
        acct.generateGapAddresses()
        for _ in range(20):
            acct.nextExternalAddress()
        satoshis = int(round(5 * 1e8))
        txHash = rando.newHash()
        txid = reversed(txHash).hex()
        vout = 2
        address = acct.nextExternalAddress()

        utxo = account.UTXO(
            address=address,
            txHash=txHash,
            vout=vout,
            scriptPubKey=ByteArray(0),
            satoshis=satoshis,
            maturity=1,
        )

        # A helper function to get the current utxo count.
        utxocount = lambda: len(list(acct.utxoscan()))

        # Add the utxo
        acct.addUTXO(utxo)
        # Check the count and balance categories.
        assert utxocount() == 1
        assert acct.calcBalance(1).total == satoshis
        assert acct.calcBalance(1).available == satoshis
        assert acct.calcBalance(0).available == 0
        # Helper functions.
        assert acct.caresAboutTxid(txid)
        assert not acct.caresAboutTxid("")
        acct.addUTXO(utxo)
        assert utxocount() == 1
        acct.spendUTXO(utxo)
        assert utxocount() == 0

        b = acct.serialize()
        reAcct = account.Account.unblob(b.b)

        assert acct.pubKeyEncrypted == reAcct.pubKeyEncrypted
        assert acct.privKeyEncrypted == reAcct.privKeyEncrypted
        assert acct.name == reAcct.name
        assert acct.coinID == reAcct.coinID
        assert acct.netID == reAcct.netID
        assert acct.gapLimit == reAcct.gapLimit
        assert acct.cursorExt == reAcct.cursorExt
        assert acct.cursorInt == reAcct.cursorInt
        assert acct.gapLimit == reAcct.gapLimit

        # Create a faux blockchain for the account.
        class Blockchain:
            txidsForAddr = lambda addr: []
            UTXOs = lambda addrs: []
            tipHeight = 5

        acct.blockchain = Blockchain

        class Signals:
            b = None

            @classmethod
            def balance(cls, b):
                cls.b = b

        acct.signals = Signals

        # Add a txid for this first address
        txid = rando.newHash().hex()
        zerothAddr = acct.externalAddresses[0]
        acct.addTxid(zerothAddr, txid)
        # Add a voting service provider
        vspKey = rando.newKey().hex()
        ticketAddr = "ticketAddr"
        pi = PurchaseInfo("addr", 1.0, ByteArray(b"scripthashscript"),
                          ticketAddr, 1, 0, 2)
        vsp = VotingServiceProvider("https://myvsp.com", vspKey,
                                    nets.mainnet.Name, pi)
        with pytest.raises(DecredError):
            acct.setPool(None)
        acct.setPool(vsp)
        acct.setNewPool(vsp)
        # Add UTXOs
        utxos = [account.UTXO.parse(u) for u in self.dcrdataUTXOs]
        acct.resolveUTXOs(utxos)
        ticketStats = acct.ticketStats()
        assert ticketStats.count == 0
        assert ticketStats.value == 0

        # Check that the addresses txid and the vsp load from the database.
        acct.txs.clear()
        acct.stakePools.clear()
        acct.utxos.clear()
        assert acct.stakePool() is None
        acct.load(db, Blockchain, Signals)
        assert zerothAddr in acct.txs
        assert len(acct.txs[zerothAddr]) == 1
        assert acct.txs[zerothAddr][0] == txid
        assert len(acct.stakePools) == 1
        assert acct.stakePool().apiKey == vspKey
        assert acct.calcBalance().available == self.utxoTotal
        assert len(acct.getUTXOs(self.utxoTotal)[0]) == 2
        assert len(acct.getUTXOs(self.utxoTotal, lambda u: False)[0]) == 0
        assert acct.lastSeen(acct.externalAddresses, -1) == 0
        branch, idx = acct.branchAndIndex(zerothAddr)
        assert branch == account.EXTERNAL_BRANCH
        assert idx == 0
        branch, idx = acct.branchAndIndex(acct.internalAddresses[0])
        assert branch == account.INTERNAL_BRANCH
        assert idx == 0
        checkKey = acct.privKey.child(account.EXTERNAL_BRANCH).child(0).key
        with pytest.raises(DecredError):
            acct.privKeyForAddress("")
        assert acct.privKeyForAddress(zerothAddr).key == checkKey
        ticketAddrs = acct.addTicketAddresses([])
        assert len(ticketAddrs) == 1
        assert ticketAddrs[0] == ticketAddr
        assert acct.hasPool()

        # Exercise setNode
        acct.setNode(1)
        assert acct.node == 1

        # Add a coinbase transaction output to the account.
        coinbase = newCoinbaseTx()
        cbUTXO = account.UTXO("addr",
                              ByteArray(b"id"),
                              1,
                              height=5,
                              satoshis=int(1e8))
        coinbase.addTxOut(msgtx.TxOut(int(1e8)))
        coinbaseID = coinbase.id()
        cbUTXO.txid = coinbaseID
        acct.addUTXO(cbUTXO)
        acct.addMempoolTx(coinbase)
        assert coinbaseID in acct.mempool
        # Send a block signal and have the transaction confirmed.
        sig = {"message": {"block": {"Tx": [{"TxID": coinbaseID}]}}}
        acct.blockchain.tipHeight = 5
        acct.blockchain.tx = lambda *a: coinbase
        acct.blockSignal(sig)
        assert coinbaseID not in acct.mempool
        assert cbUTXO.key() in acct.utxos
        maturity = 5 + nets.mainnet.CoinbaseMaturity
        assert acct.utxos[cbUTXO.key()].maturity == maturity
        assert acct.calcBalance(
            maturity).available == self.utxoTotal + cbUTXO.satoshis
        assert acct.calcBalance(0).available == self.utxoTotal
        acct.spendUTXOs([cbUTXO])
        assert acct.calcBalance(maturity).available == self.utxoTotal

        # make a ticket and a vote
        ticket = account.UTXO.parse(dcrdataUTXO)
        utxo.setTicketInfo(dcrdataTinfo)
        utxo.scriptPubKey = ticketScript
        utxo.parseScriptClass()
        acct.addUTXO(ticket)
        expVal = acct.calcBalance().available - ticket.satoshis
        voteTx = msgtx.MsgTx.new()
        voteTx.addTxIn(
            msgtx.TxIn(
                msgtx.OutPoint(reversed(ByteArray(ticket.txid)), ticket.vout,
                               0)))
        acct.blockchain.tx = lambda *a: voteTx
        acct.addressSignal(ticketAddr, "somehash")
        assert Signals.b.available == expVal

        # Detect a new utxo for the account.
        newVal = int(5e8)
        expVal = acct.calcBalance().available + newVal
        addr = acct.externalAddresses[1]
        a = addrlib.decodeAddress(addr, nets.mainnet)
        script = txscript.payToAddrScript(a)
        newTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(newVal, script)
        newTx.addTxOut(op)
        acct.blockchain.tx = lambda *a: newTx
        utxo = account.UTXO(addr, ByteArray(b"txid"), 0, satoshis=newVal)
        acct.blockchain.txVout = lambda *a: utxo
        acct.addressSignal(addr, "somehash")
        assert Signals.b.available == expVal

        # test syncing. add a single output for external address #2
        acct.stakePool().getPurchaseInfo = lambda: None
        acct.stakePool().authorize = lambda a: True
        newVal = int(3e8)
        addr = acct.externalAddresses[2]
        a = addrlib.decodeAddress(addr, nets.mainnet)
        script = txscript.payToAddrScript(a)
        newTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(newVal, script)
        newTx.addTxOut(op)
        utxo = account.UTXO(addr, ByteArray(b"txid"), 0, satoshis=newVal)

        def t4a(*a):
            acct.blockchain.txidsForAddr = lambda *a: []
            return [newTx.id()]

        acct.blockchain.txidsForAddr = t4a

        def utxos4a(*a):
            acct.blockchain.UTXOs = lambda *a: []
            return [utxo]

        acct.blockchain.UTXOs = utxos4a
        acct.blockchain.subscribeAddresses = lambda addrs, receiver: None
        acct.blockchain.subscribeBlocks = lambda a: None
        acct.sync()
        assert Signals.b.available == newVal

        # spend the utxo by sending it. Reusing newTx for convenience.
        changeVal = int(5e7)
        addr = acct.internalAddresses[0]
        change = account.UTXO(addr,
                              ByteArray(b"newtxid"),
                              0,
                              satoshis=changeVal)
        acct.blockchain.sendToAddress = lambda *a: (newTx, [utxo], [change])
        acct.sendToAddress(1, "recipient")
        assert Signals.b.available == changeVal

        # purchase some tickets. Reusing newTx for convenience again.
        ticket = account.UTXO.parse(dcrdataUTXO)
        ticket.setTicketInfo(dcrdataTinfo)
        ticket.scriptPubKey = ticketScript
        ticket.parseScriptClass()
        acct.blockchain.purchaseTickets = lambda *a: ([newTx, []], [],
                                                      [ticket])
        acct.signals.spentTickets = lambda: True
        acct.purchaseTickets(1, 1)
        assert Signals.b.total == changeVal + ticket.satoshis

        # revoke the ticket
        ticketTx = msgtx.MsgTx.new()
        op = msgtx.TxOut(ticket.satoshis, ticket.scriptPubKey)
        ticketTx.addTxOut(op)
        ticket.tinfo.status = "missed"
        redeemHash = addrlib.AddressScriptHash(
            txscript.extractStakeScriptHash(ticketScript, opcode.OP_SSTX),
            nets.mainnet,
        )
        acct.stakePool().purchaseInfo.ticketAddress = redeemHash.string()
        revoked = False

        def rev(*a):
            nonlocal revoked
            revoked = True

        acct.blockchain.tx = lambda *a: ticketTx
        acct.blockchain.revokeTicket = rev
        acct.revokeTickets()
        assert revoked
示例#9
0
 def test_publicExtendedKey(self):
     db = KeyValueDatabase(":memory:").child("tmp")
     acct = self.newAccount(db)
     assert acct.publicExtendedKey(self.cryptoKey).string() == (
         "dpubZFbjvDBkxBN5CAeqNXCkNAFizVcdMUPKJYp1vLXzkiq16yMFzREd"
         "eQ1AzaGJ7uBBhZFBruG31MGZ5SkwevLK4PiLEFSEaZm143xgvBgkWhQ")
示例#10
0
    def test_gap_handling(self):
        db = KeyValueDatabase(":memory:").child("tmp")
        internalAddrs = [
            "DskHpgbEb6hqkuHchHhtyojpehFToEtjQSo",
            "Dsm4oCLnLraGDedfU5unareezTNT75kPbRb",
            "DsdN6A9bWhKKJ7PAGdcDLxQrYPKEnjnDv2N",
            "Dsifz8eRHvQrfaPXgHHLMDHZopFJq2pBPU9",
            "DsmmzJiTTmpafdt2xx7LGk8ZW8cdAKe53Zx",
            "DsVB47P4N23PK5C1RyaJdqkQDzVuCDKGQbj",
            "DsouVzScdUUswvtCAv6nxRzi2MeufpWnELD",
            "DsSoquT5SiDPfksgnveLv3r524k1v8RamYm",
            "DsbVDrcfhbdLy4YaSmThmWN47xhxT6FC8XB",
            "DsoSrtGYKruQLbvhA92xeJ6eeuAR1GGJVQA",
        ]

        externalAddrs = [
            "DsmP6rBEou9Qr7jaHnFz8jTfrUxngWqrKBw",
            "DseZQKiWhN3ceBwDJEgGhmwKD3fMbwF4ugf",
            "DsVxujP11N72PJ46wgU9sewptWztYUy3L7o",
            "DsYa4UBo379cRMCTpDLxYVfpMvMNBdAGrGS",
            "DsVSEmQozEnsZ9B3D4Xn4H7kEedDyREgc18",
            "DsifDp8p9mRocNj7XNNhGAsYtfWhccc2cry",
            "DsV78j9aF8NBwegbcpPkHYy9cnPM39jWXZm",
            "DsoLa9Rt1L6qAVT9gSNE5F5XSDLGoppMdwC",
            "DsXojqzUTnyRciPDuCFFoKyvQFd6nQMn7Gb",
            "DsWp4nShu8WxefgoPej1rNv4gfwy5AoULfV",
        ]

        ogGapLimit = account.DefaultGapLimit
        account.DefaultGapLimit = gapLimit = 5
        acct = self.newAccount(db)
        with pytest.raises(DecredError):
            acct.generateGapAddresses()
        acct.unlock(self.cryptoKey)
        acct.generateGapAddresses()
        acct.gapLimit = gapLimit

        listsAreEqual = lambda a, b: len(a) == len(b) and all(
            x == y for x, y in zip(a, b))

        watchAddrs = internalAddrs[:gapLimit] + externalAddrs[:gapLimit + 1]
        assert len(acct.watchAddrs()) == len(watchAddrs)
        assert set(acct.watchAddrs()) == set(watchAddrs)
        assert listsAreEqual(acct.internalAddresses, internalAddrs[:gapLimit])

        # The external branch starts with the "last seen" at the zeroth address, so
        # has one additional address to start.
        assert listsAreEqual(acct.externalAddresses,
                             externalAddrs[:gapLimit + 1])

        # Open the account to generate addresses.
        acct.addTxid(internalAddrs[0], "C4fA6958A1847D")
        newAddrs = acct.generateGapAddresses()
        assert len(newAddrs) == 1
        assert newAddrs[0] == internalAddrs[5]

        # The zeroth external address is considered "seen", so this should not
        # change anything.
        acct.addTxid(externalAddrs[0], "C4fA6958A1847D")
        newAddrs = acct.generateGapAddresses()
        assert len(newAddrs) == 0

        # Mark the 1st address as seen.
        acct.addTxid(externalAddrs[1], "C4fA6958A1847D")
        newAddrs = acct.generateGapAddresses()
        assert len(newAddrs) == 1
        assert externalAddrs[1] == acct.currentAddress()

        # cursor should be at index 0, last seen 1, max index 6, so calling
        # nextExternalAddress 5 time should put the cursor at index 6, which is
        # the gap limit.
        for i in range(5):
            acct.nextExternalAddress()
        assert acct.currentAddress() == externalAddrs[6]

        # one more should wrap the cursor back to 1, not zero, so the current
        # address is lastSeenExt(=1) + cursor(=1) = 2
        a1 = acct.nextExternalAddress()
        assert acct.currentAddress() == externalAddrs[2]
        assert a1 == acct.currentAddress()

        # Sanity check that internal addresses are wrapping too.
        for i in range(20):
            acct.nextInternalAddress()
        addrs = acct.internalAddresses
        assert addrs[len(addrs) - 1] == internalAddrs[5]

        account.DefaultGapLimit = ogGapLimit