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
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(), [], {})
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)
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"
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")
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
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)
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
def test_publicExtendedKey(self): db = KeyValueDatabase(":memory:").child("tmp") acct = self.newAccount(db) assert acct.publicExtendedKey(self.cryptoKey).string() == ( "dpubZFbjvDBkxBN5CAeqNXCkNAFizVcdMUPKJYp1vLXzkiq16yMFzREd" "eQ1AzaGJ7uBBhZFBruG31MGZ5SkwevLK4PiLEFSEaZm143xgvBgkWhQ")
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