Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultiSigInbox Plugin #487

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b4132c0
Draft
shargon Mar 2, 2021
96d0fe6
Merge signatures
shargon Mar 3, 2021
986751b
Merge branch 'master' into multisig-inbox
shargon Mar 3, 2021
ede1cb2
Optimize
shargon Mar 3, 2021
3f6fa8a
Merge remote-tracking branch 'origin/multisig-inbox' into multisig-inbox
shargon Mar 3, 2021
df07ace
Merge branch 'master' into multisig-inbox
shargon Mar 7, 2021
f7c745c
Some changes
shargon Mar 7, 2021
d00522f
Remove persisted tx
shargon Mar 8, 2021
8a97ffb
Merge branch 'master' into multisig-inbox
shargon Mar 8, 2021
cc84c33
dotnet format
shargon Mar 8, 2021
81548a8
Merge remote-tracking branch 'origin/multisig-inbox' into multisig-inbox
shargon Mar 8, 2021
bafb01b
Merge branch 'master' into multisig-inbox
superboyiii Mar 9, 2021
f006948
Move folder
erikzhang Mar 9, 2021
9d4cda1
Update Settings.cs
erikzhang Mar 9, 2021
023451f
Use NeoSystem.LoadStore()
erikzhang Mar 9, 2021
517c026
Add Settings.Network
erikzhang Mar 9, 2021
f390538
Use constant
shargon Mar 9, 2021
45f171b
Fix key
shargon Mar 10, 2021
b055cd7
Ensure completed
shargon Mar 10, 2021
fa14bff
Check completed
shargon Mar 10, 2021
e8845f8
Merge branch 'master' into multisig-inbox
superboyiii Mar 11, 2021
a83dcd3
Merge branch 'master' into multisig-inbox
superboyiii Mar 11, 2021
fc17e5b
Update src/MultiSigInbox/MultiSigInboxPlugin.cs
shargon Mar 11, 2021
5f0a55a
Prevent empty witness before json
shargon Mar 11, 2021
65a1501
Merge branch 'master' into multisig-inbox
superboyiii Mar 17, 2021
673dfba
Merge branch 'master' into multisig-inbox
superboyiii Mar 26, 2021
c0f5a5a
Merge branch 'master' into multisig-inbox
superboyiii Jun 17, 2021
3106b48
Merge branch 'master' into multisig-inbox
superboyiii Jun 25, 2021
ddb8626
Merge branch 'master' into multisig-inbox
vncoelho Sep 23, 2021
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
7 changes: 7 additions & 0 deletions neo-modules.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBFTPlugin", "src\DBFTPlugi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.RpcServer.Tests", "tests\Neo.Plugins.RpcServer.Tests\Neo.Plugins.RpcServer.Tests.csproj", "{0DDAF738-0FD3-40A7-A433-C514BCDDF542}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiSigInbox", "src\MultiSigInbox\MultiSigInbox.csproj", "{56452BCF-38ED-46AB-84B0-83EC234DE6D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -102,6 +104,10 @@ Global
{0DDAF738-0FD3-40A7-A433-C514BCDDF542}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DDAF738-0FD3-40A7-A433-C514BCDDF542}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DDAF738-0FD3-40A7-A433-C514BCDDF542}.Release|Any CPU.Build.0 = Release|Any CPU
{56452BCF-38ED-46AB-84B0-83EC234DE6D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56452BCF-38ED-46AB-84B0-83EC234DE6D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56452BCF-38ED-46AB-84B0-83EC234DE6D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56452BCF-38ED-46AB-84B0-83EC234DE6D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -122,6 +128,7 @@ Global
{149822EC-4E0C-425F-A032-4196B615BFEB} = {59D802AB-C552-422A-B9C3-64D329FBCDCC}
{90185D3E-4813-4BC1-98FE-26FD34311403} = {97E81C78-1637-481F-9485-DA1225E94C23}
{0DDAF738-0FD3-40A7-A433-C514BCDDF542} = {59D802AB-C552-422A-B9C3-64D329FBCDCC}
{56452BCF-38ED-46AB-84B0-83EC234DE6D4} = {97E81C78-1637-481F-9485-DA1225E94C23}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {61D3ADE6-BBFC-402D-AB42-1C71C9F9EDE3}
Expand Down
19 changes: 19 additions & 0 deletions src/MultiSigInbox/MultiSigInbox.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Neo.Plugins.MultiSigInbox</PackageId>
<RootNamespace>Neo.Plugins</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Neo.ConsoleService" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
<None Update="MultiSigInbox\config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions src/MultiSigInbox/MultiSigInbox/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"PluginConfiguration": {
"Network": 5195086,
"Path": "Data_MultiSigInbox_{0}",
"AutoStart": false
}
}
190 changes: 190 additions & 0 deletions src/MultiSigInbox/MultiSigInboxPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using Akka.Actor;
using Neo.ConsoleService;
using Neo.IO;
using Neo.IO.Json;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.Wallets;
using System;
using System.Linq;
using static System.IO.Path;

