forked from Qortal/qortal
Merge pull request #159 from kennycud/master
Added Core API endpoint to repair LTC wallets
This commit is contained in:
commit
903fa0346a
@ -265,4 +265,43 @@ public class CrossChainLitecoinResource {
|
||||
|
||||
return CrossChainUtils.buildServerConfigurationInfo(Litecoin.getInstance());
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/repair")
|
||||
@Operation(
|
||||
summary = "Sends all coins in wallet to primary receive address",
|
||||
description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string",
|
||||
description = "BIP32 'm' private/public key in base58",
|
||||
example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc"
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "transaction hash"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
|
||||
@SecurityRequirement(name = "apiKey")
|
||||
public String repairOldWallet(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Litecoin litecoin = Litecoin.getInstance();
|
||||
|
||||
if (!litecoin.isValidDeterministicKey(key58))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
try {
|
||||
return litecoin.repairOldWallet(key58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
}
|
||||
}
|
||||
}
|
@ -505,7 +505,7 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
|
||||
List<String> candidates = this.getSpendingCandidateAddresses(key58);
|
||||
|
||||
for(DeterministicKey key : getWalletKeys(key58)) {
|
||||
for(DeterministicKey key : getOldWalletKeys(key58)) {
|
||||
infos.add(buildAddressInfo(key, candidates));
|
||||
}
|
||||
|
||||
@ -592,11 +592,23 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
}
|
||||
}
|
||||
|
||||
private List<DeterministicKey> getWalletKeys(String key58) throws ForeignBlockchainException {
|
||||
/**
|
||||
* Get Old Wallet Keys
|
||||
*
|
||||
* Get wallet keys using the old key generation algorithm. This is used for diagnosing and repairing wallets
|
||||
* created before 2024.
|
||||
*
|
||||
* @param masterPrivateKey
|
||||
*
|
||||
* @return the keys
|
||||
*
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private List<DeterministicKey> getOldWalletKeys(String masterPrivateKey) throws ForeignBlockchainException {
|
||||
synchronized (this) {
|
||||
Context.propagate(bitcoinjContext);
|
||||
|
||||
Wallet wallet = walletFromDeterministicKey58(key58);
|
||||
Wallet wallet = walletFromDeterministicKey58(masterPrivateKey);
|
||||
DeterministicKeyChain keyChain = wallet.getActiveKeyChain();
|
||||
|
||||
keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT);
|
||||
@ -998,4 +1010,52 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
return Wallet.fromWatchingKeyB58(this.params, key58, DeterministicHierarchy.BIP32_STANDARDISATION_TIME_SECS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair Wallet
|
||||
*
|
||||
* Repair wallets generated before 2024 by moving all the address balances to the first address.
|
||||
*
|
||||
* @param privateMasterKey
|
||||
*
|
||||
* @return the transaction Id of the spend operation that moves the balances or the exception name if an exception
|
||||
* is thrown
|
||||
*
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
public String repairOldWallet(String privateMasterKey) throws ForeignBlockchainException {
|
||||
|
||||
// create a deterministic wallet to satisfy the bitcoinj API
|
||||
Wallet wallet = Wallet.createDeterministic(this.bitcoinjContext, ScriptType.P2PKH);
|
||||
|
||||
// use the blockchain resources of this instance for UTXO provision
|
||||
wallet.setUTXOProvider(new BitcoinyUTXOProvider( this ));
|
||||
|
||||
// import in each that is generated using the old key generation algorithm
|
||||
List<DeterministicKey> walletKeys = getOldWalletKeys(privateMasterKey);
|
||||
|
||||
for( DeterministicKey key : walletKeys) {
|
||||
wallet.importKey(ECKey.fromPrivate(key.getPrivKey()));
|
||||
}
|
||||
|
||||
// get the primary receive address
|
||||
Address firstAddress = Address.fromKey(this.params, walletKeys.get(0), ScriptType.P2PKH);
|
||||
|
||||
// send all the imported coins to the primary receive address
|
||||
SendRequest sendRequest = SendRequest.emptyWallet(firstAddress);
|
||||
sendRequest.feePerKb = this.getFeePerKb();
|
||||
|
||||
try {
|
||||
// allow the wallet to build the send request transaction and broadcast
|
||||
wallet.completeTx(sendRequest);
|
||||
broadcastTransaction(sendRequest.tx);
|
||||
|
||||
// return the transaction Id
|
||||
return sendRequest.tx.getTxId().toString();
|
||||
}
|
||||
catch( Exception e ) {
|
||||
// log error and return exception name
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
return e.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
package org.qortal.crosschain;
|
||||
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class BitcoinyUTXOProvider
|
||||
*
|
||||
* Uses Bitcoiny resources for UTXO provision.
|
||||
*/
|
||||
public class BitcoinyUTXOProvider implements UTXOProvider {
|
||||
|
||||
private Bitcoiny bitcoiny;
|
||||
|
||||
public BitcoinyUTXOProvider(Bitcoiny bitcoiny) {
|
||||
this.bitcoiny = bitcoiny;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UTXO> getOpenTransactionOutputs(List<ECKey> keys) throws UTXOProviderException {
|
||||
try {
|
||||
List<UTXO> utxos = new ArrayList<>();
|
||||
|
||||
for( ECKey key : keys) {
|
||||
Address address = Address.fromKey(this.bitcoiny.params, key, Script.ScriptType.P2PKH);
|
||||
byte[] script = ScriptBuilder.createOutputScript(address).getProgram();
|
||||
|
||||
// collection UTXO's for all confirmed unspent outputs
|
||||
for (UnspentOutput output : this.bitcoiny.blockchainProvider.getUnspentOutputs(script, false)) {
|
||||
utxos.add(toUTXO(output));
|
||||
}
|
||||
}
|
||||
return utxos;
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw new UTXOProviderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Unspent Output to a UTXO
|
||||
*
|
||||
* @param unspentOutput
|
||||
*
|
||||
* @return the UTXO
|
||||
*
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private UTXO toUTXO(UnspentOutput unspentOutput) throws ForeignBlockchainException {
|
||||
List<TransactionOutput> transactionOutputs = this.bitcoiny.getOutputs(unspentOutput.hash);
|
||||
|
||||
TransactionOutput transactionOutput = transactionOutputs.get(unspentOutput.index);
|
||||
|
||||
return new UTXO(
|
||||
Sha256Hash.wrap(unspentOutput.hash),
|
||||
unspentOutput.index,
|
||||
Coin.valueOf(unspentOutput.value),
|
||||
unspentOutput.height,
|
||||
false,
|
||||
transactionOutput.getScriptPubKey()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChainHeadHeight() throws UTXOProviderException {
|
||||
try {
|
||||
return this.bitcoiny.blockchainProvider.getCurrentHeight();
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw new UTXOProviderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkParameters getParams() {
|
||||
return this.bitcoiny.params;
|
||||
}
|
||||
}
|
@ -99,6 +99,14 @@ public abstract class BitcoinyTests extends Common {
|
||||
transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
@Test
|
||||
public void testRepair() throws ForeignBlockchainException {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
String transaction = bitcoiny.repairOldWallet(xprv58);
|
||||
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
|
Loading…
Reference in New Issue
Block a user