mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-01 15:27:51 +00:00
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());
|
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);
|
List<String> candidates = this.getSpendingCandidateAddresses(key58);
|
||||||
|
|
||||||
for(DeterministicKey key : getWalletKeys(key58)) {
|
for(DeterministicKey key : getOldWalletKeys(key58)) {
|
||||||
infos.add(buildAddressInfo(key, candidates));
|
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) {
|
synchronized (this) {
|
||||||
Context.propagate(bitcoinjContext);
|
Context.propagate(bitcoinjContext);
|
||||||
|
|
||||||
Wallet wallet = walletFromDeterministicKey58(key58);
|
Wallet wallet = walletFromDeterministicKey58(masterPrivateKey);
|
||||||
DeterministicKeyChain keyChain = wallet.getActiveKeyChain();
|
DeterministicKeyChain keyChain = wallet.getActiveKeyChain();
|
||||||
|
|
||||||
keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT);
|
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);
|
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);
|
transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||||
assertNotNull(transaction);
|
assertNotNull(transaction);
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
public void testRepair() throws ForeignBlockchainException {
|
||||||
|
String xprv58 = getDeterministicKey58();
|
||||||
|
|
||||||
|
String transaction = bitcoiny.repairOldWallet(xprv58);
|
||||||
|
|
||||||
|
assertNotNull(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user