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

Add command to download a contract #209

Merged
merged 40 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b085943
Add command to download a contract from a remote network into our loc…
ixje Mar 15, 2022
f389c64
Process feedback
ixje Mar 18, 2022
4fd6638
Add ExpressFindStatesAsync without key decoding and with paging support
ixje Mar 21, 2022
5e5ad53
Merge branch 'master' into download-contract
devhawk Mar 21, 2022
061f2b4
batch command
ixje Mar 22, 2022
6799ef1
Merge master
ixje Mar 22, 2022
2a3f306
Merge branch 'download-contract' of https://github.com/ixje/neo-expre…
ixje Mar 22, 2022
f4f2382
Resolve null warnings
ixje Mar 22, 2022
d241f86
Merge branch 'master' into download-contract
ixje Mar 23, 2022
0c20e5d
handle duplicate contracts
ixje Mar 28, 2022
daa9e85
process feedback
ixje Mar 30, 2022
0e992af
Update ExpressFindStatesAsync
Mar 30, 2022
5d590c1
pass storage pairs as IReadOnlyList instead of as an array
Mar 30, 2022
c83f7b0
remove EncodedFoundStates
Mar 30, 2022
ea28151
Merge branch 'master' into pr/ixje/209
Mar 31, 2022
86ffc7d
Merge branch 'master' into download-contract
ixje Apr 5, 2022
4e59429
fix LastUsedContractId and relevant logic
ixje Apr 5, 2022
c4a51a4
Merge branch 'download-contract' of https://github.com/ixje/neo-expre…
ixje Apr 5, 2022
512cd6c
- Limit contract download to single node consensus setup
ixje Apr 7, 2022
260f123
merge master
ixje Apr 7, 2022
eacec33
check consensus node count before getting express node
Apr 7, 2022
2871ce6
get contract state via state service
Apr 7, 2022
c07ce73
fix storage persisting if local contract does not yet exist
ixje Apr 8, 2022
29520df
Merge branch 'master' into download-contract
ixje Apr 8, 2022
ee6d309
Merge branch 'download-contract' of https://github.com/ixje/neo-expre…
ixje Apr 8, 2022
30a755d
Improve error message when state service plugin is not installed
ixje Apr 12, 2022
0b0a2f4
Improve error message when contract already exists
ixje Apr 12, 2022
98baa1f
Merge branch 'master' into download-contract
ixje Apr 12, 2022
3f26fa6
Generic collections > object model collections
Apr 14, 2022
b3709c9
variable rename + format
Apr 14, 2022
b9f8658
simplify GetStateHeightAsync logic
Apr 14, 2022
7183e09
minor fixed + format
Apr 14, 2022
47f1989
reworked PersistContract
Apr 14, 2022
21c3b77
fix compile break
Apr 14, 2022
be8a346
don't create empty array via new
Apr 14, 2022
b3b6eac
reworked PersistContract
Apr 15, 2022
dcacd28
throw better exeption on missing contract + disallow native contract …
Apr 15, 2022
8b658dc
Remove unused code + touch up
ixje Apr 18, 2022
257bc39
very minor whitespace + cleanup
Apr 19, 2022
5d9f89f
update changelog
Apr 19, 2022
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
76 changes: 76 additions & 0 deletions src/neoxp/Commands/ContractCommand.Download.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils;
using Neo;
using Neo.IO.Json;
using Neo.Network.RPC;