namespace Neo.Plugins.MultiSigInbox
{
public class MultiSigInboxPlugin : Plugin, IP2PPlugin
{
public const string StatePayloadCategory = "MultiSignatureInbox";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
public const string StatePayloadCategory = "MultiSignatureInbox";
public const string MutiSigPayloadCategory = "MultiSignatureInbox";

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

public override string Name => "MultiSigInbox";
public override string Description => "Enables MultiSigInbox for the node";

internal static readonly byte[] TxPrefix = new byte[] { 0x01 };

private Wallet _wallet;
private IWalletProvider walletProvider;
private IActorRef service;
private bool started = false;
private NeoSystem System;
private IStore store;

protected override void Configure()
{
Settings.Load(GetConfiguration());
}

public override void Dispose()
{
base.Dispose();
store?.Dispose();
}

protected override void OnSystemLoaded(NeoSystem system)
{
if (system.Settings.Magic != Settings.Default.Network) return;
System = system;
System.ServiceAdded += NeoSystem_ServiceAdded;
string path = string.Format(Settings.Default.Path, system.Settings.Magic.ToString("X8"));
store = system.LoadStore(GetFullPath(path));
}

private void NeoSystem_ServiceAdded(object sender, object service)
{
if (service is IWalletProvider)
{
walletProvider = service as IWalletProvider;
System.ServiceAdded -= NeoSystem_ServiceAdded;
if (Settings.Default.AutoStart)
{
walletProvider.WalletChanged += WalletProvider_WalletChanged;
}
}
}

private void WalletProvider_WalletChanged(object sender, Wallet wallet)
{
walletProvider.WalletChanged -= WalletProvider_WalletChanged;
Start(wallet);
}

[ConsoleCommand("start multisiginbox", Category = "MultiSigInbox", Description = "Start multiSigInbox service")]
private void OnStart()
{
Start(walletProvider.GetWallet());
}

public void Start(Wallet wallet)
{
if (started) return;
started = true;
_wallet = wallet;
service = System.ActorSystem.ActorOf(MultiSigInboxService.Props(System, store, wallet));
service.Tell(new MultiSigInboxService.Start());
}

[ConsoleCommand("inbox list", Category = "MultiSigInbox", Description = "List pending transactions")]
private void OnInboxList()
{
var first = true;
using var snapshot = System.GetSnapshot();

foreach (var (k, v) in store.Seek(TxPrefix, SeekDirection.Forward))
{
ContractParametersContext context = ContractParametersContext.FromJson(JObject.Parse(v), snapshot);
if (first)
{
first = false;
Console.WriteLine($"Transaction hashes");
}
Console.WriteLine($"{context.Verifiable.Hash}");
}
if (first)
{
Console.WriteLine($"No entries was found");
}
}

[ConsoleCommand("inbox read", Category = "MultiSigInbox", Description = "Read transaction")]
private void OnInboxRead(UInt256 txHash)
{
var json = store.TryGet(TxPrefix.Concat(txHash.ToArray()).ToArray());
if (json != null)
{
Console.WriteLine(Utility.StrictUTF8.GetString(json));
}
else
{
Console.WriteLine("Transaction was not found");
}
}

[ConsoleCommand("inbox send", Category = "MultiSigInbox", Description = "Send transaction")]
private void OnInboxSend(string transaction)
{
ContractParametersContext context = ContractParametersContext.FromJson(JObject.Parse(transaction), System.StoreView);

var tx = store.TryGet(TxPrefix.Concat(context.Verifiable.Hash.ToArray()).ToArray());
if (tx != null)
{
Console.WriteLine("Transaction already sent");
return;
}

store.Put(TxPrefix.Concat(context.Verifiable.ToArray()).ToArray(), Utility.StrictUTF8.GetBytes(transaction));

using var snapshot = System.GetSnapshot();
RelayContext(snapshot, context);
}

[ConsoleCommand("inbox sign", Category = "MultiSigInbox", Description = "Sign transaction")]
private void OnInboxSign(UInt256 txHash)
{
var tx = store.TryGet(TxPrefix.Concat(txHash.ToArray()).ToArray());
if (tx != null)
{
using var snapshot = System.GetSnapshot();
var context = ContractParametersContext.FromJson(JObject.Parse(tx), snapshot);

Console.WriteLine(tx.AsSerializable<Transaction>().ToJson(System.Settings).ToString(true));
RelayContext(snapshot, context);
}
else
{
Console.WriteLine("Transaction was not found");
}
}

private void RelayContext(SnapshotCache snapshot, ContractParametersContext context)
{
if (context.Completed)
{
Log($"Send tx: hash={context.Verifiable.Hash}");
System.Blockchain.Tell(context.Verifiable);
}
else
{
var tx = store.TryGet(TxPrefix.Concat(context.Verifiable.Hash.ToArray()).ToArray());

foreach (var wallet in _wallet.GetAccounts())
{
var msg = new ExtensiblePayload()
{
Category = "MultiSigInbox",
Data = tx,
ValidBlockStart = 0,
ValidBlockEnd = NativeContract.Ledger.CurrentIndex(snapshot) + 1_000,
Sender = wallet.ScriptHash
};

var sign = new ContractParametersContext(snapshot, msg);
if (_wallet.Sign(sign))
{
msg.Witness = sign.GetWitnesses()[0];
System.Blockchain.Tell(sign.Verifiable);
break;
shargon marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
}
146 changes: 146 additions & 0 deletions src/MultiSigInbox/MultiSigInboxService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using Akka.Actor;
using Neo.IO;
using Neo.IO.Json;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.Wallets;
using System.Linq;

namespace Neo.Plugins.MultiSigInbox
{
class MultiSigInboxService : UntypedActor
{
private readonly NeoSystem _system;
private readonly IStore store;
private readonly Wallet _wallet;
private bool started = false;
public class Start { }

public MultiSigInboxService(NeoSystem system, IStore store, Wallet wallet)
: this(system)
{
this.store = store;
this._wallet = wallet;
}

internal MultiSigInboxService(NeoSystem system)
{
_system = system;
Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted));
}

protected override void OnReceive(object message)
{
if (message is Start)
{
if (started) return;
OnStart();
}
else
{
if (!started) return;
switch (message)
{
case Blockchain.PersistCompleted p:
{
foreach (var tx in p.Block.Transactions)
{
store.Delete(MultiSigInboxPlugin.TxPrefix.Concat(tx.Hash.ToArray()).ToArray());
}
break;
}
case Blockchain.RelayResult rr:
{
if (rr.Result == VerifyResult.Succeed)
{
if (rr.Inventory is ExtensiblePayload payload && payload.Category == "MultiSigInbox")
OnExtensiblePayload(payload);
else if (rr.Inventory is Transaction tx)
OnVerifiedTransaction(tx);
}
break;
}
}
}
}

private void OnVerifiedTransaction(Transaction tx)
{
// Remove tx from inbox
store.Delete(MultiSigInboxPlugin.TxPrefix.Concat(tx.Hash.ToArray()).ToArray());
}

private void OnExtensiblePayload(ExtensiblePayload payload)
{
// Add to inbox

using var snapshot = _system.GetSnapshot();
var newContext = ContractParametersContext.FromJson(JObject.Parse(payload.Data), snapshot);
if (newContext.Completed) return;

var tx = store.TryGet(MultiSigInboxPlugin.TxPrefix.Concat(newContext.Verifiable.Hash.ToArray()).ToArray());
if (tx != null)
{
// merge context
var somethingAdded = false;
var oldContext = ContractParametersContext.FromJson(JObject.Parse(tx), snapshot);

foreach (var entry in oldContext.ScriptHashes)
{
var contract = _wallet.GetAccount(entry)?.Contract;
if (contract == null) continue;

var newSignatures = newContext.GetSignatures(entry);
if (newSignatures == null) continue;

var oldSignatures = oldContext.GetSignatures(entry)?.ToDictionary(u => u.Key, u => u.Value);
foreach (var sig in newSignatures)
{
if (oldSignatures?.ContainsKey(sig.Key) == true) continue;

if (oldContext.AddSignature(contract, sig.Key, sig.Value))
{
somethingAdded = true;
}
}
}

if (somethingAdded)
{
store.Put(MultiSigInboxPlugin.TxPrefix.Concat(oldContext.Verifiable.Hash.ToArray()).ToArray(), oldContext.ToJson().ToByteArray(false));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't need to do completed check here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}
else
{
// new context
store.Put(MultiSigInboxPlugin.TxPrefix.Concat(newContext.Verifiable.Hash.ToArray()).ToArray(), newContext.ToJson().ToByteArray(false));
}
}

private static void Log(string message, LogLevel level = LogLevel.Info)
{
Utility.Log(nameof(MultiSigInboxService), level, message);
}

private void OnStart()
{
Log("OnStart");
started = true;
}

protected override void PostStop()
{
Log("OnStop");
started = false;
Context.System.EventStream.Unsubscribe(Self);
base.PostStop();
}

public static Props Props(NeoSystem system, IStore store, Wallet wallet)
{
return Akka.Actor.Props.Create(() => new MultiSigInboxService(system, store, wallet));
}
}
}
Loading