Skip to content

Commit

Permalink
Coldstake redeem bug (#396)
Browse files Browse the repository at this point in the history
* Add a list of RedeemScripts property to replace the single entry RedeemScript

* Fix issue link

* Create script array json converter

* Remove all functionality frmo the old RedeemScript propert and assign its value on startup

* fix failing test
  • Loading branch information
dangershony authored Apr 28, 2022
1 parent 1a1c2ee commit cfeda4a
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Blockcore.Consensus.ScriptInfo;
using Blockcore.Consensus.TransactionInfo;
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;

namespace Blockcore.Utilities.JsonConverters
{
/// <summary>
/// Converter used to convert a <see cref="Script"/> or a <see cref="WitScript"/> to and from JSON.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public class ScriptCollectionJsonConverter : JsonConverter
{
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ICollection<Script>) || objectType == typeof(ICollection<WitScript>);
}

/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;

try
{
var array = serializer.Deserialize<string[]>(reader);

if (objectType == typeof(ICollection<Script>))
{
return array.Select(s => Script.FromBytesUnsafe(Encoders.Hex.DecodeData(s))).ToList();
}

if (objectType == typeof(ICollection<WitScript>))
{
return array.Select(s => new WitScript(s)).ToList();
}
}
catch (FormatException)
{
}

throw new JsonObjectException("A script should be a byte string", reader);
}

