Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Bitcoin.Features.Wallet;

namespace Stratis.Bitcoin.Features.Interop.ETHClient
{
Expand Down
209 changes: 105 additions & 104 deletions src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs

Large diffs are not rendered by default.

23 changes: 11 additions & 12 deletions src/Stratis.Bitcoin.Features.Interop/InteropSettings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using Stratis.Bitcoin.Configuration;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Bitcoin.Features.Wallet;

namespace Stratis.Bitcoin.Features.Interop
{
Expand All @@ -10,7 +10,6 @@ public class InteropSettings

public BNBInteropSettings BNBSettings { get; set; }


public InteropSettings(NodeSettings nodeSettings)
{
this.ETHSettings = new ETHInteropSettings(nodeSettings);
Expand All @@ -22,13 +21,13 @@ public ETHInteropSettings GetSettingsByChain(DestinationChain chain)
switch (chain)
{
case DestinationChain.ETH:
{
return this.ETHSettings;
}
{
return this.ETHSettings;
}
case DestinationChain.BNB:
{
return this.BNBSettings;
}
{
return this.BNBSettings;
}
}

throw new NotImplementedException("Provided chain type not supported: " + chain);
Expand Down Expand Up @@ -56,7 +55,7 @@ public class ETHInteropSettings

/// <summary>Passphrase for the ethereum account.</summary>
public string Passphrase { get; set; }

/// <summary>The gas limit for Ethereum interoperability transactions.</summary>
public int GasLimit { get; set; }

Expand All @@ -67,10 +66,10 @@ public class ETHInteropSettings

/// <summary>This is intended for future functionality and should therefore not be provided/set yet.</summary>
public string InteropContractCirrusAddress { get; set; }

/// <summary>This is intended for future functionality and should therefore not be provided/set yet.</summary>
public string InteropContractAddress { get; set; }

#endregion

public ETHInteropSettings(NodeSettings nodeSettings)
Expand Down Expand Up @@ -105,7 +104,7 @@ public ETHInteropSettings(NodeSettings nodeSettings)
if (string.IsNullOrWhiteSpace(this.ClientUrl))
throw new Exception($"Cannot initialize interoperability feature without -{clientUrlKey} specified.");
}

protected virtual string GetSettingsPrefix()
{
return "eth_";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using NBitcoin;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.P2P.Protocol.Payloads;
using Stratis.Features.FederatedPeg.Conversion;

namespace Stratis.Bitcoin.Features.Interop.Payloads
{
Expand All @@ -13,9 +13,9 @@ public class InteropCoordinationPayload : Payload
private int destinationChain;

public string RequestId => this.requestId;

public int TransactionId => this.transactionId;

public string Signature => this.signature;

public DestinationChain DestinationChain { get { return (DestinationChain)this.destinationChain; } set { this.destinationChain = (int)value; } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public void CanEncodeAndDecode()
int chain = 3;
string address = "0x51EC92A3aB8cfcA412Ea43766A9259523fC81501";

string encoded = InterFluxOpReturnEncoder.Encode(chain, address);
string encoded = InterFluxOpReturnEncoder.Encode((DestinationChain)chain, address);

bool success = InterFluxOpReturnEncoder.TryDecode(encoded, out int resultChain, out string resultAddress);

Expand All @@ -28,13 +28,26 @@ public void CanEncodeAndDecode()
Assert.Equal(address, resultAddress2);
}

[Fact]
public void EncodeAndDecodeETHAddress()
{
string address = "0xd2390da742872294BE05dc7359D7249d7C79460E";
string encoded = InterFluxOpReturnEncoder.Encode(DestinationChain.ETH, address);
bool success = InterFluxOpReturnEncoder.TryDecode(encoded, out int resultChain, out string resultAddress);

Assert.True(success);
Assert.Equal(DestinationChain.ETH, (DestinationChain)resultChain);
Assert.Equal(address, resultAddress);
}

[Fact]
public void DecodeDoesntThrowWhenFormatIsIncorrect()
{
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER_3_345345", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER3_", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTERefsdvsdvdsvsdv", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("xvev456545cwsdfFSXVB365", out int _, out string _));
Assert.False(InterFluxOpReturnEncoder.TryDecode("INTER1_aaaa", out int _, out string _));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ public async Task<IActionResult> BuildTransaction([FromBody] BuildTransactionReq
public async Task<IActionResult> BuildInterFluxTransaction([FromBody] BuildInterFluxTransactionRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
request.OpReturnData = InterFluxOpReturnEncoder.Encode(request.DestinationChain, request.DestinationAddress);
request.OpReturnData = InterFluxOpReturnEncoder.Encode((DestinationChain)request.DestinationChain, request.DestinationAddress);

return await this.Execute(request, cancellationToken,
async (req, token) => Json(await this.walletService.BuildTransaction(req, token)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static bool TryGetDepositsToMultisig(Network network, Transaction transac
depositsToMultisig = transaction.Outputs.Where(output =>
output.ScriptPubKey == depositScript &&
output.Value >= crossChainTransferMinimum).ToList();

return depositsToMultisig.Any();
}

Expand All @@ -49,9 +49,10 @@ public static bool TryGetTarget(Transaction transaction, IOpReturnDataReader opR
conversion = false;
targetChain = 0 /* DestinationChain.STRAX */;

// Check the common case first.
// First check cross chain transfers from the STRAX to Cirrus network or vice versa.
if (!opReturnDataReader.TryGetTargetAddress(transaction, out targetAddress))
{
// Else try and validate the destination adress by the destination chain.
byte[] opReturnBytes = OpReturnDataReader.SelectBytesContentFromOpReturn(transaction).FirstOrDefault();

if (opReturnBytes != null && InterFluxOpReturnEncoder.TryDecode(opReturnBytes, out int destinationChain, out targetAddress))
Expand All @@ -61,7 +62,7 @@ public static bool TryGetTarget(Transaction transaction, IOpReturnDataReader opR
else
return false;

conversion = true;
conversion = true;
}

return true;
Expand Down
43 changes: 38 additions & 5 deletions src/Stratis.Bitcoin.Features.Wallet/InterFluxOpReturnEncoder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System.Text.RegularExpressions;

namespace Stratis.Bitcoin.Features.Wallet
{
Expand All @@ -7,9 +8,9 @@ public static class InterFluxOpReturnEncoder
{
private static string InterFluxPrefix = "INTER";

public static string Encode(int destinationChain, string address)
public static string Encode(DestinationChain destinationChain, string address)
{
return InterFluxPrefix + destinationChain + "_" + address;
return InterFluxPrefix + (int)destinationChain + "_" + address;
}

public static bool TryDecode(string opReturnData, out int destinationChain, out string address)
Expand All @@ -23,15 +24,35 @@ public static bool TryDecode(string opReturnData, out int destinationChain, out
if (prefixIndex == -1 || separatorIndex == -1)
return false;

// Try and extract the destination chain.
if (!int.TryParse(opReturnData.Substring(InterFluxPrefix.Length, separatorIndex - InterFluxPrefix.Length), out destinationChain))
return false;

address = opReturnData.Substring(separatorIndex + 1);

if (string.IsNullOrEmpty(address))
return false;
// If the destination chain is Ethereum, try and validate.
// Once conversions to other chains are implemented, we can add validation here.
if (destinationChain == (int)DestinationChain.ETH)
{
if (!TryConvertValidOpReturnDataToETHAddress(address))
return false;
}

return !string.IsNullOrEmpty(address);
}

return true;
/// <summary>
/// Try and validate an address on the Ethereum chain.
/// </summary>
/// <param name="potentialETHAddress">The address to validate.</param>
/// <returns><c>true</c> if valid, <c>false</c> otherwise.</returns>
private static bool TryConvertValidOpReturnDataToETHAddress(string potentialETHAddress)
{
// Attempt to parse the string. An Ethereum address is 42 characters:
// 0x - initial prefix
// <20 bytes> - rightmost 20 bytes of the Keccak hash of a public key, encoded as hex
Match match = Regex.Match(potentialETHAddress, @"^0x([A-Fa-f0-9]{40})$", RegexOptions.IgnoreCase);
return match.Success;
}

public static bool TryDecode(byte[] opReturnData, out int destinationChain, out string address)
Expand All @@ -41,4 +62,16 @@ public static bool TryDecode(byte[] opReturnData, out int destinationChain, out
return TryDecode(stringData, out destinationChain, out address);
}
}

/// <summary>Chains supported by InterFlux integration.</summary>
public enum DestinationChain
{
STRAX = 0, // Stratis
ETH, // Ethereum
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be wise to explicitly set all the rest, so that no one is tempted to insert new chains in the middle of the enum in future

BNB, // Binance

ETC, // Ethereum classic
AVAX, // Avalanche
ADA, // Cardano
}
}
37 changes: 0 additions & 37 deletions src/Stratis.Bitcoin/OpReturnDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NBitcoin;
using NLog;
using TracerAttributes;
Expand All @@ -26,8 +25,6 @@ public interface IOpReturnDataReader
/// <returns><c>true</c> if address was extracted; <c>false</c> otherwise.</returns>
bool TryGetTargetAddress(Transaction transaction, out string address);

bool TryGetTargetETHAddress(Transaction transaction, out string address);

/// <summary>
/// Tries to find a single OP_RETURN output that can be interpreted as a transaction id.
/// </summary>
Expand Down Expand Up @@ -68,24 +65,6 @@ public bool TryGetTargetAddress(Transaction transaction, out string address)
return true;
}

public bool TryGetTargetETHAddress(Transaction transaction, out string address)
{
var opReturnAddresses = SelectBytesContentFromOpReturn(transaction)
.Select(this.TryConvertValidOpReturnDataToETHAddress)
.Where(s => s != null)
.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();

// A standard OP_RETURN is not long enough to fit more than 1 Ethereum address, but a non-standard transaction could have multiple.
if (opReturnAddresses.Count != 1)
{
address = null;
return false;
}

address = opReturnAddresses[0];
return true;
}

/// <inheritdoc />
public bool TryGetTransactionId(Transaction transaction, out string txId)
{
Expand Down Expand Up @@ -134,22 +113,6 @@ private string TryConvertValidOpReturnDataToAddress(byte[] data)
}
}

private string TryConvertValidOpReturnDataToETHAddress(byte[] data)
{
// After removing the RETURN operator, convert the remaining bytes to our candidate address.
string destination = Encoding.UTF8.GetString(data);

// Attempt to parse the string. An Ethereum address is 42 characters:
// 0x - initial prefix
// <20 bytes> - rightmost 20 bytes of the Keccak hash of a public key, encoded as hex
Match match = Regex.Match(destination, @"^0x([A-Fa-f0-9]{40})$", RegexOptions.IgnoreCase);

if (!match.Success)
return null;

return destination;
}

private string TryConvertValidOpReturnDataToHash(byte[] data)
{
// Attempt to parse the hash. Validates the uint256 string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Controllers;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Features.Wallet.Models;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Features.FederatedPeg.Events;
using Stratis.Features.FederatedPeg.Interfaces;
using Stratis.Features.FederatedPeg.Models;
Expand Down
21 changes: 7 additions & 14 deletions src/Stratis.Features.FederatedPeg.Tests/DepositExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Stratis.Bitcoin;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Networks;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Features.FederatedPeg.Interfaces;
using Stratis.Features.FederatedPeg.SourceChain;
using Stratis.Features.FederatedPeg.Tests.Utils;
Expand Down Expand Up @@ -271,23 +270,23 @@ public void ExtractLargeConversionDeposits_ReturnDeposits_AboveNormalThreshold()
// Create the target address.
BitcoinPubKeyAddress targetAddress = this.addressHelper.GetNewTargetChainPubKeyAddress();
byte[] opReturnBytes = Encoding.UTF8.GetBytes(targetAddress.ToString());

// Set amount to be less than deposit minimum
CreateDepositTransaction(targetAddress, block, FederatedPegSettings.CrossChainTransferMinimum - 1, opReturnBytes);

// Set amount to be less than the small threshold amount.
CreateDepositTransaction(targetAddress, block, this.federationSettings.SmallDepositThresholdAmount - 1, opReturnBytes);

// Set amount to be exactly the normal threshold amount.
CreateDepositTransaction(targetAddress, block, this.federationSettings.NormalDepositThresholdAmount, opReturnBytes);

byte[] ethOpReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode((int)DestinationChain.ETH, TargetETHAddress));
byte[] ethOpReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode(DestinationChain.ETH, TargetETHAddress));

// Set amount to be equal to the normal threshold amount.
CreateConversionTransaction(TargetETHAddress, block, this.federationSettings.NormalDepositThresholdAmount, ethOpReturnBytes);
CreateConversionTransaction(block, this.federationSettings.NormalDepositThresholdAmount, ethOpReturnBytes);

// Set amount to be greater than the conversion deposit minimum amount.
CreateConversionTransaction(TargetETHAddress, block, Money.Coins(DepositExtractor.ConversionTransactionMinimum + 1), ethOpReturnBytes);
CreateConversionTransaction(block, Money.Coins(DepositExtractor.ConversionTransactionMinimum + 1), ethOpReturnBytes);

int blockHeight = 12345;
IReadOnlyList<IDeposit> extractedDeposits = this.depositExtractor.ExtractDepositsFromBlock(block, blockHeight, new[] { DepositRetrievalType.ConversionLarge });
Expand All @@ -301,20 +300,14 @@ public void ExtractLargeConversionDeposits_ReturnDeposits_AboveNormalThreshold()
}
}

private Transaction CreateConversionTransaction(string targetETHAddress, Block block, Money depositAmount, byte[] opReturnBytes)
private Transaction CreateConversionTransaction(Block block, Money depositAmount, byte[] opReturnBytes)
{
// Create the conversion transaction.
Transaction conversionTransaction = this.transactionBuilder.BuildOpReturnTransaction(this.addressHelper.SourceChainMultisigAddress, opReturnBytes, depositAmount);

// Add the conversion transaction to the block.
block.AddTransaction(conversionTransaction);

this.opReturnDataReader.TryGetTargetETHAddress(conversionTransaction, out string _).Returns(callInfo =>
{
callInfo[1] = targetETHAddress;
return true;
});

return conversionTransaction;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Stratis.Bitcoin.Primitives;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Bitcoin.Utilities;
using Stratis.Features.FederatedPeg.Conversion;
using Stratis.Features.FederatedPeg.Interfaces;
using Stratis.Features.FederatedPeg.Models;
using Stratis.Features.FederatedPeg.SourceChain;
Expand Down Expand Up @@ -54,7 +53,7 @@ public MaturedBlocksProviderTests()

this.addressHelper = new MultisigAddressHelper(this.network, this.mainChainNetwork);
this.targetAddress = this.addressHelper.GetNewTargetChainPubKeyAddress();
this.opReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode((int)DestinationChain.STRAX, this.targetAddress.ToString()));
this.opReturnBytes = Encoding.UTF8.GetBytes(InterFluxOpReturnEncoder.Encode(DestinationChain.STRAX, this.targetAddress.ToString()));

this.federatedPegSettings = Substitute.For<IFederatedPegSettings>();
this.federatedPegSettings.MultiSigRedeemScript.Returns(this.addressHelper.PayToMultiSig);
Expand Down
Loading