Skip to content

Commit

Permalink
Practically rewrote the get unused receive address functionality. The…
Browse files Browse the repository at this point in the history
… prior implementation was skipping addresses.
  • Loading branch information
kennycud committed Dec 22, 2023
1 parent 46a9075 commit 423a1f7
Showing 1 changed file with 8 additions and 57 deletions.
65 changes: 8 additions & 57 deletions src/main/java/org/qortal/crosschain/Bitcoiny.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.bitcoinj.script.Script.ScriptType;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.KeyChain;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
import org.qortal.api.model.SimpleForeignTransaction;
Expand Down Expand Up @@ -720,7 +721,7 @@ else if (!transactionInvolvesExternalWallet) {
}

/**
* Returns first unused receive address given 'm' BIP32 key.
* Returns first unused receive address given a BIP32 key.
*
* @param key58 BIP32/HD extended Bitcoin private/public key
* @return P2PKH address
Expand All @@ -732,65 +733,15 @@ public String getUnusedReceiveAddress(String key58) throws ForeignBlockchainExce
Wallet wallet = walletFromDeterministicKey58(key58);
DeterministicKeyChain keyChain = wallet.getActiveKeyChain();

keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT);
keyChain.maybeLookAhead();

final int keyChainPathSize = keyChain.getAccountPath().size();
List<DeterministicKey> keys = new ArrayList<>(keyChain.getLeafKeys());

int ki = 0;
do {
for (; ki < keys.size(); ++ki) {
DeterministicKey dKey = keys.get(ki);
List<ChildNumber> dKeyPath = dKey.getPath();

// If keyChain is based on 'm', then make sure dKey is m/0/ki - i.e. a 'receive' address, not 'change' (m/1/ki)
if (dKeyPath.size() != keyChainPathSize + 2 || dKeyPath.get(dKeyPath.size() - 2) != ChildNumber.ZERO)
continue;

// Check unspent
Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH);
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();

List<UnspentOutput> unspentOutputs = this.blockchainProvider.getUnspentOutputs(script, false);

/*
* If there are no unspent outputs then either:
* a) all the outputs have been spent
* b) address has never been used
*
* For case (a) we want to remember not to check this address (key) again.
*/

if (unspentOutputs.isEmpty()) {
// If this is a known key that has been spent before, then we can skip asking for transaction history
if (this.spentKeys.contains(dKey)) {
wallet.getActiveKeyChain().markKeyAsUsed(dKey);
continue;
}

// Ask for transaction history - if it's empty then key has never been used
List<TransactionHash> historicTransactionHashes = this.blockchainProvider.getAddressTransactions(script, false);

if (!historicTransactionHashes.isEmpty()) {
// Fully spent key - case (a)
this.spentKeys.add(dKey);
wallet.getActiveKeyChain().markKeyAsUsed(dKey);
continue;
}

// Key never been used - case (b)
return address.toString();
}

// Key has unspent outputs, hence used, so no good to us
this.spentKeys.remove(dKey);
}
// the next receive funds address
Address address = Address.fromKey(this.params, keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS), ScriptType.P2PKH);

// Generate some more keys
keys.addAll(generateMoreKeys(keyChain));
// if zero transactions, return address
if( 0 == getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).size() )
return address.toString();

// Process new keys
// else try the next receive funds address
} while (true);
}

Expand Down

0 comments on commit 423a1f7

Please sign in to comment.