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 1 commit
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
75 changes: 75 additions & 0 deletions src/neoxp/Commands/ContractCommand.Download.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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;
private RpcClient rpcClient = null!;

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

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

[Argument(1, Description = "Source network RPC address")]
internal string Source { get; init; } = string.Empty;
ixje marked this conversation as resolved.
Show resolved Hide resolved

[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();

this.rpcClient = new RpcClient(new Uri(Source));

if (!UInt160.TryParse(Contract, out _))
{
await writer.WriteLineAsync($"Invalid contract hash: {Contract} ").ConfigureAwait(false);
}
else
{
// 1. Get ContractState
var state = await this.rpcClient.GetContractStateAsync(Contract).ConfigureAwait(false);

// 2. Get Full storage of the contract
var storage_pairs = (JArray)await this.rpcClient.RpcSendAsync("getfullstorage", Contract).ConfigureAwait(false);
ixje marked this conversation as resolved.
Show resolved Hide resolved

await writer.WriteLineAsync(storage_pairs.ToString(true)).ConfigureAwait(false);
ixje marked this conversation as resolved.
Show resolved Hide resolved
await expressNode.PersistContractAsync(state, storage_pairs);
}
}

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(Get), typeof(Hash), typeof(Invoke), typeof(List), typeof(Run), typeof(Storage), typeof(Download))]
ixje marked this conversation as resolved.
Show resolved Hide resolved
partial class ContractCommand
{
internal int OnExecute(CommandLineApplication app, IConsole console)
Expand Down
4 changes: 4 additions & 0 deletions src/neoxp/IExpressNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Threading.Tasks;
using Neo;
using Neo.Cryptography.ECC;
using Neo.IO.Json;
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 +41,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, JArray storagePairs);
}
}
86 changes: 86 additions & 0 deletions src/neoxp/Node/Modules/ExpressRpcMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
using Neo.IO;
using Neo.IO.Json;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.Plugins;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
Expand Down Expand Up @@ -403,6 +405,90 @@ public JObject GetNep11Properties(JArray @params)
}
return json;
}

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

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

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

private 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;
}

[RpcMethod]
public JObject? ExpressPersistContract(JObject @params)
{
var state = @params[0]["state"];
ixje marked this conversation as resolved.
Show resolved Hide resolved
var storagePairs = (JArray) @params[0]["storage"];
var snapshot = neoSystem.GetSnapshot();

var nef = new NefFile
{
CheckSum = (uint)state["nef"]["checksum"].AsNumber(),
Compiler = state["nef"]["compiler"].AsString(),
Source = state["nef"]["source"].AsString(),
Tokens = new MethodToken[0], // TODO: support parsing MethodTokens or use fix of https://github.com/neo-project/neo/issues/2674
Script = Convert.FromBase64String(state["nef"]["script"].AsString())
};

ContractManifest m = ContractManifest.FromJson(state["manifest"]);

// 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.
int contractId = (int)state["id"].AsNumber();
ixje marked this conversation as resolved.
Show resolved Hide resolved
if (contractId <= LastUsedContractId(snapshot))
{
contractId = GetNextAvailableId(snapshot);
}
else
{
// Update available id such that a regular contract deploy will use the right next id;
SetLastUsedContractId(snapshot, contractId);
}

var c = new ContractState
ixje marked this conversation as resolved.
Show resolved Hide resolved
{
Id = contractId,
Hash = UInt160.Parse(state["hash"].AsString()),
UpdateCounter = (ushort)state["updatecounter"].AsNumber(),
Nef = nef,
Manifest = m
};

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

foreach (var pair in storagePairs)
{
snapshot.Add(
new StorageKey { Id = c.Id, Key = Convert.FromBase64String(pair["k"].AsString())},
new StorageItem(Convert.FromBase64String(pair["v"].AsString()))
);
}

snapshot.Commit();

return true;
}

static readonly IReadOnlySet<string> nep11PropertyNames = new HashSet<string>
{
Expand Down
26 changes: 26 additions & 0 deletions 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 @@ -406,5 +407,30 @@ IReadOnlyList<ExpressStorage> ListStorages(UInt160 scriptHash)

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

private const byte Prefix_Contract = 8;

bool PersistContract(ContractState state, JArray storagePairs)
ixje marked this conversation as resolved.
Show resolved Hide resolved
{
var snapshot = neoSystem.StoreView.CreateSnapshot();

StorageKey key = new KeyBuilder(NativeContract.ContractManagement.Id, Prefix_Contract).Add(state.Hash);
snapshot.Add(key, new StorageItem(state));

foreach (var pair in storagePairs)
{
snapshot.Add(
new StorageKey { Id = state.Id, Key = Convert.FromBase64String(pair["k"].AsString())},
new StorageItem(Convert.FromBase64String(pair["v"].AsString()))
);
}

snapshot.Commit();
return true;
}

public Task<bool> PersistContractAsync(ContractState state, JArray storagePairs)
=> MakeAsync(() => PersistContract(state, storagePairs));

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

return Array.Empty<ExpressStorage>();
}

public async Task<bool> PersistContractAsync(ContractState state, JArray storagePairs)
{
JObject o = new JObject();
o["state"] = state.ToJson();
o["storage"] = storagePairs;
var json = await rpcClient.RpcSendAsync("expresspersistcontract", o).ConfigureAwait(false);
return true;
}
}
}