diff --git a/neo3-gui/neo3-gui/Helpers.cs b/neo3-gui/neo3-gui/Helpers.cs index 020766bd..c6a7e7eb 100644 --- a/neo3-gui/neo3-gui/Helpers.cs +++ b/neo3-gui/neo3-gui/Helpers.cs @@ -112,6 +112,32 @@ public static string SafeSerialize(this ContractParametersContext signContext) return signContext.ToJson().SerializeJson(); } + /// + /// create tx by script and signer + /// + /// + /// + /// + /// + public static Transaction InitTransaction(this Wallet wallet, byte[] script, params UInt160[] signers) + { + var cosigners = signers.Select(account => new Cosigner { Account = account }).ToArray(); + return InitTransaction(wallet, script, cosigners); + } + + /// + /// create tx by script and signer + /// + /// + /// + /// + /// + public static Transaction InitTransaction(this Wallet wallet, byte[] script, params Cosigner[] signers) + { + var tx = wallet.MakeTransaction(script, null, null, signers); + return tx; + } + /// /// append sign to signContext @@ -695,7 +721,7 @@ public static byte[] TryGetPrivateKey(this string privateKey) } try { - return Wallet.GetPrivateKeyFromWIF(privateKey); + return Wallet.GetPrivateKeyFromWIF(privateKey); } catch (FormatException) { diff --git a/neo3-gui/neo3-gui/Models/Contracts/ValidatorModel.cs b/neo3-gui/neo3-gui/Models/Contracts/ValidatorModel.cs new file mode 100644 index 00000000..e5b5dd61 --- /dev/null +++ b/neo3-gui/neo3-gui/Models/Contracts/ValidatorModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Models.Contracts +{ + public class ValidatorModel + { + public string Publickey { get; set; } + + public string Votes { get; set; } + + /// + /// In Use + /// + public bool Active { get; set; } + } +} diff --git a/neo3-gui/neo3-gui/Models/Contracts/VoteResultModel.cs b/neo3-gui/neo3-gui/Models/Contracts/VoteResultModel.cs new file mode 100644 index 00000000..133830c2 --- /dev/null +++ b/neo3-gui/neo3-gui/Models/Contracts/VoteResultModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Models.Contracts +{ + public class VoteResultModel + { + public UInt256 TxId { get; set; } + } +} diff --git a/neo3-gui/neo3-gui/Models/ErrorCode.cs b/neo3-gui/neo3-gui/Models/ErrorCode.cs index c84289a8..7b2d05e0 100644 --- a/neo3-gui/neo3-gui/Models/ErrorCode.cs +++ b/neo3-gui/neo3-gui/Models/ErrorCode.cs @@ -62,7 +62,9 @@ public enum ErrorCode ExecuteContractFail = 20027, [Description("Unknown contract. ")] UnknownContract = 20028, - [Description("Contract already onchain. ")] + [Description("Contract already onchain.")] ContractAlreadyExist = 20029, + [Description("Validator already exits.")] + ValidatorAlreadyExist = 20030, } } \ No newline at end of file diff --git a/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs b/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs index c2337c0d..4b443c7b 100644 --- a/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs +++ b/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.IO.Json; using Neo.Ledger; @@ -16,6 +18,7 @@ using Neo.SmartContract.Native; using Neo.VM; using Neo.Wallets; +using Neo.Wallets.SQLite; namespace Neo.Services.ApiServices { @@ -35,7 +38,16 @@ public async Task GetContract(UInt160 contractHash) return new ContractModel(contract); } - + public async Task GetManifestFile(UInt160 contractHash) + { + using var snapshot = Blockchain.Singleton.GetSnapshot(); + var contract = snapshot.Contracts.TryGet(contractHash); + if (contract == null) + { + return Error(ErrorCode.UnknownContract); + } + return contract.Manifest.ToJson(); + } public async Task DeployContract(string nefPath, string manifestPath = null, bool sendTx = false) @@ -57,7 +69,7 @@ public async Task DeployContract(string nefPath, string manifestPath = n using var snapshot = Blockchain.Singleton.GetSnapshot(); - var oldContract= snapshot.Contracts.TryGet(nefFile.ScriptHash); + var oldContract = snapshot.Contracts.TryGet(nefFile.ScriptHash); if (oldContract != null) { return Error(ErrorCode.ContractAlreadyExist); @@ -137,21 +149,13 @@ public async Task InvokeContract(InvokeContractParameterModel para) signers.AddRange(para.Cosigners.Select(s => new Cosigner() { Account = s.Account, Scopes = s.Scopes })); } - Transaction tx = new Transaction - { - Sender = UInt160.Zero, - Attributes = new TransactionAttribute[0], - Witnesses = new Witness[0], - Cosigners = signers.ToArray(), - }; - using (ScriptBuilder scriptBuilder = new ScriptBuilder()) - { - scriptBuilder.EmitAppCall(para.ContractHash, para.Method, contractParameters); - tx.Script = scriptBuilder.ToArray(); - } + Transaction tx = null; + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(para.ContractHash, para.Method, contractParameters); + try { - tx = CurrentWallet.MakeTransaction(tx.Script, null, tx.Attributes, tx.Cosigners); + tx = CurrentWallet.InitTransaction(sb.ToArray(), signers.ToArray()); } catch (InvalidOperationException) { @@ -189,6 +193,179 @@ public async Task InvokeContract(InvokeContractParameterModel para) return result; } + + #region Vote + + + /// + /// Get all working or candidate validators + /// + /// + public async Task GetValidators() + { + using var snapshot = Blockchain.Singleton.GetSnapshot(); + var validators = NativeContract.NEO.GetValidators(snapshot); + var registerValidators = NativeContract.NEO.GetRegisteredValidators(snapshot); + return registerValidators.OrderByDescending(v => v.Votes).Select(p => new ValidatorModel + { + Publickey = p.PublicKey.ToString(), + Votes = p.Votes.ToString(), + Active = validators.Contains(p.PublicKey) + }).ToArray(); + } + + + + + /// + /// apply for new validator + /// + /// + /// + public async Task ApplyForValidator(string pubkey) + { + if (CurrentWallet == null) + { + return Error(ErrorCode.WalletNotOpen); + } + if (pubkey.IsNull()) + { + return Error(ErrorCode.ParameterIsNull); + } + ECPoint publicKey = null; + try + { + publicKey = ECPoint.Parse(pubkey, ECCurve.Secp256r1); + } + catch (Exception e) + { + return Error(ErrorCode.InvalidPara); + } + using var snapshot = Blockchain.Singleton.GetSnapshot(); + var validators = NativeContract.NEO.GetRegisteredValidators(snapshot); + if (validators.Any(v => v.PublicKey.ToString() == pubkey)) + { + return Error(ErrorCode.ValidatorAlreadyExist); + } + VerificationContract contract = new VerificationContract + { + Script = SmartContract.Contract.CreateSignatureRedeemScript(publicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + + var account = contract.ScriptHash; + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(NativeContract.NEO.Hash, "registerValidator", publicKey); + + Transaction tx = null; + try + { + tx = CurrentWallet.InitTransaction(sb.ToArray(), account); + } + catch (InvalidOperationException) + { + return Error(ErrorCode.EngineFault); + } + catch (Exception ex) + { + if (ex.Message.Contains("Insufficient GAS")) + { + return Error(ErrorCode.GasNotEnough); + } + throw; + } + + var (signSuccess, context) = CurrentWallet.TrySignTx(tx); + if (!signSuccess) + { + return Error(ErrorCode.SignFail, context.SafeSerialize()); + } + var result = new VoteResultModel(); + await tx.Broadcast(); + result.TxId = tx.Hash; + return result; + } + + + + + /// + /// vote for consensus node + /// + /// + public async Task VoteCN(UInt160 account, string[] pubkeys) + { + if (CurrentWallet == null) + { + return Error(ErrorCode.WalletNotOpen); + } + if (account == null || pubkeys.IsEmpty()) + { + return Error(ErrorCode.ParameterIsNull); + } + ECPoint[] publicKeys = null; + try + { + publicKeys = pubkeys.Select(p => ECPoint.Parse(p, ECCurve.Secp256r1)).ToArray(); + } + catch (Exception e) + { + return Error(ErrorCode.InvalidPara); + } + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitAppCall(NativeContract.NEO.Hash, "vote", new ContractParameter + { + Type = ContractParameterType.Hash160, + Value = account + }, new ContractParameter + { + Type = ContractParameterType.Array, + Value = publicKeys.Select(p => new ContractParameter + { + Type = ContractParameterType.PublicKey, + Value = p + }).ToArray() + }); + + Transaction tx = null; + try + { + tx = CurrentWallet.InitTransaction(sb.ToArray(), account); + } + catch (InvalidOperationException) + { + return Error(ErrorCode.EngineFault); + } + catch (Exception ex) + { + if (ex.Message.Contains("Insufficient GAS")) + { + return Error(ErrorCode.GasNotEnough); + } + throw; + } + + var (signSuccess, context) = CurrentWallet.TrySignTx(tx); + if (!signSuccess) + { + return Error(ErrorCode.SignFail, context.SafeSerialize()); + } + var result = new VoteResultModel(); + await tx.Broadcast(); + result.TxId = tx.Hash; + return result; + } + + + #endregion + + + + + + #region Private + + /// /// try to read nef file /// @@ -265,5 +442,8 @@ private async Task CheckBadOpcode(byte[] script) context.InstructionPointer += ci.Size; } } + + + #endregion } } diff --git a/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs b/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs index 11aa3f8b..252f876b 100644 --- a/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs +++ b/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs @@ -46,7 +46,7 @@ public async Task GetTransaction(UInt256 txId) model.Transfers = trans.Select(tx => tx.ToTransferModel()).ToList(); var executeResult = db.GetExecuteLog(txId); - if (executeResult.Notifications.NotEmpty()) + if (executeResult?.Notifications.NotEmpty() == true) { model.Notifies.AddRange( executeResult.Notifications.Select(n => new NotifyModel() @@ -103,7 +103,7 @@ public async Task QueryTransactions(int pageIndex = 1, int limit = 100, } - + /// /// query all nep transactions(on chain) diff --git a/neo3-gui/neo3-gui/Services/ApiServices/WalletApiService.cs b/neo3-gui/neo3-gui/Services/ApiServices/WalletApiService.cs index 274f95a7..e5a13374 100644 --- a/neo3-gui/neo3-gui/Services/ApiServices/WalletApiService.cs +++ b/neo3-gui/neo3-gui/Services/ApiServices/WalletApiService.cs @@ -268,6 +268,25 @@ public async Task ListPublicKey(int count = 100) }); } + + /// + /// list current wallet candidate address(only single sign address) + /// + /// + public async Task ListCandidatePublicKey(int count = 100) + { + if (CurrentWallet == null) + { + return Error(ErrorCode.WalletNotOpen); + } + var accounts = CurrentWallet.GetAccounts().Where(a => !a.WatchOnly && a.Contract.Script.IsSignatureContract()).Take(count).ToList(); + return accounts.Select(a => new PublicKeyModel + { + Address = a.Address, + PublicKey = a.GetKey().PublicKey.EncodePoint(true), + }); + } + /// /// import watch only addresses ///