namespace NeoExpress.Commands
{
partial class ContractCommand
{
[Command(Name = "download", Description = "Download contract with storage from remote chain into local chain")]
internal class Download
{
readonly ExpressChainManagerFactory chainManagerFactory;

public Download(ExpressChainManagerFactory chainManagerFactory)
{
this.chainManagerFactory = chainManagerFactory;
}

[Argument(0, Description = "Contract invocation hash")]
[Required]
internal string Contract { get; init; } = string.Empty;

[Argument(1, Description = "URL of Neo JSON-RPC Node\nSpecify MainNet (default), TestNet or JSON-RPC URL")]
internal string RpcUri { get; } = string.Empty;

[Option(Description = "Path to neo-express data file")]
internal string Input { get; init; } = string.Empty;

internal async Task ExecuteAsync(TextWriter writer)
ixje marked this conversation as resolved.
Show resolved Hide resolved
{
var (chainManager, _) = chainManagerFactory.LoadChain(Input);
var expressNode = chainManager.GetExpressNode();

if (!UInt160.TryParse(Contract, out var contractHash))
throw new ArgumentException($"Invalid contract hash: \"{Contract}\"");

if (!TransactionExecutor.TryParseRpcUri(RpcUri, out var uri))
throw new ArgumentException($"Invalid RpcUri value \"{RpcUri}\"");
ixje marked this conversation as resolved.
Show resolved Hide resolved

using var rpcClient = new RpcClient(uri);
var stateAPI = new StateAPI(rpcClient);

var stateHeight = await stateAPI.GetStateHeightAsync();
ixje marked this conversation as resolved.
Show resolved Hide resolved
if (stateHeight.localRootIndex is null)
throw new Exception("Null \"localRootIndex\" in state height response");
var stateRoot = await stateAPI.GetStateRootAsync(stateHeight.localRootIndex.Value);
var states = await stateAPI.FindStatesAsync(stateRoot.RootHash, contractHash, new byte[0]);
ixje marked this conversation as resolved.
Show resolved Hide resolved
ixje marked this conversation as resolved.
Show resolved Hide resolved

var contractState = await rpcClient.GetContractStateAsync(Contract).ConfigureAwait(false);

await expressNode.PersistContractAsync(contractState, states.Results);
ixje marked this conversation as resolved.
Show resolved Hide resolved

}

internal async Task<int> OnExecuteAsync(CommandLineApplication app, IConsole console)
{
devhawk marked this conversation as resolved.
Show resolved Hide resolved
try
{
await ExecuteAsync(console.Out).ConfigureAwait(false);
return 0;
}
catch (Exception ex)
{
app.WriteException(ex);
return 1;
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/neoxp/Commands/ContractCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace NeoExpress.Commands
{
[Command("contract", Description = "Manage smart contracts")]
[Subcommand(typeof(Deploy), typeof(Get), typeof(Hash), typeof(Invoke), typeof(List), typeof(Run), typeof(Storage))]
[Subcommand(typeof(Deploy), typeof(Download), typeof(Get), typeof(Hash), typeof(Invoke), typeof(List), typeof(Run), typeof(Storage))]
partial class ContractCommand
{
internal int OnExecute(CommandLineApplication app, IConsole console)
Expand Down
3 changes: 3 additions & 0 deletions src/neoxp/IExpressNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Neo.Cryptography.ECC;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC.Models;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
Expand Down Expand Up @@ -39,5 +40,7 @@ enum CheckpointMode { Online, Offline }
Task<IReadOnlyList<(ulong requestId, OracleRequest request)>> ListOracleRequestsAsync();
Task<IReadOnlyList<ExpressStorage>> ListStoragesAsync(UInt160 scriptHash);
Task<IReadOnlyList<TokenContract>> ListTokenContractsAsync();

Task<bool> PersistContractAsync(ContractState state, (byte[] key, byte[] value)[] storagePairs);
}
}
57 changes: 57 additions & 0 deletions src/neoxp/Node/ExpressOracle.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Neo;
using Neo.BlockchainToolkit.Models;
using Neo.Cryptography.ECC;
Expand Down Expand Up @@ -138,5 +139,61 @@ public static void SignOracleResponseTransaction(ProtocolSettings settings, Expr

return tx;
}

public const byte Prefix_Contract = 8;
private const byte Prefix_NextAvailableId = 15;

private static int LastUsedContractId(DataCache snapshot)
{
StorageKey key = new KeyBuilder(NativeContract.ContractManagement.Id, Prefix_NextAvailableId);
StorageItem item = snapshot.TryGet(key);
return (int)(BigInteger)item;
}

private static void SetLastUsedContractId(DataCache snapshot, int newId)
{
StorageKey key = new KeyBuilder(NativeContract.ContractManagement.Id, Prefix_NextAvailableId);
StorageItem item = snapshot.GetAndChange(key);
item.Set(newId);
}

private static int GetNextAvailableId(DataCache snapshot)
{
StorageKey key = new KeyBuilder(NativeContract.ContractManagement.Id, Prefix_NextAvailableId);
StorageItem item = snapshot.GetAndChange(key);
int value = (int)(BigInteger)item;
item.Add(1);
return value;
}

public static bool PersistContract(SnapshotCache snapshot, ContractState state, (byte[] key, byte[] value)[] storagePairs)
ixje marked this conversation as resolved.
Show resolved Hide resolved
{
// Our local chain might already be using the contract id of the pulled contract, we need to check for this
// to avoid having contracts with duplicate id's. This is important because the contract id is part of the
// StorageContext used with Storage syscalls and else we'll potentially override storage keys or iterate
// over keys that shouldn't exist for one of the contracts.
if (state.Id <= LastUsedContractId(snapshot))
{
state.Id = GetNextAvailableId(snapshot);
}
else
{
// Update available id such that a regular contract deploy will use the right next id;
SetLastUsedContractId(snapshot, state.Id);
}

StorageKey key = new KeyBuilder(NativeContract.ContractManagement.Id, Prefix_Contract).Add(state.Hash);
ixje marked this conversation as resolved.
Show resolved Hide resolved
snapshot.Add(key, new StorageItem(state));

foreach (var pair in storagePairs)
{
snapshot.Add(
new StorageKey { Id = state.Id, Key = pair.key},
new StorageItem(pair.value)
);
}
snapshot.Commit();
return true;
ixje marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
24 changes: 24 additions & 0 deletions src/neoxp/Node/Modules/ExpressRpcMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
using System.Linq;
using System.Numerics;
using System.Threading;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
ixje marked this conversation as resolved.
Show resolved Hide resolved
using Neo;
using Neo.BlockchainToolkit.Persistence;
using Neo.IO;
using Neo.IO.Json;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
using Neo.Persistence;
using Neo.Plugins;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using NeoExpress.Models;
using ByteString = Neo.VM.Types.ByteString;
using RpcException = Neo.Plugins.RpcException;
using Utility = Neo.Utility;

namespace NeoExpress.Node
{
Expand Down Expand Up @@ -403,6 +409,24 @@ public JObject GetNep11Properties(JArray @params)
}
return json;
}

[RpcMethod]
public JObject? ExpressPersistContract(JObject @params)
{

var state = RpcClient.ContractStateFromJson(@params[0]["state"]);
var storagePairs = new (byte[] Key, byte[] value)[0];

foreach (var pair in (JArray)@params[0]["storage"])
{
storagePairs.Append((
Convert.FromBase64String(pair["key"].AsString()),
Convert.FromBase64String(pair["value"].AsString())
));
}

return ExpressOracle.PersistContract(neoSystem.GetSnapshot(), state, storagePairs);
}

static readonly IReadOnlySet<string> nep11PropertyNames = new HashSet<string>
{
Expand Down
12 changes: 11 additions & 1 deletion src/neoxp/Node/OfflineNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.IO.Json;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
Expand Down Expand Up @@ -103,7 +104,7 @@ Task<T> MakeAsync<T>(Func<T> func)
return Task.FromException<T>(ex);
}
}

IExpressNode.CheckpointMode CreateCheckpoint(string checkPointPath)
{
var multiSigAccount = nodeWallet.GetMultiSigAccounts().Single();
Expand Down Expand Up @@ -406,5 +407,14 @@ IReadOnlyList<ExpressStorage> ListStorages(UInt160 scriptHash)

public Task<IReadOnlyList<ExpressStorage>> ListStoragesAsync(UInt160 scriptHash)
=> MakeAsync(() => ListStorages(scriptHash));

bool PersistContract(ContractState state, (byte[] key, byte[] value)[] storagePairs)
{
return ExpressOracle.PersistContract(neoSystem.GetSnapshot(), state, storagePairs);
}

public Task<bool> PersistContractAsync(ContractState state, (byte[] key, byte[] value)[] storagePairs)
=> MakeAsync(() => PersistContract(state, storagePairs));

}
}
19 changes: 19 additions & 0 deletions src/neoxp/Node/OnlineNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,5 +328,24 @@ public async Task<IReadOnlyList<ExpressStorage>> ListStoragesAsync(UInt160 scrip

return Array.Empty<ExpressStorage>();
}

public async Task<bool> PersistContractAsync(ContractState state, (byte[] key, byte[] value)[] storagePairs)
{
JObject o = new JObject();
o["state"] = state.ToJson();

JArray storage = new JArray();
foreach (var pair in storagePairs)
{
JObject kv = new JObject();
kv["key"] = Convert.ToBase64String(pair.key);
kv["value"] = Convert.ToBase64String(pair.value);
storage.Add(kv);
}
o["storage"] = storage;

var response = await rpcClient.RpcSendAsync("expresspersistcontract", o).ConfigureAwait(false);
return response.AsBoolean();
}
}
}