public void ProfileData_AddMarkerName_AddsMarkerAndContainsName() { var data = new ProfileData(); var markerNames = new List <string>() { "Marker01", "Marker02", "Marker03", "Marker04" }; var markerList = new List <ProfileMarker>(); for (int i = 0; i < 10; ++i) { var marker = new ProfileMarker() { msMarkerTotal = 0.5f, depth = i }; int expectedIndex = i % markerNames.Count; data.AddMarkerName(markerNames[expectedIndex], marker); markerList.Add(marker); Assert.IsTrue(expectedIndex == marker.nameIndex, "Index mismatch at: " + i + " , " + marker.nameIndex);; } for (int i = 0; i < markerList.Count; ++i) { var curName = data.GetMarkerName(markerList[i]); Assert.IsTrue(markerNames.Contains(curName)); } }
internal ExecutionState SwitchContext(ExecutionContext context, uint instructionPointer) { if (context == null) { throw new VMException(this, "SwitchContext failed, context can't be null"); } using (var m = new ProfileMarker("SwitchContext")) { var tempContext = PreviousContext; PreviousContext = CurrentContext; SetCurrentContext(context); PushFrame(context, instructionPointer, DefaultRegisterCount); _activeAddresses.Push(context.Address); var result = context.Execute(this.CurrentFrame, this.Stack); PreviousContext = tempContext; var temp = _activeAddresses.Pop(); if (temp != context.Address) { throw new VMException(this, "VM implementation bug detected: address stack"); } return(result); } }
public BigInteger MintToken(string symbol, Address from, Address target, byte[] rom, byte[] ram) { var Runtime = this; Runtime.Expect(Runtime.TokenExists(symbol), "invalid token"); IToken token; using (var m = new ProfileMarker("Runtime.GetToken")) token = Runtime.GetToken(symbol); Runtime.Expect(!token.IsFungible(), "token must be non-fungible"); // TODO should not be necessary, verified by trigger //Runtime.Expect(IsWitness(target), "invalid witness"); Runtime.Expect(Runtime.IsRootChain(), "can only mint nft in root chain"); Runtime.Expect(rom.Length <= TokenContent.MaxROMSize, "ROM size exceeds maximum allowed"); Runtime.Expect(ram.Length <= TokenContent.MaxRAMSize, "RAM size exceeds maximum allowed"); BigInteger tokenID; using (var m = new ProfileMarker("Nexus.CreateNFT")) tokenID = Nexus.CreateNFT(this, symbol, Runtime.Chain.Name, target, rom, ram); Runtime.Expect(tokenID > 0, "invalid tokenID"); using (var m = new ProfileMarker("Nexus.MintToken")) Nexus.MintToken(this, token, from, target, Chain.Name, tokenID, rom, ram); return(tokenID); }
public void AllowGas(Address from, Address target, BigInteger price, BigInteger limit) { if (Runtime.IsReadOnlyMode()) { return; } if (_lastInflationDate == 0) { _lastInflationDate = Runtime.Time; } Runtime.Expect(from.IsUser, "must be a user address"); Runtime.Expect(Runtime.PreviousContext.Name == VirtualMachine.EntryContextName, "must be entry context"); Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); Runtime.Expect(target.IsSystem, "destination must be system address"); Runtime.Expect(price > 0, "price must be positive amount"); Runtime.Expect(limit > 0, "limit must be positive amount"); if (target.IsNull) { target = Runtime.Chain.Address; } var maxAmount = price * limit; using (var m = new ProfileMarker("_allowanceMap")) { var allowance = _allowanceMap.ContainsKey(from) ? _allowanceMap.Get <Address, BigInteger>(from) : 0; Runtime.Expect(allowance == 0, "unexpected pending allowance"); allowance += maxAmount; _allowanceMap.Set(from, allowance); _allowanceTargets.Set(from, target); } BigInteger balance; using (var m = new ProfileMarker("Runtime.GetBalance")) { balance = Runtime.GetBalance(DomainSettings.FuelTokenSymbol, from); } if (maxAmount > balance) { var diff = maxAmount - balance; var fuelToken = Runtime.GetToken(DomainSettings.FuelTokenSymbol); throw new BalanceException(fuelToken, from, diff); } Runtime.Expect(balance >= maxAmount, $"not enough {DomainSettings.FuelTokenSymbol} {balance} in address {from} {maxAmount}"); using (var m = new ProfileMarker("Runtime.TransferTokens")) Runtime.TransferTokens(DomainSettings.FuelTokenSymbol, from, this.Address, maxAmount); using (var m = new ProfileMarker("Runtime.Notify")) Runtime.Notify(EventKind.GasEscrow, from, new GasEventData(target, price, limit)); }
internal ExecutionState SwitchContext(ExecutionContext context, uint instructionPointer) { using (var m = new ProfileMarker("SwitchContext")) { this.CurrentContext = context; PushFrame(context, instructionPointer, DefaultRegisterCount); return(context.Execute(this.CurrentFrame, this.Stack)); } }
private bool ExecuteTransaction(int index, Transaction transaction, byte[] script, Address validator, Timestamp time, StorageChangeSetContext changeSet , Action <Hash, Event> onNotify, OracleReader oracle, ITask task, BigInteger minimumFee, out VMObject result, bool allowModify = true) { if (!transaction.HasSignatures) { throw new ChainException("Cannot execute unsigned transaction"); } result = null; uint offset = 0; RuntimeVM runtime; using (var m = new ProfileMarker("new RuntimeVM")) { runtime = new RuntimeVM(index, script, offset, this, validator, time, transaction, changeSet, oracle, task, false); } runtime.MinimumFee = minimumFee; ExecutionState state; using (var m = new ProfileMarker("runtime.Execute")) state = runtime.Execute(); if (state != ExecutionState.Halt) { return(false); } //var cost = runtime.UsedGas; using (var m = new ProfileMarker("runtime.Events")) { foreach (var evt in runtime.Events) { using (var m2 = new ProfileMarker(evt.ToString())) if (allowModify) { onNotify(transaction.Hash, evt); } } } if (runtime.Stack.Count > 0) { result = runtime.Stack.Pop(); } // merge transaction oracle data oracle.MergeTxData(); return(true); }
public override ExecutionState ExecuteInterop(string method) { BigInteger gasCost; // construtor if (method.EndsWith("()")) { gasCost = 10; } else { int dotPos = method.IndexOf('.'); Expect(dotPos > 0, "extcall is missing namespace"); var methodNamespace = method.Substring(0, dotPos); switch (methodNamespace) { case "Runtime": gasCost = 50; break; case "Nexus": gasCost = 1000; break; case "Organization": gasCost = 200; break; default: Expect(false, "invalid extcall namespace: " + methodNamespace); gasCost = 0; break; } } if (gasCost > 0) { ConsumeGas(gasCost); } if (handlers.ContainsKey(method)) { using (var m = new ProfileMarker(method)) return(handlers[method](this)); } return(ExecutionState.Fault); }
public void ProfileData_AddThreadName_AddsThreadAndContainsName() { var data = new ProfileData(); var threadNames = new List <string>() { "Thread01", "Thread02", "Thread03", "Thread04" }; var threadDict = new Dictionary <string, ProfileThread>(); for (int i = 0; i < 10; ++i) { int expectedIndex = i % threadNames.Count; string threadName = threadNames[expectedIndex]; ProfileThread thread; if (!threadDict.TryGetValue(threadName, out thread)) { thread = new ProfileThread(); threadDict.Add(threadName, thread); } var marker = new ProfileMarker() { msMarkerTotal = 0.5f, depth = i }; thread.Add(marker); data.AddThreadName(threadName, thread); Assert.IsTrue(expectedIndex == thread.threadIndex, "Index mismatch at: " + i + " , " + thread.threadIndex);; } foreach (var curThread in threadDict) { var curName = data.GetThreadName(curThread.Value); Assert.IsTrue(threadNames.Contains(curName)); } }
public void MintTokens(string symbol, Address from, Address target, BigInteger amount) { var Runtime = this; // TODO should not be necessary, verified by trigger //Runtime.Expect(IsWitness(from), "invalid witness"); Runtime.Expect(amount > 0, "amount must be positive and greater than zero"); Runtime.Expect(Runtime.TokenExists(symbol), "invalid token"); IToken token; using (var m = new ProfileMarker("Runtime.GetToken")) token = Runtime.GetToken(symbol); Runtime.Expect(token.Flags.HasFlag(TokenFlags.Fungible), "token must be fungible"); Runtime.Expect(!token.Flags.HasFlag(TokenFlags.Fiat), "token can't be fiat"); using (var m = new ProfileMarker("Nexus.MintTokens")) Nexus.MintTokens(this, token, from, target, Chain.Name, amount); }
private bool ExecuteTransaction(int index, Transaction transaction, Timestamp time, StorageChangeSetContext changeSet, Action <Hash, Event> onNotify, OracleReader oracle, BigInteger minimumFee, out byte[] result) { result = null; RuntimeVM runtime; using (var m = new ProfileMarker("new RuntimeVM")) runtime = new RuntimeVM(index, transaction.Script, this, time, transaction, changeSet, oracle, false); runtime.MinimumFee = minimumFee; runtime.ThrowOnFault = true; ExecutionState state; using (var m = new ProfileMarker("runtime.Execute")) state = runtime.Execute(); if (state != ExecutionState.Halt) { return(false); } var cost = runtime.UsedGas; using (var m = new ProfileMarker("runtime.Events")) { foreach (var evt in runtime.Events) { using (var m2 = new ProfileMarker(evt.ToString())) onNotify(transaction.Hash, evt); } } if (runtime.Stack.Count > 0) { var obj = runtime.Stack.Pop(); result = Serialization.Serialize(obj); } return(true); }
private ExecutionState InternalCall(NativeContract contract, ContractMethod method, ExecutionFrame frame, Stack <VMObject> stack) { var args = new object[method.parameters.Length]; for (int i = 0; i < args.Length; i++) { var arg = stack.Pop(); args[i] = arg.Data; } object result; using (var m = new ProfileMarker(method.name)) result = contract.CallInternalMethod((RuntimeVM)frame.VM, method.name, args); if (method.returnType != VMType.None) { var obj = VMObject.FromObject(result); stack.Push(obj); } return(ExecutionState.Running); }
public void AllowGas(Address from, Address target, BigInteger price, BigInteger limit) { if (Runtime.IsReadOnlyMode()) { return; } Runtime.Expect(from.IsUser, "must be a user address"); Runtime.Expect(Runtime.IsWitness(from), "invalid witness"); Runtime.Expect(target.IsSystem, "destination must be system address"); Runtime.Expect(price > 0, "price must be positive amount"); Runtime.Expect(limit > 0, "limit must be positive amount"); var maxAmount = price * limit; using (var m = new ProfileMarker("_allowanceMap")) { var allowance = _allowanceMap.ContainsKey(from) ? _allowanceMap.Get <Address, BigInteger>(from) : 0; Runtime.Expect(allowance == 0, "unexpected pending allowance"); allowance += maxAmount; _allowanceMap.Set(from, allowance); _allowanceTargets.Set(from, target); } BigInteger balance; using (var m = new ProfileMarker("Runtime.GetBalance")) balance = Runtime.GetBalance(DomainSettings.FuelTokenSymbol, from); Runtime.Expect(balance >= maxAmount, "not enough gas in address"); using (var m = new ProfileMarker("Runtime.TransferTokens")) Runtime.TransferTokens(DomainSettings.FuelTokenSymbol, from, this.Address, maxAmount); using (var m = new ProfileMarker("Runtime.Notify")) Runtime.Notify(EventKind.GasEscrow, from, new GasEventData(target, price, limit)); }
public bool IsWitness(Address address) { if (address.IsInterop) { return(false); } if (address == Address.Null) { return(false); } if (address == this.Chain.Address /*|| address == this.Address*/) { return(false); } if (address == this.EntryAddress) { return(true); } using (var m = new ProfileMarker("validatedWitnesses.Contains")) if (validatedWitnesses.Contains(address)) { return(true); } if (address.IsSystem) { var org = Nexus.GetOrganizationByAddress(RootStorage, address); if (org != null) { this.ConsumeGas(10000); var result = org.IsWitness(Transaction); if (result) { validatedWitnesses.Add(address); } return(result); } else { return(address == CurrentContext.Address); } } if (this.Transaction == null) { return(false); } bool accountResult; if (address.IsUser && Nexus.HasGenesis && OptimizedHasAddressScript(RootStorage, address)) { using (var m = new ProfileMarker("InvokeTriggerOnAccount")) accountResult = InvokeTriggerOnAccount(address, AccountTrigger.OnWitness, address); } else { using (var m = new ProfileMarker("Transaction.IsSignedBy")) accountResult = this.Transaction.IsSignedBy(address); } if (accountResult) { validatedWitnesses.Add(address); } return(accountResult); }
private void MintBlock(List <Transaction> transactions) { if (Mempool.ValidatorAddress == null) { Mempool.Logger.Error($"Validator keys not set for mempool"); return; } var lastBlockHash = Chain.GetLastBlockHash(); var lastBlock = Chain.GetBlockByHash(lastBlockHash); var isFirstBlock = lastBlock == null; var protocol = Nexus.GetProtocolVersion(Nexus.RootStorage); var minFee = Mempool.MinimumFee; Mempool.Logger.Message($"Minting new block with {transactions.Count} potential transactions"); while (transactions.Count > 0) { var block = new Block(isFirstBlock ? 1 : (lastBlock.Height + 1) , Chain.Address , Timestamp.Now , transactions.Select(x => x.Hash) , isFirstBlock ? Hash.Null : lastBlock.Hash , protocol , Mempool.ValidatorAddress , Mempool.Payload); try { using (var m = new ProfileMarker("Chain.ProcessBlock")) { Chain.ProcessBlock(block, transactions, minFee); } } catch (InvalidTransactionException e) { using (var m = new ProfileMarker("InvalidTransactionException")) { int index = -1; for (int i = 0; i < transactions.Count; i++) { if (transactions[i].Hash == e.Hash) { index = i; break; } } lock (_pending) { if (index >= 0) { transactions.RemoveAt(index); } _pending.Remove(e.Hash); Mempool.RegisterRejectionReason(e.Hash, e.Message); } Mempool.OnTransactionFailed?.Invoke(e.Hash); continue; } } try { StorageChangeSetContext changeSet; using (var m = new ProfileMarker("block.process")) { changeSet = Chain.ProcessBlock(block, transactions, minFee); } using (var m = new ProfileMarker("block.Sign")) { block.Sign(Mempool.ValidatorKeys); } using (var m = new ProfileMarker("Chain.AddBlock")) Chain.AddBlock(block, transactions, minFee, changeSet); } catch (Exception e) { Mempool.Logger.Error(e.ToString()); } lock (_pending) { _pending.Clear(); } using (var m = new ProfileMarker("Mempool.OnTransactionCommitted")) foreach (var tx in transactions) { Mempool.OnTransactionCommitted?.Invoke(tx.Hash); } return; } }
public StorageChangeSetContext ProcessBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee, out Transaction inflationTx, PhantasmaKeys signingKeys) { if (!block.Validator.IsUser) { throw new BlockGenerationException($"block validator must be user address"); } Block lastBlock; using (var m = new ProfileMarker("GetLastBlock")) { var lastBlockHash = GetLastBlockHash(); lastBlock = GetBlockByHash(lastBlockHash); } if (lastBlock != null) { if (lastBlock.Height != block.Height - 1) { throw new BlockGenerationException($"height of block should be {lastBlock.Height + 1}"); } if (block.PreviousHash != lastBlock.Hash) { throw new BlockGenerationException($"previous hash should be {lastBlock.PreviousHash}"); } if (block.Timestamp < lastBlock.Timestamp) { throw new BlockGenerationException($"timestamp of block {block.Timestamp} should be greater than {lastBlock.Timestamp}"); } } var inputHashes = new HashSet <Hash>(transactions.Select(x => x.Hash).Distinct()); var txBlockMap = new StorageMap(TxBlockHashMapTag, this.Storage); var diff = transactions.Count() - inputHashes.Count; if (diff > 0) { var temp = new HashSet <Hash>(); foreach (var tx in transactions) { if (temp.Contains(tx.Hash)) { throw new DuplicatedTransactionException(tx.Hash, $"transaction {tx.Hash} appears more than once in the block being minted"); } else if (txBlockMap.ContainsKey <Hash>(tx.Hash)) { var previousBlockHash = txBlockMap.Get <Hash, Hash>(tx.Hash); throw new DuplicatedTransactionException(tx.Hash, $"transaction {tx.Hash} already added to previous block {previousBlockHash}"); } else { temp.Add(tx.Hash); } } } foreach (var hash in block.TransactionHashes) { if (!inputHashes.Contains(hash)) { throw new BlockGenerationException($"missing in inputs transaction with hash {hash}"); } } var outputHashes = new HashSet <Hash>(block.TransactionHashes); foreach (var tx in transactions) { if (!outputHashes.Contains(tx.Hash)) { throw new BlockGenerationException($"missing in outputs transaction with hash {tx.Hash}"); } } foreach (var tx in transactions) { if (!tx.IsValid(this)) { #if DEBUG tx.IsValid(this); #endif throw new InvalidTransactionException(tx.Hash, $"invalid transaction with hash {tx.Hash}"); } } var oracle = Nexus.GetOracleReader(); block.CleanUp(); var changeSet = ProcessTransactions(block, transactions, oracle, minimumFee, out inflationTx, signingKeys); Address expectedValidator; using (var m = new ProfileMarker("GetValidator")) expectedValidator = Nexus.HasGenesis ? GetValidator(Nexus.RootStorage, block.Timestamp) : Nexus.GetGenesisAddress(Nexus.RootStorage); var migrationFound = false; var migratedAddress = Address.Null; foreach (var hash in outputHashes) { if (migrationFound) { break; } var events = block.GetEventsForTransaction(hash); foreach (var evt in events) { if (evt.Kind == EventKind.AddressMigration && evt.Contract == "validator") { var oldAddress = evt.GetContent <Address>(); if (oldAddress == expectedValidator) { migratedAddress = evt.Address; migrationFound = true; break; } } } } if (block.Validator != expectedValidator && !expectedValidator.IsNull) { if (migrationFound && migratedAddress == block.Validator) { expectedValidator = migratedAddress; } else { throw new BlockGenerationException($"unexpected validator {block.Validator}, expected {expectedValidator}"); } } if (oracle.Entries.Any()) { block.MergeOracle(oracle); oracle.Clear(); } return(changeSet); }
private TaskResult ProcessPendingTask(Block block, OracleReader oracle, BigInteger minimumFee, StorageChangeSetContext changeSet, bool allowModify, ChainTask task, out Transaction transaction) { transaction = null; BigInteger currentRun = GetTaskTimeFromBlock(task.Mode, block); var taskKey = GetTaskKey(task.ID, "task_run"); if (task.Mode != TaskFrequencyMode.Always) { bool isFirstRun = !changeSet.Has(taskKey); if (isFirstRun) { var taskBlockHash = GetBlockHashAtHeight(task.Height); var taskBlock = GetBlockByHash(taskBlockHash); BigInteger firstRun = GetTaskTimeFromBlock(task.Mode, taskBlock) + task.Delay; if (currentRun < firstRun) { return(TaskResult.Skipped); // skip execution for now } } else { BigInteger lastRun = isFirstRun ? changeSet.Get <BigInteger>(taskKey) : 0; var diff = currentRun - lastRun; if (diff < task.Frequency) { return(TaskResult.Skipped); // skip execution for now } } } else { currentRun = 0; } using (var m = new ProfileMarker("ExecuteTask")) { var taskScript = new ScriptBuilder() .AllowGas(task.Owner, Address.Null, minimumFee, task.GasLimit) .CallContract(task.ContextName, task.Method) .SpendGas(task.Owner) .EndScript(); transaction = new Transaction(this.Nexus.Name, this.Name, taskScript, block.Timestamp.Value + 1, "TASK"); VMObject vmResult; if (ExecuteTransaction(-1, transaction, transaction.Script, block.Validator, block.Timestamp, changeSet, block.Notify, oracle, task, minimumFee, out vmResult, allowModify)) { var resultBytes = Serialization.Serialize(vmResult); block.SetResultForHash(transaction.Hash, resultBytes); // update last_run value in storage if (currentRun > 0) { changeSet.Put <BigInteger>(taskKey, currentRun); } var shouldStop = vmResult.AsBool(); return(shouldStop ? TaskResult.Halted : TaskResult.Running); } else { transaction = null; return(TaskResult.Crashed); } } }
public override ExecutionState Execute(ExecutionFrame frame, Stack <VMObject> stack) { if (this.Contract.ABI == null) { throw new VMException(frame.VM, $"VM nativecall failed: ABI is missing for contract '{this.Contract.Name}'"); } if (stack.Count <= 0) { throw new VMException(frame.VM, $"VM nativecall failed: method name not present in the VM stack {frame.Context.Name}"); } var stackObj = stack.Pop(); var methodName = stackObj.AsString(); var runtime = (RuntimeVM)frame.VM; if (methodName.Equals(SmartContract.ConstructorName, StringComparison.OrdinalIgnoreCase) && runtime.HasGenesis) { BigInteger usedQuota; if (Nexus.IsNativeContract(Contract.Name)) { usedQuota = 1024; // does not matter what number, just than its greater than 0 } else { usedQuota = runtime.CallNativeContext(NativeContractKind.Storage, nameof(StorageContract.GetUsedDataQuota), this.Contract.Address).AsNumber(); } if (usedQuota > 0) { throw new VMException(frame.VM, $"VM nativecall failed: constructor can only be called once"); } } var method = this.Contract.ABI.FindMethod(methodName); if (method == null) { throw new VMException(frame.VM, $"VM nativecall failed: contract '{this.Contract.Name}' does not have method '{methodName}' in its ABI"); } if (stack.Count < method.parameters.Length) { throw new VMException(frame.VM, $"VM nativecall failed: calling method {methodName} with {stack.Count} arguments instead of {method.parameters.Length}"); } ExecutionState result; BigInteger gasCost = 10; result = runtime.ConsumeGas(gasCost); if (result != ExecutionState.Running) { return(result); } var native = Contract as NativeContract; if (native != null) { using (var m = new ProfileMarker("InternalCall")) { try { native.SetRuntime(runtime); native.LoadFromStorage(runtime.Storage); result = InternalCall(native, method, frame, stack); native.SaveChangesToStorage(); } catch (ArgumentException ex) { throw new VMException(frame.VM, $"VM nativecall failed: calling method {methodName} with arguments of wrong type, " + ex.ToString()); } } } else { var custom = Contract as CustomContract; if (custom != null) { if (method.offset < 0) { throw new VMException(frame.VM, $"VM context call failed: abi contains invalid offset for {method.name}"); } #if SUSHI_MODE var debugPath = @"C:\Code\Poltergeist\Builds\Windows\debug.asm"; var disasm = new VM.Disassembler(custom.Script); var asm = string.Join("\n", disasm.Instructions.Select(x => x.ToString())); System.IO.File.WriteAllText(debugPath, asm); #endif var context = new ScriptContext(Contract.Name, custom.Script, (uint)method.offset); result = context.Execute(frame, stack); } else { throw new VMException(frame.VM, $"VM context call failed: unknown contract instance class {Contract.GetType().Name}"); } } // we terminate here execution, since it will be restarted in next context if (result == ExecutionState.Running) { result = ExecutionState.Halt; } return(result); }
public StorageChangeSetContext ValidateBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee) { if (!block.Validator.IsUser) { throw new BlockGenerationException($"block validator must be user address"); } Block lastBlock; using (var m = new ProfileMarker("GetLastBlock")) { var lastBlockHash = GetLastBlockHash(); lastBlock = GetBlockByHash(lastBlockHash); } if (lastBlock != null) { if (lastBlock.Height != block.Height - 1) { throw new BlockGenerationException($"height of block should be {lastBlock.Height + 1}"); } if (block.PreviousHash != lastBlock.Hash) { throw new BlockGenerationException($"previous hash should be {lastBlock.PreviousHash}"); } if (block.Timestamp < lastBlock.Timestamp) { throw new BlockGenerationException($"timestamp of block should be greater than {lastBlock.Timestamp}"); } } var inputHashes = new HashSet <Hash>(transactions.Select(x => x.Hash)); foreach (var hash in block.TransactionHashes) { if (!inputHashes.Contains(hash)) { throw new BlockGenerationException($"missing in inputs transaction with hash {hash}"); } } var outputHashes = new HashSet <Hash>(block.TransactionHashes); foreach (var tx in transactions) { if (!outputHashes.Contains(tx.Hash)) { throw new BlockGenerationException($"missing in outputs transaction with hash {tx.Hash}"); } } // TODO avoid fetching this every time var expectedProtocol = Nexus.GetGovernanceValue(Nexus.RootStorage, Nexus.NexusProtocolVersionTag); if (block.Protocol != expectedProtocol) { throw new BlockGenerationException($"invalid protocol number {block.Protocol}, expected protocol {expectedProtocol}"); } foreach (var tx in transactions) { if (!tx.IsValid(this)) { throw new InvalidTransactionException(tx.Hash, $"invalid transaction with hash {tx.Hash}"); } } var changeSet = new StorageChangeSetContext(this.Storage); var oracle = Nexus.CreateOracleReader(); block.CleanUp(); Address expectedValidator; using (var m = new ProfileMarker("GetValidator")) expectedValidator = Nexus.HasGenesis ? GetValidator(Nexus.RootStorage, block.Timestamp) : Nexus.GetGenesisAddress(Nexus.RootStorage); if (block.Validator != expectedValidator) { throw new BlockGenerationException($"unexpected validator {block.Validator}, expected {expectedValidator}"); } int txIndex = 0; foreach (var tx in transactions) { byte[] result; try { using (var m = new ProfileMarker("ExecuteTransaction")) { if (ExecuteTransaction(txIndex, tx, block.Timestamp, changeSet, block.Notify, oracle, minimumFee, out result)) { if (result != null) { block.SetResultForHash(tx.Hash, result); } } else { throw new InvalidTransactionException(tx.Hash, "script execution failed"); } } } catch (Exception e) { if (e.InnerException != null) { e = e.InnerException; } if (tx == null) { throw new BlockGenerationException(e.Message); } throw new InvalidTransactionException(tx.Hash, e.Message); } txIndex++; } using (var m = new ProfileMarker("CloseBlock")) { CloseBlock(block, changeSet); } if (oracle.Entries.Any()) { block.MergeOracle(oracle); } return(changeSet); }
public void Step(ref ExecutionFrame frame, Stack <VMObject> stack) { try { opcode = (Opcode)Read8(); frame.VM.ValidateOpcode(opcode); switch (opcode) { case Opcode.NOP: { break; } // args: byte src_reg, byte dest_reg case Opcode.MOVE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); frame.Registers[dst] = frame.Registers[src]; frame.Registers[src] = new VMObject(); break; } // args: byte src_reg, byte dest_reg case Opcode.COPY: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); frame.Registers[dst].Copy(frame.Registers[src]); break; } // args: byte dst_reg, byte type, var length, var data_bytes case Opcode.LOAD: { var dst = Read8(); var type = (VMType)Read8(); var len = (int)ReadVar(0xFFFF); Expect(dst < frame.Registers.Length, "invalid dst register"); var bytes = ReadBytes(len); frame.Registers[dst].SetValue(bytes, type); break; } // args: byte src_reg, dst_reg, byte type case Opcode.CAST: { var src = Read8(); var dst = Read8(); var type = (VMType)Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src]; val = VMObject.CastTo(val, type); frame.Registers[dst] = val; break; } // args: byte src_reg case Opcode.PUSH: { var src = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); var val = frame.Registers[src]; var temp = new VMObject(); temp.Copy(val); stack.Push(temp); break; } // args: byte dest_reg case Opcode.POP: { var dst = Read8(); Expect(stack.Count > 0, "stack is empty"); Expect(dst < frame.Registers.Length, "invalid dst register"); frame.Registers[dst] = stack.Pop(); break; } // args: byte src_reg, byte dest_reg case Opcode.SWAP: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var temp = frame.Registers[src]; frame.Registers[src] = frame.Registers[dst]; frame.Registers[dst] = temp; break; } // args: ushort offset, byte regCount case Opcode.CALL: { var count = Read8(); var ofs = Read16(); Expect(ofs < this.Script.Length, "invalid jump offset"); Expect(count >= 1, "at least 1 register required"); Expect(count <= VirtualMachine.MaxRegisterCount, "invalid register allocs"); frame.VM.PushFrame(this, InstructionPointer, count); frame = frame.VM.CurrentFrame; InstructionPointer = ofs; break; } // args: byte srcReg case Opcode.EXTCALL: using (var m = new ProfileMarker("EXTCALL")) { var src = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); var method = frame.Registers[src].AsString(); var state = frame.VM.ExecuteInterop(method); if (state != ExecutionState.Running) { throw new VMException(frame.VM, "VM extcall failed: " + method); } break; } // args: ushort offset, byte src_reg // NOTE: JMP only has offset arg, not the rest case Opcode.JMP: case Opcode.JMPIF: case Opcode.JMPNOT: { bool shouldJump; if (opcode == Opcode.JMP) { shouldJump = true; } else { var src = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); shouldJump = frame.Registers[src].AsBool(); if (opcode == Opcode.JMPNOT) { shouldJump = !shouldJump; } } var newPos = (short)Read16(); Expect(newPos >= 0, "jump offset can't be negative value"); Expect(newPos < this.Script.Length, "trying to jump outside of script bounds"); if (shouldJump) { InstructionPointer = (uint)newPos; } break; } // args: var length, var bytes case Opcode.THROW: { var src = Read8(); Expect(src < frame.Registers.Length, "invalid exception register"); var exception = frame.Registers[src]; var exceptionMessage = exception.AsString(); throw new VMException(frame.VM, exceptionMessage); } // args: none case Opcode.RET: { if (frame.VM.frames.Count > 1) { var temp = frame.VM.PeekFrame(); if (temp.Context.Name == this.Name) { InstructionPointer = frame.VM.PopFrame(); frame = frame.VM.CurrentFrame; } else { SetState(ExecutionState.Halt); } } else { SetState(ExecutionState.Halt); } return; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.CAT: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length, "invalid srcA register"); Expect(srcB < frame.Registers.Length, "invalid srcB register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var A = frame.Registers[srcA]; var B = frame.Registers[srcB]; if (!A.IsEmpty) { if (B.IsEmpty) { frame.Registers[dst].Copy(A); } else { if (A.Type != B.Type) { throw new VMException(frame.VM, "Invalid cast during concat opcode"); } var bytesA = A.AsByteArray(); var bytesB = B.AsByteArray(); var result = new byte[bytesA.Length + bytesB.Length]; Array.Copy(bytesA, result, bytesA.Length); Array.Copy(bytesB, 0, result, bytesA.Length, bytesB.Length); VMType type = A.Type; frame.Registers[dst].SetValue(result, type); } } else { if (B.IsEmpty) { frame.Registers[dst] = new VMObject(); } else { frame.Registers[dst].Copy(B); } } break; } case Opcode.SUBSTR: { throw new NotImplementedException(); } // args: byte src_reg, byte dest_reg, var length case Opcode.LEFT: { var src = Read8(); var dst = Read8(); var len = (int)ReadVar(0xFFFF); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var src_array = frame.Registers[src].AsByteArray(); Expect(len <= src_array.Length, "invalid length"); var result = new byte[len]; Array.Copy(src_array, result, len); frame.Registers[dst].SetValue(result, VMType.Bytes); break; } // args: byte src_reg, byte dest_reg, byte length case Opcode.RIGHT: { var src = Read8(); var dst = Read8(); var len = (int)ReadVar(0xFFFF); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var src_array = frame.Registers[src].AsByteArray(); Expect(len <= src_array.Length, "invalid length register"); var ofs = src_array.Length - len; var result = new byte[len]; Array.Copy(src_array, ofs, result, 0, len); frame.Registers[dst].SetValue(result, VMType.Bytes); break; } // args: byte src_reg, byte dest_reg case Opcode.SIZE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); int size; var src_val = frame.Registers[src]; switch (src_val.Type) { case VMType.String: size = src_val.AsString().Length; break; case VMType.Timestamp: case VMType.Number: case VMType.Enum: case VMType.Bool: size = 1; break; case VMType.None: size = 0; break; default: var src_array = src_val.AsByteArray(); size = src_array.Length; break; } frame.Registers[dst].SetValue(size); break; } // args: byte src_reg, byte dest_reg case Opcode.COUNT: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src]; int count; switch (val.Type) { case VMType.Struct: { var children = val.GetChildren(); count = children.Count; break; } default: count = 1; break; } frame.Registers[dst].SetValue(count); break; } // args: byte src_reg, byte dest_reg case Opcode.NOT: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src].AsBool(); frame.Registers[dst].SetValue(!val); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.AND: case Opcode.OR: case Opcode.XOR: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length, "invalid srcA register"); Expect(srcB < frame.Registers.Length, "invalid srcB register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var valA = frame.Registers[srcA]; var valB = frame.Registers[srcB]; switch (valA.Type) { case VMType.Bool: { Expect(valB.Type == VMType.Bool, $"expected {valA.Type} for logical op"); var a = valA.AsBool(); var b = valB.AsBool(); bool result; switch (opcode) { case Opcode.AND: result = (a && b); break; case Opcode.OR: result = (a || b); break; case Opcode.XOR: result = (a ^ b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } case VMType.Enum: { Expect(valB.Type == VMType.Enum, $"expected {valA.Type} for flag op"); var numA = valA.AsNumber(); var numB = valB.AsNumber(); Expect(numA.GetBitLength() <= 32, "too many bits"); Expect(numB.GetBitLength() <= 32, "too many bits"); var a = (uint)numA; var b = (uint)numB; if (opcode != Opcode.AND) { SetState(ExecutionState.Fault); } bool result = (a & b) != 0; frame.Registers[dst].SetValue(result); break; } case VMType.Number: { Expect(valB.Type == VMType.Number, $"expected {valA.Type} for logical op"); var numA = valA.AsNumber(); var numB = valB.AsNumber(); Expect(numA.GetBitLength() <= 64, "too many bits"); Expect(numB.GetBitLength() <= 64, "too many bits"); var a = (long)numA; var b = (long)numB; BigInteger result; switch (opcode) { case Opcode.AND: result = (a & b); break; case Opcode.OR: result = (a | b); break; case Opcode.XOR: result = (a ^ b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } default: throw new VMException(frame.VM, "logical op unsupported for type " + valA.Type); } break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.EQUAL: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length, "invalid srcA register"); Expect(srcB < frame.Registers.Length, "invalid srcB register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var a = frame.Registers[srcA]; var b = frame.Registers[srcB]; var result = a.Equals(b); frame.Registers[dst].SetValue(result); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.LT: case Opcode.GT: case Opcode.LTE: case Opcode.GTE: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length, "invalid srcA register"); Expect(srcB < frame.Registers.Length, "invalid srcB register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var a = frame.Registers[srcA].AsNumber(); var b = frame.Registers[srcB].AsNumber(); bool result; switch (opcode) { case Opcode.LT: result = (a < b); break; case Opcode.GT: result = (a > b); break; case Opcode.LTE: result = (a <= b); break; case Opcode.GTE: result = (a >= b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } // args: byte reg case Opcode.INC: { var dst = Read8(); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[dst].AsNumber(); frame.Registers[dst].SetValue(val + 1); break; } // args: byte reg case Opcode.DEC: { var dst = Read8(); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[dst].AsNumber(); frame.Registers[dst].SetValue(val - 1); break; } // args: byte src_reg, byte dest_reg case Opcode.SIGN: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src].AsNumber(); if (val == 0) { frame.Registers[dst].SetValue(BigInteger.Zero); } else { frame.Registers[dst].SetValue(val < 0 ? -1 : 1); } break; } // args: byte src_reg, byte dest_reg case Opcode.NEGATE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src].AsNumber(); frame.Registers[dst].SetValue(-val); break; } // args: byte src_reg, byte dest_reg case Opcode.ABS: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var val = frame.Registers[src].AsNumber(); frame.Registers[dst].SetValue(val < 0 ? -val : val); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.ADD: case Opcode.SUB: case Opcode.MUL: case Opcode.DIV: case Opcode.MOD: case Opcode.SHR: case Opcode.SHL: case Opcode.MIN: case Opcode.MAX: case Opcode.POW: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length, "invalid srcA register"); Expect(srcB < frame.Registers.Length, "invalid srcB register"); Expect(dst < frame.Registers.Length, "invalid dst register"); if (opcode == Opcode.ADD && frame.Registers[srcA].Type == VMType.String) { Expect(frame.Registers[srcB].Type == VMType.String, "invalid string as right operand"); var a = frame.Registers[srcA].AsString(); var b = frame.Registers[srcB].AsString(); var result = a + b; frame.Registers[dst].SetValue(result); } else { var a = frame.Registers[srcA].AsNumber(); var b = frame.Registers[srcB].AsNumber(); BigInteger result; switch (opcode) { case Opcode.ADD: result = a + b; break; case Opcode.SUB: result = a - b; break; case Opcode.MUL: result = a * b; break; case Opcode.DIV: result = a / b; break; case Opcode.MOD: result = a % b; break; case Opcode.SHR: result = a >> (int)b; break; case Opcode.SHL: result = a << (int)b; break; case Opcode.MIN: result = a < b ? a : b; break; case Opcode.MAX: result = a > b ? a : b; break; case Opcode.POW: result = BigInteger.Pow(a, b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); } break; } // args: byte src_reg, byte dest_reg, byte key case Opcode.PUT: { var src = Read8(); var dst = Read8(); var keyReg = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); Expect(keyReg < frame.Registers.Length, "invalid key register"); var key = frame.Registers[keyReg]; Throw.If(key.Type == VMType.None, "invalid key type"); var value = frame.Registers[src]; frame.Registers[dst].SetKey(key, value); break; } // args: byte src_reg, byte dest_reg, byte key case Opcode.GET: { var src = Read8(); var dst = Read8(); var keyReg = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); Expect(keyReg < frame.Registers.Length, "invalid key register"); var key = frame.Registers[keyReg]; Throw.If(key.Type == VMType.None, "invalid key type"); var val = frame.Registers[src].GetKey(key); frame.Registers[dst] = val; break; } // args: byte dest_reg case Opcode.CLEAR: { var dst = Read8(); Expect(dst < frame.Registers.Length, "invalid dst register"); frame.Registers[dst] = new VMObject(); break; } // args: byte dest_reg, var key case Opcode.CTX: using (var m = new ProfileMarker("CTX")) { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var contextName = frame.Registers[src].AsString(); ExecutionContext context = frame.VM.FindContext(contextName); if (context == null) { throw new VMException(frame.VM, $"VM ctx instruction failed: could not find context with name '{contextName}'"); } frame.Registers[dst].SetValue(context); break; } // args: byte src_reg case Opcode.SWITCH: using (var m = new ProfileMarker("SWITCH")) { var src = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); var context = frame.Registers[src].AsInterop <ExecutionContext>(); _state = frame.VM.SwitchContext(context, InstructionPointer); if (_state == ExecutionState.Halt) { _state = ExecutionState.Running; frame.VM.PopFrame(); } else { throw new VMException(frame.VM, $"VM switch instruction failed: execution state did not halt"); } break; } // args: byte src_reg dst_reg case Opcode.UNPACK: using (var m = new ProfileMarker("SWITCH")) { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length, "invalid src register"); Expect(dst < frame.Registers.Length, "invalid dst register"); var bytes = frame.Registers[src].AsByteArray(); frame.Registers[dst] = VMObject.FromBytes(bytes); break; } case Opcode.DEBUG: { break; // put here a breakpoint for debugging } default: { throw new VMException(frame.VM, $"Unknown VM opcode: {(int)opcode}"); } } } catch (Exception ex) { ex = ex.ExpandInnerExceptions(); Trace.WriteLine(ex.ToString()); SetState(ExecutionState.Fault); if (!(ex is VMException)) { ex = new VMException(frame.VM, ex.Message); } throw ex; } }
public void AddBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee, StorageChangeSetContext changeSet) { if (!block.IsSigned) { throw new BlockGenerationException($"block must be signed"); } var unsignedBytes = block.ToByteArray(false); if (!block.Signature.Verify(unsignedBytes, block.Validator)) { throw new BlockGenerationException($"block signature does not match validator {block.Validator.Text}"); } var hashList = new StorageList(BlockHeightListTag, this.Storage); var expectedBlockHeight = hashList.Count() + 1; if (expectedBlockHeight != block.Height) { throw new ChainException("unexpected block height"); } // from here on, the block is accepted using (var m = new ProfileMarker("changeSet.Execute")) changeSet.Execute(); hashList.Add <Hash>(block.Hash); using (var m = new ProfileMarker("Compress")) { var blockMap = new StorageMap(BlockHashMapTag, this.Storage); var blockBytes = block.ToByteArray(true); blockBytes = CompressionUtils.Compress(blockBytes); blockMap.Set <Hash, byte[]>(block.Hash, blockBytes); var txMap = new StorageMap(TransactionHashMapTag, this.Storage); var txBlockMap = new StorageMap(TxBlockHashMapTag, this.Storage); foreach (Transaction tx in transactions) { var txBytes = tx.ToByteArray(true); txBytes = CompressionUtils.Compress(txBytes); txMap.Set <Hash, byte[]>(tx.Hash, txBytes); txBlockMap.Set <Hash, Hash>(tx.Hash, block.Hash); } } using (var m = new ProfileMarker("AddressBlockHashMapTag")) foreach (var transaction in transactions) { var addresses = new HashSet <Address>(); var events = block.GetEventsForTransaction(transaction.Hash); foreach (var evt in events) { if (evt.Address.IsSystem) { continue; } addresses.Add(evt.Address); } var addressTxMap = new StorageMap(AddressBlockHashMapTag, this.Storage); foreach (var address in addresses) { var addressList = addressTxMap.Get <Address, StorageList>(address); addressList.Add <Hash>(transaction.Hash); } } using (var m = new ProfileMarker("Nexus.PluginTriggerBlock")) Nexus.PluginTriggerBlock(this, block); }
public StorageChangeSetContext ProcessBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee) { if (!block.Validator.IsUser) { throw new BlockGenerationException($"block validator must be user address"); } Block lastBlock; using (var m = new ProfileMarker("GetLastBlock")) { var lastBlockHash = GetLastBlockHash(); lastBlock = GetBlockByHash(lastBlockHash); } if (lastBlock != null) { if (lastBlock.Height != block.Height - 1) { throw new BlockGenerationException($"height of block should be {lastBlock.Height + 1}"); } if (block.PreviousHash != lastBlock.Hash) { throw new BlockGenerationException($"previous hash should be {lastBlock.PreviousHash}"); } if (block.Timestamp < lastBlock.Timestamp) { throw new BlockGenerationException($"timestamp of block {block.Timestamp} should be greater than {lastBlock.Timestamp}"); } } var inputHashes = new HashSet <Hash>(transactions.Select(x => x.Hash)); foreach (var hash in block.TransactionHashes) { if (!inputHashes.Contains(hash)) { throw new BlockGenerationException($"missing in inputs transaction with hash {hash}"); } } var outputHashes = new HashSet <Hash>(block.TransactionHashes); foreach (var tx in transactions) { if (!outputHashes.Contains(tx.Hash)) { throw new BlockGenerationException($"missing in outputs transaction with hash {tx.Hash}"); } } foreach (var tx in transactions) { if (!tx.IsValid(this)) { #if DEBUG tx.IsValid(this); #endif throw new InvalidTransactionException(tx.Hash, $"invalid transaction with hash {tx.Hash}"); } } var oracle = Nexus.GetOracleReader(); block.CleanUp(); Address expectedValidator; using (var m = new ProfileMarker("GetValidator")) expectedValidator = Nexus.HasGenesis ? GetValidator(Nexus.RootStorage, block.Timestamp) : Nexus.GetGenesisAddress(Nexus.RootStorage); if (block.Validator != expectedValidator && !expectedValidator.IsNull) { throw new BlockGenerationException($"unexpected validator {block.Validator}, expected {expectedValidator}"); } var changeSet = ProcessTransactions(block, transactions, oracle, minimumFee); if (oracle.Entries.Any()) { block.MergeOracle(oracle); oracle.Clear(); } return(changeSet); }
public StorageChangeSetContext ProcessTransactions(Block block, IEnumerable <Transaction> transactions , OracleReader oracle, BigInteger minimumFee, bool allowModify = true) { if (allowModify) { block.CleanUp(); } var changeSet = new StorageChangeSetContext(this.Storage); transactions = ProcessPendingTasks(block, oracle, minimumFee, changeSet, allowModify).Concat(transactions); int txIndex = 0; foreach (var tx in transactions) { VMObject vmResult; try { using (var m = new ProfileMarker("ExecuteTransaction")) { if (ExecuteTransaction(txIndex, tx, tx.Script, block.Validator, block.Timestamp, changeSet, block.Notify, oracle, ChainTask.Null, minimumFee, out vmResult, allowModify)) { // merge transaction oracle data oracle.MergeTxData(); if (vmResult != null) { if (allowModify) { var resultBytes = Serialization.Serialize(vmResult); block.SetResultForHash(tx.Hash, resultBytes); } } } else { throw new InvalidTransactionException(tx.Hash, "script execution failed"); } } } catch (Exception e) { e = e.ExpandInnerExceptions(); if (tx == null) { throw new BlockGenerationException(e.Message); } throw new InvalidTransactionException(tx.Hash, e.Message); } txIndex++; } if (this.IsRoot) { var inflationReady = NativeContract.LoadFieldFromStorage <bool>(changeSet, NativeContractKind.Gas, nameof(GasContract._inflationReady)); if (inflationReady) { var script = new ScriptBuilder() .AllowGas(block.Validator, Address.Null, minimumFee, 999999) .CallContract(NativeContractKind.Gas, nameof(GasContract.ApplyInflation), block.Validator) .SpendGas(block.Validator) .EndScript(); var transaction = new Transaction(this.Nexus.Name, this.Name, script, block.Timestamp.Value + 1, "SYSTEM"); VMObject vmResult; if (!ExecuteTransaction(-1, transaction, transaction.Script, block.Validator, block.Timestamp, changeSet, block.Notify, oracle, ChainTask.Null, minimumFee, out vmResult, allowModify)) { throw new ChainException("failed to execute inflation transaction"); } transactions = transactions.Concat(new Transaction[] { transaction }); } } if (block.Protocol > DomainSettings.LatestKnownProtocol) { throw new BlockGenerationException($"unexpected protocol number {block.Protocol}, maybe software update required?"); } // Only check protocol version if block is created on this node, no need to check if it's a non validator node. if (allowModify) { var expectedProtocol = Nexus.GetGovernanceValue(Nexus.RootStorage, Nexus.NexusProtocolVersionTag); if (block.Protocol != expectedProtocol) { throw new BlockGenerationException($"invalid protocol number {block.Protocol}, expected protocol {expectedProtocol}"); } using (var m = new ProfileMarker("CloseBlock")) { CloseBlock(block, changeSet); } } return(changeSet); }
public override ExecutionState Execute(ExecutionFrame frame, Stack <VMObject> stack) { if (this.Contract.ABI == null) { throw new VMException(frame.VM, $"VM nativecall failed: ABI is missing for contract '{this.Contract.Name}'"); } if (stack.Count <= 0) { throw new VMException(frame.VM, $"VM nativecall failed: method name not present in the VM stack"); } var stackObj = stack.Pop(); var methodName = stackObj.AsString(); var method = this.Contract.ABI.FindMethod(methodName); if (method == null) { throw new VMException(frame.VM, $"VM nativecall failed: contract '{this.Contract.Name}' does not have method '{methodName}' in its ABI"); } if (stack.Count < method.parameters.Length) { throw new VMException(frame.VM, $"VM nativecall failed: calling method {methodName} with {stack.Count} arguments instead of {method.parameters.Length}"); } var runtime = (RuntimeVM)frame.VM; using (var m = new ProfileMarker("InternalCall")) if (this.Contract.HasInternalMethod(methodName)) { ExecutionState result; try { BigInteger gasCost = 10; result = runtime.ConsumeGas(gasCost); if (result == ExecutionState.Running) { Contract.LoadRuntimeData(runtime); result = InternalCall(method, frame, stack); Contract.UnloadRuntimeData(); } } catch (ArgumentException ex) { throw new VMException(frame.VM, $"VM nativecall failed: calling method {methodName} with arguments of wrong type, " + ex.ToString()); } // we terminate here execution, since it will be restarted in next context if (result == ExecutionState.Running) { result = ExecutionState.Halt; } return(result); } if (!(this.Contract is CustomContract customContract)) { throw new VMException(frame.VM, $"VM nativecall failed: contract '{this.Contract.Name}' is not a valid custom contract"); } stack.Push(stackObj); var context = new ScriptContext(customContract.Name, customContract.Script); return(context.Execute(frame, stack)); }
public void Step(ExecutionFrame frame, Stack <VMObject> stack) { try { var opcode = (Opcode)Read8(); frame.VM.ValidateOpcode(opcode); switch (opcode) { case Opcode.NOP: { break; } // args: byte src_reg, byte dest_reg case Opcode.MOVE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); frame.Registers[dst] = frame.Registers[src]; break; } // args: byte src_reg, byte dest_reg case Opcode.COPY: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); frame.Registers[dst].Copy(frame.Registers[src]); break; } // args: byte dst_reg, byte type, var length, var data_bytes case Opcode.LOAD: { var dst = Read8(); var type = (VMType)Read8(); var len = (int)ReadVar(0xFFFF); Expect(dst < frame.Registers.Length); var bytes = ReadBytes(len); frame.Registers[dst].SetValue(bytes, type); break; } // args: byte src_reg, dst_reg, byte type case Opcode.CAST: { var src = Read8(); var dst = Read8(); var type = (VMType)Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src]; val = VMObject.CastTo(val, type); frame.Registers[dst] = val; break; } // args: byte src_reg case Opcode.PUSH: { var src = Read8(); Expect(src < frame.Registers.Length); var val = frame.Registers[src]; var temp = new VMObject(); temp.Copy(val); stack.Push(temp); break; } // args: byte dest_reg case Opcode.POP: { var dst = Read8(); Expect(stack.Count > 0); Expect(dst < frame.Registers.Length); frame.Registers[dst] = stack.Pop(); break; } // args: byte src_reg, byte dest_reg case Opcode.SWAP: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var temp = frame.Registers[src]; frame.Registers[src] = frame.Registers[dst]; frame.Registers[dst] = temp; break; } // args: ushort offset, byte regCount case Opcode.CALL: { var count = Read8(); var ofs = Read16(); Expect(ofs < this.Script.Length); Expect(count >= 1); Expect(count <= VirtualMachine.MaxRegisterCount); frame.VM.PushFrame(this, InstructionPointer, count); InstructionPointer = ofs; break; } // args: byte srcReg case Opcode.EXTCALL: using (var m = new ProfileMarker("EXTCALL")) { var src = Read8(); Expect(src < frame.Registers.Length); var method = frame.Registers[src].AsString(); var state = frame.VM.ExecuteInterop(method); if (state != ExecutionState.Running) { throw new VMException(frame.VM, "VM extcall failed: " + method); } break; } // args: ushort offset, byte src_reg // NOTE: JMP only has offset arg, not the rest case Opcode.JMP: case Opcode.JMPIF: case Opcode.JMPNOT: { bool shouldJump; if (opcode == Opcode.JMP) { shouldJump = true; } else { var src = Read8(); Expect(src < frame.Registers.Length); shouldJump = frame.Registers[src].AsBool(); if (opcode == Opcode.JMPNOT) { shouldJump = !shouldJump; } } var newPos = (short)Read16(); Expect(newPos >= 0); Expect(newPos < this.Script.Length); if (shouldJump) { InstructionPointer = (uint)newPos; } break; } // args: var length, var bytes case Opcode.THROW: { var len = (int)ReadVar(1024); if (len > 0) { var bytes = ReadBytes(len); } SetState(ExecutionState.Fault); return; } // args: none case Opcode.RET: { if (frame.VM.frames.Count > 1) { var temp = frame.VM.PeekFrame(); if (temp.Context == this) { InstructionPointer = frame.VM.PopFrame(); //Expect(InstructionPointer == this.Script.Length); } else { SetState(ExecutionState.Halt); } } else { SetState(ExecutionState.Halt); } return; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.CAT: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length); Expect(srcB < frame.Registers.Length); Expect(dst < frame.Registers.Length); var A = frame.Registers[srcA]; var B = frame.Registers[srcB]; if (!A.IsEmpty) { if (B.IsEmpty) { frame.Registers[dst].Copy(A); } else { var bytesA = A.AsByteArray(); var bytesB = B.AsByteArray(); var result = new byte[bytesA.Length + bytesB.Length]; Array.Copy(bytesA, result, bytesA.Length); Array.Copy(bytesB, 0, result, bytesA.Length, bytesB.Length); Expect(A.Type == B.Type); VMType type = A.Type; frame.Registers[dst].SetValue(result, type); } } else { if (B.IsEmpty) { frame.Registers[dst] = new VMObject(); } else { frame.Registers[dst].Copy(B); } } break; } case Opcode.SUBSTR: { throw new NotImplementedException(); } // args: byte src_reg, byte dest_reg, var length case Opcode.LEFT: { var src = Read8(); var dst = Read8(); var len = (int)ReadVar(0xFFFF); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var src_array = frame.Registers[src].AsByteArray(); Expect(len <= src_array.Length); var result = new byte[len]; Array.Copy(src_array, result, len); frame.Registers[dst].SetValue(result, VMType.Bytes); break; } // args: byte src_reg, byte dest_reg, byte length case Opcode.RIGHT: { var src = Read8(); var dst = Read8(); var len = (int)ReadVar(0xFFFF); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var src_array = frame.Registers[src].AsByteArray(); Expect(len <= src_array.Length); var ofs = src_array.Length - len; var result = new byte[len]; Array.Copy(src_array, ofs, result, 0, len); frame.Registers[dst].SetValue(result, VMType.Bytes); break; } // args: byte src_reg, byte dest_reg case Opcode.SIZE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var src_array = frame.Registers[src].AsByteArray(); frame.Registers[dst].SetValue(src_array.Length); break; } // args: byte src_reg, byte dest_reg case Opcode.COUNT: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src]; int count; switch (val.Type) { case VMType.Struct: { var children = val.GetChildren(); count = children.Count; break; } default: count = 1; break; } frame.Registers[dst].SetValue(count); break; } // args: byte src_reg, byte dest_reg case Opcode.NOT: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src].AsBool(); frame.Registers[dst].SetValue(!val); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.AND: case Opcode.OR: case Opcode.XOR: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length); Expect(srcB < frame.Registers.Length); Expect(dst < frame.Registers.Length); var a = frame.Registers[srcA].AsBool(); var b = frame.Registers[srcB].AsBool(); bool result; switch (opcode) { case Opcode.AND: result = (a && b); break; case Opcode.OR: result = (a || b); break; case Opcode.XOR: result = (a ^ b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.EQUAL: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length); Expect(srcB < frame.Registers.Length); Expect(dst < frame.Registers.Length); var a = frame.Registers[srcA]; var b = frame.Registers[srcB]; var result = a.Equals(b); frame.Registers[dst].SetValue(result); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.LT: case Opcode.GT: case Opcode.LTE: case Opcode.GTE: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length); Expect(srcB < frame.Registers.Length); Expect(dst < frame.Registers.Length); var a = frame.Registers[srcA].AsNumber(); var b = frame.Registers[srcB].AsNumber(); bool result; switch (opcode) { case Opcode.LT: result = (a < b); break; case Opcode.GT: result = (a > b); break; case Opcode.LTE: result = (a <= b); break; case Opcode.GTE: result = (a >= b); break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } // args: byte reg case Opcode.INC: { var dst = Read8(); Expect(dst < frame.Registers.Length); var val = frame.Registers[dst].AsNumber(); frame.Registers[dst].SetValue(val + 1); break; } // args: byte reg case Opcode.DEC: { var dst = Read8(); Expect(dst < frame.Registers.Length); var val = frame.Registers[dst].AsNumber(); frame.Registers[dst].SetValue(val - 1); break; } // args: byte src_reg, byte dest_reg case Opcode.SIGN: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src].AsNumber(); if (val == 0) { frame.Registers[dst].SetValue(BigInteger.Zero); } else { frame.Registers[dst].SetValue(val < 0 ? -1 : 1); } break; } // args: byte src_reg, byte dest_reg case Opcode.NEGATE: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src].AsNumber(); frame.Registers[dst].SetValue(-val); break; } // args: byte src_reg, byte dest_reg case Opcode.ABS: { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var val = frame.Registers[src].AsNumber(); frame.Registers[dst].SetValue(val < 0 ? -val : val); break; } // args: byte src_a_reg, byte src_b_reg, byte dest_reg case Opcode.ADD: case Opcode.SUB: case Opcode.MUL: case Opcode.DIV: case Opcode.MOD: case Opcode.SHR: case Opcode.SHL: case Opcode.MIN: case Opcode.MAX: { var srcA = Read8(); var srcB = Read8(); var dst = Read8(); Expect(srcA < frame.Registers.Length); Expect(srcB < frame.Registers.Length); Expect(dst < frame.Registers.Length); var a = frame.Registers[srcA].AsNumber(); var b = frame.Registers[srcB].AsNumber(); BigInteger result; switch (opcode) { case Opcode.ADD: result = a + b; break; case Opcode.SUB: result = a - b; break; case Opcode.MUL: result = a * b; break; case Opcode.DIV: result = a / b; break; case Opcode.MOD: result = a % b; break; case Opcode.SHR: result = a >> (int)b; break; case Opcode.SHL: result = a << (int)b; break; case Opcode.MIN: result = a < b ? a : b; break; case Opcode.MAX: result = a > b ? a : b; break; default: { SetState(ExecutionState.Fault); return; } } frame.Registers[dst].SetValue(result); break; } // args: byte src_reg, byte dest_reg, byte key case Opcode.PUT: { var src = Read8(); var dst = Read8(); var keyReg = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); Expect(keyReg < frame.Registers.Length); var key = frame.Registers[keyReg]; var value = frame.Registers[src]; frame.Registers[dst].SetKey(key, value); break; } // args: byte src_reg, byte dest_reg, byte key case Opcode.GET: { var src = Read8(); var dst = Read8(); var keyReg = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); Expect(keyReg < frame.Registers.Length); var key = frame.Registers[keyReg]; var val = frame.Registers[src].GetKey(key); frame.Registers[dst] = val; break; } // args: byte dest_reg case Opcode.THIS: { var dst = Read8(); Expect(dst < frame.Registers.Length); frame.Registers[dst].SetValue(this); break; } // args: byte dest_reg, var key case Opcode.CTX: using (var m = new ProfileMarker("CTX")) { var src = Read8(); var dst = Read8(); Expect(src < frame.Registers.Length); Expect(dst < frame.Registers.Length); var contextName = frame.Registers[src].AsString(); ExecutionContext context = frame.VM.FindContext(contextName); if (context == null) { throw new VMException(frame.VM, $"VM ctx instruction failed: could not find context with name '{contextName}'"); } frame.Registers[dst].SetValue(context); break; } // args: byte src_reg case Opcode.SWITCH: using (var m = new ProfileMarker("SWITCH")) { var src = Read8(); Expect(src < frame.Registers.Length); var context = frame.Registers[src].AsInterop <ExecutionContext>(); _state = frame.VM.SwitchContext(context, InstructionPointer); if (_state == ExecutionState.Halt) { _state = ExecutionState.Running; frame.VM.PopFrame(); } else { throw new VMException(frame.VM, $"VM switch instruction failed: execution state did not halt"); } break; } default: { throw new VMException(frame.VM, $"Unknown VM opcode: {(int)opcode}"); } } } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ((TargetInvocationException)ex).InnerException; } Trace.WriteLine(ex.ToString()); SetState(ExecutionState.Fault); if (!(ex is VMException)) { ex = new VMException(frame.VM, ex.Message); } if (frame.VM.ThrowOnFault) // enable this when debugging difficult stuff in the VM, should not be activated for production code { throw ex; } } }
private TaskResult ProcessPendingTask(Block block, OracleReader oracle, BigInteger minimumFee, StorageChangeSetContext changeSet, bool allowModify, ChainTask task, out Transaction transaction) { transaction = null; if (task.Mode != TaskFrequencyMode.Always) { var taskKey = GetTaskKey(task.ID, "task_run"); BigInteger lastRun = changeSet.Has(taskKey) ? changeSet.Get <BigInteger>(taskKey) : 0; switch (task.Mode) { case TaskFrequencyMode.Blocks: { var diff = block.Height - lastRun; if (diff < task.Frequency) { return(TaskResult.Skipped); // skip execution for now } break; } case TaskFrequencyMode.Time: { var diff = block.Timestamp.Value - lastRun; if (diff < task.Frequency) { return(TaskResult.Skipped); // skip execution for now } break; } } } using (var m = new ProfileMarker("ExecuteTask")) { var taskScript = new ScriptBuilder() .AllowGas(task.Owner, Address.Null, minimumFee, task.GasLimit) .CallContract(task.ContextName, task.Method) .SpendGas(task.Owner) .EndScript(); transaction = new Transaction(this.Nexus.Name, this.Name, taskScript, block.Timestamp.Value + 1, "TASK"); VMObject vmResult; if (ExecuteTransaction(-1, transaction, transaction.Script, block.Validator, block.Timestamp, changeSet, block.Notify, oracle, task, minimumFee, out vmResult, allowModify)) { // merge transaction oracle data oracle.MergeTxData(); var resultBytes = Serialization.Serialize(vmResult); block.SetResultForHash(transaction.Hash, resultBytes); var shouldStop = vmResult.AsBool(); return(shouldStop ? TaskResult.Halted : TaskResult.Running); } else { transaction = null; return(TaskResult.Crashed); } } }