/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
{
if (value is ICollection<Script> array)
{
var serialize = array.Select(s => Encoders.Hex.EncodeData(s.ToBytes(false))).ToArray();
serializer.Serialize(writer, serialize);
}

if (value is ICollection<WitScript> arrayw)
{
var serialize = arrayw.Select(s => s.ToString()).ToArray();
serializer.Serialize(writer, serialize);
}
}
}
}
}
69 changes: 52 additions & 17 deletions src/Features/Blockcore.Features.ColdStaking/ColdStakingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler wa
if (payToScript)
{
HdAddress address = coldAddress ?? hotAddress;
address.RedeemScript = destination;
if (address.RedeemScripts == null)
address.RedeemScripts = new List<Script>();
address.RedeemScripts.Add(destination);
destination = destination.WitHash.ScriptPubKey;
}
}
Expand All @@ -405,7 +407,9 @@ internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler wa
if (payToScript)
{
HdAddress address = coldAddress ?? hotAddress;
address.RedeemScript = destination;
if (address.RedeemScripts == null)
address.RedeemScripts = new List<Script>();
address.RedeemScripts.Add(destination);
destination = destination.Hash.ScriptPubKey;
}
}
Expand Down Expand Up @@ -567,11 +571,16 @@ internal Transaction GetColdStakingWithdrawalTransaction(IWalletTransactionHandl

if (prevscript.IsScriptType(ScriptType.P2SH) || prevscript.IsScriptType(ScriptType.P2WSH))
{
if (item.Address.RedeemScript == null)
throw new WalletException("Missing redeem script");
if (item.Address.RedeemScripts == null)
throw new WalletException("Wallet has no redeem scripts");

Script redeemScript = item.Address.RedeemScripts.FirstOrDefault(r => r.Hash.ScriptPubKey == item.Transaction.ScriptPubKey || r.WitHash.ScriptPubKey == item.Transaction.ScriptPubKey);

if (redeemScript == null)
throw new WalletException($"RedeemScript was not found for address '{item.Address.Address}' with output '{item.Transaction.ScriptPubKey}'");

// Provide the redeem script to the builder
var scriptCoin = ScriptCoin.Create(this.network, item.ToOutPoint(), new TxOut(item.Transaction.Amount, prevscript), item.Address.RedeemScript);
var scriptCoin = ScriptCoin.Create(this.network, item.ToOutPoint(), new TxOut(item.Transaction.Amount, prevscript), redeemScript);
context.TransactionBuilder.AddCoins(scriptCoin);
}

Expand Down Expand Up @@ -626,12 +635,18 @@ public override void TransactionFoundInternal(Wallet.Types.Wallet wallet, Script
{
if (this.walletIndex[wallet.Name].ScriptToAddressLookup.TryGetValue(script, out HdAddress address))
{
if (ColdStakingScriptTemplate.Instance.ExtractScriptPubKeyParameters(address.RedeemScript, out hotPubKeyHash, out coldPubKeyHash))
if (address.RedeemScripts != null)
{
base.TransactionFoundInternal(wallet, hotPubKeyHash.ScriptPubKey, a => a.Index == HotWalletAccountIndex);
base.TransactionFoundInternal(wallet, coldPubKeyHash.ScriptPubKey, a => a.Index == ColdWalletAccountIndex);
foreach (Script redeemScript in address.RedeemScripts)
{
if (ColdStakingScriptTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript, out hotPubKeyHash, out coldPubKeyHash))
{
base.TransactionFoundInternal(wallet, hotPubKeyHash.ScriptPubKey, a => a.Index == HotWalletAccountIndex);
base.TransactionFoundInternal(wallet, coldPubKeyHash.ScriptPubKey, a => a.Index == ColdWalletAccountIndex);

return;
return;
}
}
}
}
}
Expand Down Expand Up @@ -670,7 +685,14 @@ public override bool ProcessTransaction(Transaction transaction, int? blockHeigh
|| walletIndexItem.Value.ScriptToAddressLookup.TryGetValue(coldPubKey.ScriptPubKey, out address))
{
Script destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKey, coldPubKey);
address.RedeemScript = destination;

if (address.RedeemScripts == null)
address.RedeemScripts = new List<Script>();

if (!address.RedeemScripts.Contains(destination))
{
address.RedeemScripts.Add(destination);
}

// Find the type of script for the opreturn (P2SH or P2WSH)
foreach (TxOut utxoInner in transaction.Outputs)
Expand Down Expand Up @@ -705,16 +727,29 @@ protected override void AddAddressToIndex(Wallet.Types.Wallet wallet, HdAddress
{
base.AddAddressToIndex(wallet, address);

if (address.RedeemScript != null)
if (address.RedeemScriptObsolete != null)
{
// this is to support legacy wallet that still have the RedeemScript set
// we just push it to the RedeemScripts collection and go on as usual.
if (address.RedeemScripts == null)
address.RedeemScripts = new List<Script>();

address.RedeemScripts.Add(address.RedeemScriptObsolete);
}

if (address.RedeemScripts != null)
{
// The redeem script has no indication on the script type (P2SH or P2WSH),
// so we track both, add both to the indexer then.
foreach (Script redeemScript in address.RedeemScripts)
{
// The redeem script has no indication on the script type (P2SH or P2WSH),
// so we track both, add both to the indexer then.

if (!this.walletIndex[wallet.Name].ScriptToAddressLookup.TryGetValue(address.RedeemScript.Hash.ScriptPubKey, out HdAddress _))
this.walletIndex[wallet.Name].ScriptToAddressLookup[address.RedeemScript.Hash.ScriptPubKey] = address;
if (!this.walletIndex[wallet.Name].ScriptToAddressLookup.TryGetValue(redeemScript.Hash.ScriptPubKey, out HdAddress _))
this.walletIndex[wallet.Name].ScriptToAddressLookup[redeemScript.Hash.ScriptPubKey] = address;

if (!this.walletIndex[wallet.Name].ScriptToAddressLookup.TryGetValue(address.RedeemScript.WitHash.ScriptPubKey, out HdAddress _))
this.walletIndex[wallet.Name].ScriptToAddressLookup[address.RedeemScript.WitHash.ScriptPubKey] = address;
if (!this.walletIndex[wallet.Name].ScriptToAddressLookup.TryGetValue(redeemScript.WitHash.ScriptPubKey, out HdAddress _))
this.walletIndex[wallet.Name].ScriptToAddressLookup[redeemScript.WitHash.ScriptPubKey] = address;
}
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions src/Features/Blockcore.Features.Miner/Staking/PosMinting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -947,10 +947,15 @@ private bool SignTransactionInput(UtxoStakeDescription input, Transaction transa
if (PayToScriptHashTemplate.Instance.CheckScriptPubKey(input.TxOut.ScriptPubKey) ||
PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(input.TxOut.ScriptPubKey))
{
if (input.Address.RedeemScript == null)
throw new MinerException("Redeem script does not match output");
if (input.Address.RedeemScripts == null)
throw new MinerException("Wallet has no redeem scripts");

var scriptCoin = ScriptCoin.Create(this.network, input.OutPoint, input.TxOut, input.Address.RedeemScript);
Script redeemScript = input.Address.RedeemScripts.FirstOrDefault(r => r.Hash.ScriptPubKey == input.TxOut.ScriptPubKey || r.WitHash.ScriptPubKey == input.TxOut.ScriptPubKey);

if (redeemScript == null)
throw new MinerException($"RedeemScript was not found for address {input.Address.Address} with output {input.TxOut.ScriptPubKey}");

var scriptCoin = ScriptCoin.Create(this.network, input.OutPoint, input.TxOut, redeemScript);

transactionBuilder.AddCoins(scriptCoin);
}
Expand All @@ -968,7 +973,7 @@ private bool SignTransactionInput(UtxoStakeDescription input, Transaction transa
}
catch (Exception e)
{
this.logger.LogDebug("Exception occurred: {0}", e.ToString());
this.logger.LogWarning("Exception occurred: {0}", e.ToString());
}

return res;
Expand Down
14 changes: 13 additions & 1 deletion src/Features/Blockcore.Features.Wallet/Types/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -930,9 +930,21 @@ public HdAddress()
/// <summary>
/// A script that is used for P2SH and P2WSH scenarios (mostly used for staking).
/// </summary>
/// <remarks>
/// Obsolete: This is kept for legacy reasons, use the the <see cref="RedeemScripts"/> property instead.
/// </remarks>
[JsonProperty(PropertyName = "redeemScript")]
[JsonConverter(typeof(ScriptJsonConverter))]
public Script RedeemScript { get; set; }
[ObsoleteAttribute("This is kept for legacy reasons, use the the RedeemScripts property instead.")]
public Script RedeemScriptObsolete { get; set; }

/// <summary>
/// A collection of scripts that is used for P2SH and P2WSH scenarios (mostly used for cold staking).
/// This solves issue https://github.com/block-core/blockcore/issues/395
/// </summary>
[JsonProperty(PropertyName = "redeemScripts")]
[JsonConverter(typeof(ScriptCollectionJsonConverter))]
public ICollection<Script> RedeemScripts { get; set; }

/// <summary>
/// A path to the address as defined in BIP44.
Expand Down
11 changes: 8 additions & 3 deletions src/Networks/Blockcore.Networks.X1/Components/X1PosMinting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -961,10 +961,15 @@ private bool SignTransactionInput(UtxoStakeDescription input, Transaction transa
if (PayToScriptHashTemplate.Instance.CheckScriptPubKey(input.TxOut.ScriptPubKey) ||
PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(input.TxOut.ScriptPubKey))
{
if (input.Address.RedeemScript == null)
throw new MinerException("Redeem script does not match output");
if (input.Address.RedeemScripts == null)
throw new MinerException("Wallet has no redeem scripts");

var scriptCoin = ScriptCoin.Create(this.network, input.OutPoint, input.TxOut, input.Address.RedeemScript);
Script redeemScript = input.Address.RedeemScripts.FirstOrDefault(r => r.Hash.ScriptPubKey == input.TxOut.ScriptPubKey || r.WitHash.ScriptPubKey == input.TxOut.ScriptPubKey);

if (redeemScript == null)
throw new MinerException($"RedeemScript was not found for address {input.Address.Address} with output {input.TxOut.ScriptPubKey}");

var scriptCoin = ScriptCoin.Create(this.network, input.OutPoint, input.TxOut, redeemScript);

transactionBuilder.AddCoins(scriptCoin);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,7 @@ private Transaction AddSpendableColdstakingTransactionToWallet(Wallet.Types.Wall
transaction.Outputs.Add(new TxOut(Money.Coins(101), script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey));

if (script)
address.RedeemScript = scriptPubKey;
address.RedeemScripts = new List<Script> { scriptPubKey };

wallet.walletStore.InsertOrUpdate(new TransactionOutputData()
{
Expand Down Expand Up @@ -912,7 +912,7 @@ private Transaction AddSpendableColdstakingTransactionToNormalWallet(Wallet.Type
transaction.Outputs.Add(new TxOut(Money.Coins(202), script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey));

if (script)
address.RedeemScript = scriptPubKey;
address.RedeemScripts = new List<Script> { scriptPubKey };

wallet.walletStore.InsertOrUpdate(new TransactionOutputData()
{
Expand Down

0 comments on commit cfeda4a

Please sign in to comment.