forked from stratisproject/StratisBitcoinFullNode
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request stratisproject#312 from rowandh/proposer-build-tra…
…nsaction Build signed transaction in proposer
- Loading branch information
Showing
9 changed files
with
330 additions
and
15 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
src/Stratis.Feature.PoA.Tokenless.Tests/EndorsedTransactionBuilderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using Moq; | ||
using NBitcoin; | ||
using Newtonsoft.Json; | ||
using Stratis.Feature.PoA.Tokenless.Consensus; | ||
using Stratis.Feature.PoA.Tokenless.Endorsement; | ||
using Stratis.SmartContracts.CLR; | ||
using Stratis.SmartContracts.Core; | ||
using Stratis.SmartContracts.Core.ReadWrite; | ||
using Stratis.SmartContracts.Core.Util; | ||
using Xunit; | ||
|
||
namespace Stratis.Feature.PoA.Tokenless.Tests | ||
{ | ||
public class EndorsedTransactionBuilderTests | ||
{ | ||
private readonly List<SignedProposalResponse> proposalResponses; | ||
private readonly Key key; | ||
private readonly ProposalResponse response; | ||
private readonly TokenlessNetwork network; | ||
private readonly TokenlessSigner signer; | ||
private readonly Key transactionSigningKey; | ||
|
||
public EndorsedTransactionBuilderTests() | ||
{ | ||
this.network = new TokenlessNetwork(); | ||
this.signer = new TokenlessSigner(this.network, new SenderRetriever()); | ||
|
||
this.key = new Key(); | ||
this.transactionSigningKey = new Key(); | ||
|
||
this.response = new ProposalResponse | ||
{ | ||
ReadWriteSet = new ReadWriteSet | ||
{ | ||
Reads = new List<ReadItem> | ||
{ | ||
new ReadItem {ContractAddress = uint160.One, Key = new byte[] {0xAA}, Version = "1"} | ||
}, | ||
Writes = new List<WriteItem> | ||
{ | ||
new WriteItem | ||
{ | ||
ContractAddress = uint160.One, IsPrivateData = false, Key = new byte[] {0xBB}, | ||
Value = new byte[] {0xCC} | ||
} | ||
} | ||
} | ||
}; | ||
|
||
this.proposalResponses = new List<SignedProposalResponse> | ||
{ | ||
new SignedProposalResponse | ||
{ | ||
ProposalResponse = response, | ||
Endorsement = new Endorsement.Endorsement(this.key.Sign(response.GetHash()).ToDER(), this.key.PubKey.ToBytes()) | ||
} | ||
}; | ||
} | ||
|
||
[Fact] | ||
public void Proposer_Signs_Transaction() | ||
{ | ||
var endorsementSigner = new Mock<IEndorsementSigner>(); | ||
|
||
var builder = new EndorsedTransactionBuilder(endorsementSigner.Object); | ||
|
||
Transaction tx = builder.Build(this.proposalResponses); | ||
|
||
// We don't need to verify the tx here, the endorsement signer tests take care of that already. | ||
endorsementSigner.Verify(s => s.Sign(tx), Times.Once); | ||
} | ||
|
||
[Fact] | ||
public void First_Output_Uses_OpReadWrite() | ||
{ | ||
var endorsementSigner = new Mock<IEndorsementSigner>(); | ||
|
||
var builder = new EndorsedTransactionBuilder(endorsementSigner.Object); | ||
|
||
Transaction tx = builder.Build(this.proposalResponses); | ||
|
||
Assert.True(tx.Outputs[0].ScriptPubKey.IsReadWriteSet()); | ||
} | ||
|
||
[Fact] | ||
public void Second_Output_Is_Endorsement() | ||
{ | ||
var endorsementSigner = new Mock<IEndorsementSigner>(); | ||
|
||
var builder = new EndorsedTransactionBuilder(endorsementSigner.Object); | ||
|
||
Transaction tx = builder.Build(this.proposalResponses); | ||
|
||
Assert.True(tx.Outputs.Count > 1); | ||
|
||
var endorsementData = tx.Outputs[1].ScriptPubKey.ToBytes(); | ||
|
||
var endorsement = Endorsement.Endorsement.FromBytes(endorsementData); | ||
|
||
Assert.NotNull(endorsement); | ||
Assert.True(endorsement.Signature.SequenceEqual(this.proposalResponses[0].Endorsement.Signature)); | ||
Assert.True(endorsement.PubKey.SequenceEqual(this.proposalResponses[0].Endorsement.PubKey)); | ||
} | ||
|
||
[Fact] | ||
public void First_Output_Contains_Correct_Data() | ||
{ | ||
var endorsementSigner = new Mock<IEndorsementSigner>(); | ||
|
||
var builder = new EndorsedTransactionBuilder(endorsementSigner.Object); | ||
|
||
Transaction tx = builder.Build(this.proposalResponses); | ||
|
||
// Expect the data to include the generated RWS, and endorsements | ||
// First op should be OP_READWRITE, second op should be raw data | ||
var rwsData = tx.Outputs[0].ScriptPubKey.ToBytes().Skip(1).ToArray(); | ||
|
||
var rws = ReadWriteSet.FromJsonEncodedBytes(rwsData); | ||
|
||
Assert.NotNull(rws); | ||
Assert.True(rwsData.SequenceEqual(this.proposalResponses[0].ProposalResponse.ReadWriteSet.ToJsonEncodedBytes())); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/Stratis.Feature.PoA.Tokenless/Endorsement/EndorsedTransactionBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using NBitcoin; | ||
using Stratis.Feature.PoA.Tokenless.Consensus; | ||
using Stratis.SmartContracts.Core.ReadWrite; | ||
|
||
namespace Stratis.Feature.PoA.Tokenless.Endorsement | ||
{ | ||
public interface IEndorsedTransactionBuilder | ||
{ | ||
Transaction Build(IReadOnlyList<SignedProposalResponse> proposalResponses); | ||
} | ||
|
||
/// <summary> | ||
/// Builds a transaction that has satisfied an endorsement policy. | ||
/// </summary> | ||
public class EndorsedTransactionBuilder : IEndorsedTransactionBuilder | ||
{ | ||
private readonly IEndorsementSigner endorsementSigner; | ||
|
||
public EndorsedTransactionBuilder(IEndorsementSigner endorsementSigner) | ||
{ | ||
this.endorsementSigner = endorsementSigner; | ||
} | ||
|
||
public Transaction Build(IReadOnlyList<SignedProposalResponse> proposalResponses) | ||
{ | ||
if (!ValidateProposalResponses(proposalResponses)) | ||
return null; | ||
|
||
var transaction = new Transaction(); | ||
|
||
// Endorsement signer uses the same logic to sign the first input with the transaction signing key. | ||
this.endorsementSigner.Sign(transaction); | ||
|
||
// TODO at the moment this is the full RWS. We should check that only the public RWS is signed and returned by the endorser. | ||
AddReadWriteSet(transaction, proposalResponses); | ||
AddEndorsements(transaction, proposalResponses.Select(p => p.Endorsement)); | ||
|
||
return transaction; | ||
} | ||
|
||
private static void AddEndorsements(Transaction transaction, IEnumerable<Endorsement> endorsements) | ||
{ | ||
foreach(Endorsement endorsement in endorsements) | ||
{ | ||
transaction.Outputs.Add(new TxOut(Money.Zero, new Script(endorsement.ToJson()))); | ||
} | ||
} | ||
|
||
private static void AddReadWriteSet(Transaction transaction, IEnumerable<SignedProposalResponse> proposalResponses) | ||
{ | ||
// We can pick any RWS here as they should all be the same | ||
ReadWriteSet rws = GetReadWriteSet(proposalResponses); | ||
|
||
Script rwsScriptPubKey = TxReadWriteDataTemplate.Instance.GenerateScriptPubKey(rws.ToJsonEncodedBytes()); | ||
|
||
transaction.Outputs.Add(new TxOut(Money.Zero, rwsScriptPubKey)); | ||
} | ||
|
||
private static bool ValidateProposalResponses(IReadOnlyList<SignedProposalResponse> proposalResponses) | ||
{ | ||
// Nothing to compare against. | ||
if (proposalResponses.Count < 2) return true; | ||
|
||
var serializedProposalResponses = proposalResponses | ||
.Select(r => r.ProposalResponse.ToBytes()) | ||
.ToList(); | ||
|
||
// All elements should be the same. | ||
return serializedProposalResponses.All(b => b.SequenceEqual(serializedProposalResponses[0])); | ||
} | ||
|
||
private static ReadWriteSet GetReadWriteSet(IEnumerable<SignedProposalResponse> proposalResponses) | ||
{ | ||
return proposalResponses.First().ProposalResponse.ReadWriteSet; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.