From c11c862e91e47d48169676224e5643520689654b Mon Sep 17 00:00:00 2001 From: Thais Bastos Date: Thu, 20 Feb 2020 16:00:50 -0300 Subject: [PATCH] Included show commands --- neo-cli/CLI/MainService.cs | 452 +++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) diff --git a/neo-cli/CLI/MainService.cs b/neo-cli/CLI/MainService.cs index 01710cc1b..1bd860cc8 100644 --- a/neo-cli/CLI/MainService.cs +++ b/neo-cli/CLI/MainService.cs @@ -13,6 +13,7 @@ using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; +using Neo.SmartContract.Native.Tokens; using Neo.VM; using Neo.Wallets; using Neo.Wallets.NEP6; @@ -39,6 +40,15 @@ public class MainService : ConsoleServiceBase public event EventHandler WalletChanged; private Wallet currentWallet; + + public static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); + + private Dictionary knownSmartContracts = new Dictionary() { + { "neo", "0x43cf98eddbe047e198a3e5d57006311442a0ca15" }, + { "gas", "0xa1760976db5fcdfab2a9930e8f6ce875b2d18225" }, + { "policy", "0x9c5699b260bd468e2160dd5d45dfd2686bba8b77" }, + }; + public Wallet CurrentWallet { get @@ -211,11 +221,438 @@ protected override bool OnCommand(string[] args) return OnInstallCommand(args); case "uninstall": return OnUnInstallCommand(args); + case "block": + return OnShowBlock(args); + case "transaction": + return OnShowTransaction(args); + case "contract": + return OnShowContract(args); + case "last-transactions": + return OnShowLastTransactions(args); default: return base.OnCommand(args); } } + /// + /// Prints the last transactions in the blockchain + /// + /// + /// + private bool OnShowLastTransactions(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine("Insuficient arguments"); + } + else + { + int desiredCount = int.Parse(args[2]); + if (desiredCount < 1) + { + Console.WriteLine("Minimum 1 transaction"); + return true; + } + if (desiredCount > 100) + { + Console.WriteLine("Maximum 100 transactions"); + return true; + } + using (var snapshot = Blockchain.Singleton.GetSnapshot()) + { + var blockHash = snapshot.CurrentBlockHash; + var countedTransactions = 0; + var countedBlocks = 0; + var maxBlocks = 10000; + Block block = snapshot.GetBlock(blockHash); + do + { + foreach (var tx in block.Transactions) + { + Console.WriteLine(ToCLIString(tx, block.Timestamp)); + countedTransactions++; + if (countedTransactions == desiredCount) + return true; + } + + block = snapshot.GetBlock(block.PrevHash); + countedBlocks++; + if (countedBlocks == maxBlocks) + { + var shouldContinue = ReadUserInput("Max searched blocks reached (10000), continue? ").IsYes(); + if (shouldContinue) + { + countedBlocks = 0; + } + else + { + break; + } + } + } while (block != null && desiredCount > countedTransactions); + } + } + + return true; + } + + /// + /// Prints the SmartContract ABI + /// + /// + /// + private bool OnShowContract(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine("Missing contract hash"); + } + else + { + string contractHash = args[2]; + if (knownSmartContracts.ContainsKey(contractHash)) + { + contractHash = knownSmartContracts[contractHash]; + } + + using (var snapshot = Blockchain.Singleton.GetSnapshot()) + { + var contract160 = UInt160.Parse(contractHash); + var smartContract = snapshot.Contracts.TryGet(contract160); + if (smartContract != null) + { + Console.WriteLine(ToCLIString(smartContract)); + } + } + } + + return true; + } + + + /// + /// Find and print a transaction using its hash + /// + /// + /// + private bool OnShowTransaction(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine("Missing transaction hash"); + } + else + { + string transactionHash = args[2]; + using (var snapshot = Blockchain.Singleton.GetSnapshot()) + { + var tx256 = UInt256.Parse(transactionHash); + var tx = snapshot.GetTransaction(tx256); + if (tx != null) + { + Console.WriteLine(ToCLIString(tx)); + } + } + } + + return true; + } + + /// + /// Finds and print the block using it's scripthash or index + /// + /// + /// + private bool OnShowBlock(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine("Missing block index or hash"); + } + else + { + string blockId = args[2]; + if (blockId.Length == 66) + { + var blockHash = UInt256.Parse(blockId); + if (Blockchain.Singleton.ContainsBlock(blockHash)) + { + var block = Blockchain.Singleton.GetBlock(blockHash); + Console.WriteLine($"Block:\n{ToCLIString(block)}"); + } + else + { + Console.WriteLine("Block not found"); + } + } + else + { + var blockIndex = uint.Parse(blockId); + var header = Blockchain.Singleton.GetBlock(blockIndex); + if (header != null) + { + Console.WriteLine($"Block:\n{ToCLIString(header)}"); + } + } + } + + return true; + } + + public static string DisassembleScript(byte[] script) + { + var supportedMethods = InteropService.SupportedMethods(); + string output = ""; + var outputAppends = new List(); + for (int i = 0; i < script.Length; i++) + { + OpCode currentOpCode = (OpCode)script[i]; + + if (currentOpCode == OpCode.SYSCALL) + { + var interop = script.Skip(i + 1).Take(4).ToArray(); + var callNumber = BitConverter.ToUInt32(interop); + var bytes = BitConverter.GetBytes(InteropService.Contract.CallEx); + var methodName = supportedMethods.ElementAtOrDefault((int)callNumber); + outputAppends.Add($"\t{methodName}\n"); + i = i + 4; + } + else if (currentOpCode <= OpCode.OVER) + { + var byteArraySize = (int)currentOpCode; + var byteArray = script.Skip(i + 1).Take(byteArraySize).ToArray(); + var hexString = byteArray.ToHexString(); + if (byteArraySize == 20) + { + var scriptHash = new UInt160(byteArray); + hexString = scriptHash.ToString(); + } + + outputAppends.Add($"\t{hexString}\n"); + i = i + byteArraySize; + } + + outputAppends.Add($"\t{currentOpCode.ToString()}\n"); + } + + for (int i = outputAppends.Count - 1; i >= 0; i--) + { + output += outputAppends[i]; + } + + return output; + } + + public static string ToCLIString(Cosigner cosigner) + { + string output = $"\tAccount: {cosigner.Account}\n"; + + output += "\tScope:\t"; + if (cosigner.Scopes == WitnessScope.Global) + { + output += $"Global\t"; + } + if (cosigner.Scopes.HasFlag(WitnessScope.CalledByEntry)) + { + output += $"CalledByEntry\t"; + } + if (cosigner.Scopes.HasFlag(WitnessScope.CustomContracts)) + { + output += $"CustomContract\t"; + } + if (cosigner.Scopes.HasFlag(WitnessScope.CustomGroups)) + { + output += $"CustomGroup\t"; + } + + output += "\n"; + + if (cosigner.AllowedContracts != null && cosigner.AllowedContracts.Length > 0) + { + output += "Allowed contracts: \n"; + foreach (var allowedContract in cosigner.AllowedContracts) + { + output += $"\t{allowedContract.ToString()}\n"; + } + } + + if (cosigner.AllowedGroups != null && cosigner.AllowedGroups.Length > 0) + { + output += "Allowed groups: \n"; + foreach (var allowedGroup in cosigner.AllowedGroups) + { + output += $"\t{allowedGroup.ToString()}\n"; + } + } + + return output; + } + + public static string ToCLIString(Witness witness) + { + string output = ""; + output += $"Invocation: \n"; + output += DisassembleScript(witness.InvocationScript); + output += $"Verification: \n"; + output += DisassembleScript(witness.VerificationScript); + return output; + } + + + public static string ToCLIString(NotifyEventArgs notification) + { + string output = ""; + var vmArray = (Neo.VM.Types.Array)notification.State; + var notificationName = ""; + if (vmArray.Count > 0) + { + notificationName = vmArray[0].GetString(); + } + + var adapter = GetCliStringAdapter(notificationName); + output += adapter(vmArray); + + return output; + } + + + public static string ToCLIString(Block block) + { + string output = ""; + output += $"Hash: {block.Hash}\n"; + output += $"Index: {block.Index}\n"; + output += $"Size: {block.Size}\n"; + output += $"PreviousBlockHash: {block.PrevHash}\n"; + output += $"MerkleRoot: {block.MerkleRoot}\n"; + output += $"Time: {block.Timestamp}\n"; + output += $"NextConsensus: {block.NextConsensus}\n"; + output += $"Transactions:\n"; + foreach (Transaction t in block.Transactions) + { + output += $"{ToCLIString(t, block.Timestamp)}"; + output += $"\n"; + } + output += $"Witnesses:\n"; + output += $"\tInvocation: {block.Witness.InvocationScript.ToHexString()}\n"; + output += $"\tVerification: {block.Witness.VerificationScript.ToHexString()}\n"; + return output; + } + + public static string ToCLIString(Transaction t, ulong blockTimestamp = 0) + { + string output = ""; + output += $"Hash: {t.Hash}\n"; + if (blockTimestamp > 0) + { + var blockTime = UnixEpoch.AddMilliseconds(blockTimestamp); + blockTime = TimeZoneInfo.ConvertTimeFromUtc(blockTime, TimeZoneInfo.Local); + output += $"Timestamp: {blockTime.ToShortDateString()} {blockTime.ToLongTimeString()}\n"; + } + output += $"NetFee: {new BigDecimal(t.NetworkFee, NeoToken.GAS.Decimals)}\n"; + output += $"SysFee: {new BigDecimal(t.SystemFee, NeoToken.GAS.Decimals)}\n"; + output += $"Sender: {t.Sender.ToAddress()}\n"; + if (t.Cosigners != null && t.Cosigners.Length > 0) + { + output += $"Cosigners:\n"; + foreach (var cosigner in t.Cosigners) + { + output += ToCLIString(cosigner); + } + } + + + output += $"Script:\n"; + output += DisassembleScript(t.Script); + + output += "Witnesses: \n"; + foreach (var witness in t.Witnesses) + { + output += $"{ToCLIString(witness)}"; + } + + + return output; + } + + + public static string ToCLIString(ContractState c) + { + string output = ""; + output += $"Hash: {c.ScriptHash}\n"; + output += $"EntryPoint: \n"; + output += $"\tName: {c.Manifest.Abi.EntryPoint.Name}\n"; + output += $"\tParameters: \n"; + foreach (var parameter in c.Manifest.Abi.EntryPoint.Parameters) + { + output += $"\t\tName: {parameter.Name}\n"; + output += $"\t\tType: {parameter.Type}\n"; + } + + output += $"Methods: \n"; + foreach (var method in c.Manifest.Abi.Methods) + { + output += $"\tName: {method.Name}\n"; + output += $"\tReturn Type: {method.ReturnType}\n"; + if (method.Parameters.Length > 0) + { + output += $"\tParameters: \n"; + foreach (var parameter in method.Parameters) + { + output += $"\t\tName: {parameter.Name}\n"; + output += $"\t\tType: {parameter.Type}\n"; + } + } + + } + + output += $"Events: \n"; + foreach (var abiEvent in c.Manifest.Abi.Events) + { + output += $"\tName: {abiEvent.Name}\n"; + if (abiEvent.Parameters.Length > 0) + { + output += $"\tParameters: \n"; + foreach (var parameter in abiEvent.Parameters) + { + output += $"\t\tName: {parameter.Name}\n"; + output += $"\t\tType: {parameter.Type}\n"; + } + } + } + + return output; + } + + private static Dictionary> notificationAdapters = new Dictionary>() + { + {"transfer", TransferNotificationCLIStringAdapter}, + {"Transfer", TransferNotificationCLIStringAdapter} + }; + + private static string TransferNotificationCLIStringAdapter(VM.Types.Array notificationArray) + { + var fromBytes = notificationArray[1].GetSpan().ToArray(); + var toBytes = notificationArray[2].GetSpan().ToArray(); + var from = fromBytes.Length > 0 ? new UInt160(fromBytes) : UInt160.Zero; + var to = toBytes.Length > 0 ? new UInt160(toBytes) : UInt160.Zero; + var amount = notificationArray[3].GetBigInteger(); + + var output = $"transfer / {from.ToAddress()} / {to.ToAddress()} / {amount}"; + + return output; + } + + private static string DefaultCLIStringAdapter(VM.Types.Array notificationArray) + { + var output = notificationArray.ToString(); + return output; + } + + public static Func GetCliStringAdapter(string methodName) + { + return notificationAdapters.ContainsKey(methodName) ? notificationAdapters[methodName] : DefaultCLIStringAdapter; + } + private bool OnBroadcastCommand(string[] args) { if (!Enum.TryParse(args[1], true, out MessageCommand command)) @@ -738,6 +1175,11 @@ private bool OnHelpCommand(string[] args) Console.WriteLine("\tshow state"); Console.WriteLine("\tshow pool [verbose]"); Console.WriteLine("\trelay "); + Console.WriteLine("Show Commands:"); + Console.WriteLine("\tshow block "); + Console.WriteLine("\tshow transaction "); + Console.WriteLine("\tshow last-transactions <1 - 100>"); + Console.WriteLine("\tshow contract