public object InvokeContract(string contractName, string methodName, params object[] args) { var contract = FindContract <SmartContract>(contractName); Throw.IfNull(contract, nameof(contract)); var script = ScriptUtils.BeginScript().CallContract(contractName, methodName, args).EndScript(); var changeSet = new StorageChangeSetContext(this.Storage); var vm = new RuntimeVM(script, this, this.LastBlock, null, changeSet, true); contract.SetRuntimeData(vm); var state = vm.Execute(); if (state != ExecutionState.Halt) { throw new ChainException($"Invocation of method '{methodName}' of contract '{contractName}' failed with state: " + state); } if (vm.Stack.Count == 0) { throw new ChainException($"No result, vm stack is empty"); } var result = vm.Stack.Pop(); return(result.ToObject()); }
private static VMObject ExecuteScript(Chain chain, byte[] script, ContractInterface abi, string methodName, params object[] args) { var method = abi.FindMethod(methodName); if (method == null) { throw new Exception("ABI is missing: " + method.name); } var changeSet = new StorageChangeSetContext(chain.Storage); var oracle = chain.Nexus.GetOracleReader(); var vm = new RuntimeVM(-1, script, (uint)method.offset, chain, Address.Null, Timestamp.Now, null, changeSet, oracle, ChainTask.Null, true); //var vm = new GasMachine(script, (uint)method.offset); // TODO maybe this needs to be in inverted order? foreach (var arg in args) { vm.Stack.Push(VMObject.FromObject(arg)); } var result = vm.Execute(); if (result == ExecutionState.Halt) { return(vm.Stack.Pop()); } throw new Exception("Script execution failed for: " + method.name); }
internal bool Execute(Chain chain, Block block, StorageChangeSetContext changeSet, Action <Hash, Event> onNotify, out byte[] result) { result = null; var runtime = new RuntimeVM(this.Script, chain, block, this, changeSet, false); var state = runtime.Execute(); if (state != ExecutionState.Halt) { return(false); } var cost = runtime.UsedGas; // fee distribution TODO // if (chain.NativeTokenAddress != null && cost > 0) { //chain.TransferToken(this.PublicKey, chain.DistributionPubKey, cost); } foreach (var evt in runtime.Events) { onNotify(this.Hash, evt); } if (runtime.Stack.Count > 0) { var obj = runtime.Stack.Pop(); result = Serialization.Serialize(obj); } return(true); }
internal bool Execute(Chain chain, Block block, StorageChangeSetContext changeSet, Action <Hash, Event> onNotify, OracleReaderDelegate oracleReader, out byte[] result) { result = null; var runtime = new RuntimeVM(this.Script, chain, block, this, changeSet, false); runtime.ThrowOnFault = true; runtime.OracleReader = oracleReader; var state = runtime.Execute(); if (state != ExecutionState.Halt) { return(false); } var cost = runtime.UsedGas; foreach (var evt in runtime.Events) { onNotify(this.Hash, evt); } if (runtime.Stack.Count > 0) { var obj = runtime.Stack.Pop(); result = Serialization.Serialize(obj); } return(true); }
private bool ExecuteTransaction(Transaction transaction, Timestamp time, StorageChangeSetContext changeSet, Action <Hash, Event> onNotify, OracleReader oracle, BigInteger minimumFee, out byte[] result) { result = null; var runtime = new RuntimeVM(transaction.Script, this, time, transaction, changeSet, oracle, false); runtime.MinimumFee = minimumFee; runtime.ThrowOnFault = true; var state = runtime.Execute(); if (state != ExecutionState.Halt) { return(false); } var cost = runtime.UsedGas; foreach (var evt in runtime.Events) { onNotify(transaction.Hash, evt); } if (runtime.Stack.Count > 0) { var obj = runtime.Stack.Pop(); result = Serialization.Serialize(obj); } return(true); }
public void TestDBChangeSetStorageMapClear() { var storage = new KeyStoreStorage(CreateKeyStoreAdapterTest("test2")); var changeSet = new StorageChangeSetContext(storage); var testMapKey = Encoding.UTF8.GetBytes($".test._valueMap"); var testMapKey2 = Encoding.UTF8.GetBytes($".test2._valueMap"); var testMap = new StorageMap(testMapKey, changeSet); var testMap2 = new StorageMap(testMapKey2, changeSet); testMap.Set("test1", "Value1"); testMap.Set("test2", "Value2"); testMap.Set("test3", "Value3"); testMap.Set("test4", "Value4"); Assert.IsTrue(testMap.Count() == 4); testMap2.Set <BigInteger, string>(new BigInteger(1), "Value21"); testMap2.Set <BigInteger, string>(new BigInteger(2), "Value22"); testMap2.Set <BigInteger, string>(new BigInteger(3), "Value23"); testMap2.Set <BigInteger, string>(new BigInteger(4), "Value24"); Assert.IsTrue(testMap2.Count() == 4); changeSet.Execute(); var count = 0; testMap.Visit <string, string>((key, value) => { count++; }); testMap2.Visit <BigInteger, string>((key, value) => { count++; }); Console.WriteLine($"visit: {count} count: {(int)testMap.Count() + testMap2.Count()}"); Assert.AreEqual(count, (int)testMap.Count() + testMap2.Count()); testMap.Clear(); testMap2.Clear(); Assert.IsTrue(testMap.Count() == 0); Assert.IsTrue(testMap2.Count() == 0); Assert.IsNull(testMap.Get <string, string>("test1")); Assert.IsNull(testMap.Get <string, string>("test2")); Assert.IsNull(testMap.Get <string, string>("test3")); Assert.IsNull(testMap.Get <string, string>("test4")); Assert.IsNull(testMap2.Get <BigInteger, string>(new BigInteger(1))); Assert.IsNull(testMap2.Get <BigInteger, string>(new BigInteger(2))); Assert.IsNull(testMap2.Get <BigInteger, string>(new BigInteger(3))); Assert.IsNull(testMap2.Get <BigInteger, string>(new BigInteger(4))); }
public void TestDBChangeSetStorageMap22() { var storage = (StorageContext) new KeyStoreStorage(CreateKeyStoreAdapterTest("test2")); var changeSet = new StorageChangeSetContext(storage); var testMapKey = Encoding.UTF8.GetBytes($".test._valueMap"); var testMap = new StorageMap(testMapKey, changeSet); testMap.Set(1, 1); Assert.IsTrue(testMap.Count() == 1); testMap.Clear(); Assert.IsTrue(testMap.Count() == 0); }
private RuntimeVM ExecuteScript(string[] scriptString) { var script = BuildScript(scriptString); var keys = KeyPair.Generate(); var nexus = new Nexus("vmnet", keys.Address, new ConsoleLogger()); var tx = new Transaction(nexus.Name, nexus.RootChain.Name, script, 0, 0); var changeSet = new StorageChangeSetContext(new MemoryStorageContext()); var vm = new RuntimeVM(tx.Script, nexus.RootChain, null, tx, changeSet, true); vm.Execute(); return(vm); }
public RuntimeVM(byte[] script, Chain chain, Timestamp time, Transaction transaction, StorageChangeSetContext changeSet, OracleReader oracle, bool readOnlyMode, bool delayPayment = false) : base(script) { Core.Throw.IfNull(chain, nameof(chain)); Core.Throw.IfNull(changeSet, nameof(changeSet)); // NOTE: block and transaction can be null, required for Chain.InvokeContract //Throw.IfNull(block, nameof(block)); //Throw.IfNull(transaction, nameof(transaction)); this.MinimumFee = 1; this.GasPrice = 0; this.UsedGas = 0; this.PaidGas = 0; this.GasTarget = chain.Address; this.MaxGas = 10000; // a minimum amount required for allowing calls to Gas contract etc this.DelayPayment = delayPayment; this.Time = time; this.Chain = chain; this.Transaction = transaction; this.Oracle = oracle; this.changeSet = changeSet; this.readOnlyMode = readOnlyMode; this.isBlockOperation = false; this.randomized = false; this.FeeTargetAddress = Address.Null; if (this.Chain != null && !Chain.IsRoot) { var parentName = chain.Nexus.GetParentChainByName(chain.Name); this.ParentChain = chain.Nexus.GetChainByName(parentName); } else { this.ParentChain = null; } ExtCalls.RegisterWithRuntime(this); }
public VMObject InvokeScript(byte[] script) { var changeSet = new StorageChangeSetContext(this.Storage); var vm = new RuntimeVM(script, this, this.LastBlock, null, changeSet, true); var state = vm.Execute(); if (state != ExecutionState.Halt) { return(null); } if (vm.Stack.Count == 0) { throw new ChainException($"No result, vm stack is empty"); } var result = vm.Stack.Pop(); return(result); }
public VMObject InvokeScript(StorageContext storage, byte[] script, Timestamp time) { var oracle = Nexus.CreateOracleReader(); var changeSet = new StorageChangeSetContext(storage); var vm = new RuntimeVM(script, this, time, null, changeSet, oracle, true); var state = vm.Execute(); if (state != ExecutionState.Halt) { return(null); } if (vm.Stack.Count == 0) { throw new ChainException($"No result, vm stack is empty"); } var result = vm.Stack.Pop(); return(result); }
public RuntimeVM(byte[] script, Chain chain, Block block, Transaction transaction, StorageChangeSetContext changeSet, bool readOnlyMode) : base(script) { Throw.IfNull(chain, nameof(chain)); Throw.IfNull(changeSet, nameof(changeSet)); // NOTE: block and transaction can be null, required for Chain.InvokeContract //Throw.IfNull(block, nameof(block)); //Throw.IfNull(transaction, nameof(transaction)); this.GasPrice = 0; this.UsedGas = 0; this.PaidGas = 0; this.MaxGas = 100; // a minimum amount required for allowing calls to Gas contract etc this.Chain = chain; this.Block = block; this.Transaction = transaction; this.ChangeSet = changeSet; this.readOnlyMode = readOnlyMode; Chain.RegisterInterop(this); }
public IAPIResult InvokeRawScript([APIParameter("Address or name of chain", "root")] string chainInput, [APIParameter("Serialized script bytes, in hexadecimal format", "0000000000")] string scriptData) { var chain = FindChainByInput(chainInput); if (chain == null) { return(new ErrorResult { error = "invalid chain" }); } byte[] script; try { script = Base16.Decode(scriptData); } catch { return(new ErrorResult { error = "Failed to decode script" }); } if (script.Length == 0) { return(new ErrorResult { error = "Invalid transaction script" }); } var changeSet = new StorageChangeSetContext(chain.Storage); var vm = new RuntimeVM(script, chain, null, null, changeSet, true); var state = vm.Execute(); if (state != ExecutionState.Halt) { return(new ErrorResult { error = $"Execution failed, state:{state}" }); } string encodedResult; if (vm.Stack.Count == 0) { encodedResult = ""; } else { var temp = vm.Stack.Pop(); var result = temp.ToObject(); var resultBytes = Serialization.Serialize(result); encodedResult = Base16.Encode(resultBytes); } var evts = vm.Events.Select(evt => new EventResult() { address = evt.Address.Text, kind = evt.Kind.ToString(), data = Base16.Encode(evt.Data) }); return(new ScriptResult { result = encodedResult, events = evts.ToArray() }); }
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 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); }
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) { 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; 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())) if (allowModify) { onNotify(transaction.Hash, evt); } } } if (runtime.Stack.Count > 0) { result = runtime.Stack.Pop(); } return(true); }
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 void CloseBlock(Block block, StorageChangeSetContext storage) { var rootStorage = this.IsRoot ? storage : Nexus.RootStorage; if (block.Height > 1) { var prevBlock = GetBlockByHash(block.PreviousHash); if (prevBlock.Validator != block.Validator) { block.Notify(new Event(EventKind.ValidatorSwitch, block.Validator, "block", Serialization.Serialize(prevBlock))); } } var balance = new BalanceSheet(DomainSettings.FuelTokenSymbol); var blockAddress = Address.FromHash("block"); var totalAvailable = balance.Get(storage, blockAddress); var targets = new List <Address>(); if (Nexus.HasGenesis) { var validators = Nexus.GetValidators(); var totalValidators = Nexus.GetPrimaryValidatorCount(); for (int i = 0; i < totalValidators; i++) { var validator = validators[i]; if (validator.type != ValidatorType.Primary) { continue; } targets.Add(validator.address); } } else if (totalAvailable > 0) { targets.Add(Nexus.GetGenesisAddress(rootStorage)); } if (targets.Count > 0) { if (!balance.Subtract(storage, blockAddress, totalAvailable)) { throw new BlockGenerationException("could not subtract balance from block address"); } var amountPerValidator = totalAvailable / targets.Count; var leftOvers = totalAvailable - (amountPerValidator * targets.Count); foreach (var address in targets) { BigInteger amount = amountPerValidator; if (address == block.Validator) { amount += leftOvers; } // TODO this should use triggers when available... if (!balance.Add(storage, address, amount)) { throw new BlockGenerationException($"could not add balance to {address}"); } var eventData = Serialization.Serialize(new TokenEventData(DomainSettings.FuelTokenSymbol, amount, this.Name)); block.Notify(new Event(EventKind.TokenClaim, address, "block", eventData)); } } }
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); } } }
private IEnumerable <Transaction> ProcessPendingTasks(Block block, OracleReader oracle, BigInteger minimumFee, StorageChangeSetContext changeSet, bool allowModify) { var taskList = new StorageList(TaskListTag, changeSet); var taskCount = taskList.Count(); List <Transaction> transactions = null; int i = 0; while (i < taskCount) { var taskID = taskList.Get <BigInteger>(i); var task = GetTask(changeSet, taskID); Transaction tx; var taskResult = ProcessPendingTask(block, oracle, minimumFee, changeSet, allowModify, task, out tx); if (taskResult == TaskResult.Running) { i++; } else { taskList.RemoveAt <BigInteger>(i); } if (tx != null) { if (transactions == null) { transactions = new List <Transaction>(); } transactions.Add(tx); } } if (transactions != null) { return(transactions); } return(Enumerable.Empty <Transaction>()); }
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); }
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); }
public void AddBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee) { /*if (CurrentEpoch != null && CurrentEpoch.IsSlashed(Timestamp.Now)) * { * return false; * }*/ var lastBlockHash = GetLastBlockHash(); var 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}"); } } 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(); foreach (var tx in transactions) { byte[] result; try { if (ExecuteTransaction(tx, block.Timestamp, changeSet, block.Notify, oracle, minimumFee, out result)) { if (result != null) { block.SetResultForHash(tx.Hash, result); } } else { throw new InvalidTransactionException(tx.Hash, $"execution failed"); } } catch (Exception e) { if (e.InnerException != null) { e = e.InnerException; } throw new InvalidTransactionException(tx.Hash, e.Message); } } block.MergeOracle(oracle); 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 changeSet.Execute(); hashList.Add <Hash>(block.Hash); var blockMap = new StorageMap(BlockHashMapTag, this.Storage); blockMap.Set <Hash, Block>(block.Hash, block); var txMap = new StorageMap(TransactionHashMapTag, this.Storage); var txBlockMap = new StorageMap(TxBlockHashMapTag, this.Storage); foreach (Transaction tx in transactions) { txMap.Set <Hash, Transaction>(tx.Hash, tx); txBlockMap.Set <Hash, Hash>(tx.Hash, block.Hash); } var blockValidator = GetValidatorForBlock(block); if (blockValidator.IsNull) { throw new BlockGenerationException("no validator for this block"); } Nexus.PluginTriggerBlock(this, block); }
public void AddBlock(Block block, IEnumerable <Transaction> transactions, OracleReaderDelegate oracleReader) { /*if (CurrentEpoch != null && CurrentEpoch.IsSlashed(Timestamp.Now)) * { * return false; * }*/ 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}"); } } 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)) { throw new InvalidTransactionException(tx.Hash, $"invalid transaction with hash {tx.Hash}"); } } var changeSet = new StorageChangeSetContext(this.Storage); foreach (var tx in transactions) { byte[] result; if (tx.Execute(this, block, changeSet, block.Notify, oracleReader, out result)) { if (result != null) { block.SetResultForHash(tx.Hash, result); } } else { throw new InvalidTransactionException(tx.Hash, $"transaction execution failed with hash {tx.Hash}"); } } // from here on, the block is accepted _blockHeightMap[block.Height] = block; _blocks[block.Hash] = block; _blockChangeSets[block.Hash] = changeSet; changeSet.Execute(); if (CurrentEpoch == null) { GenerateEpoch(); } CurrentEpoch.AddBlockHash(block.Hash); CurrentEpoch.UpdateHash(); LastBlock = block; foreach (Transaction tx in transactions) { _transactions[tx.Hash] = tx; _transactionBlockMap[tx.Hash] = block.Hash; } Nexus.PluginTriggerBlock(this, block); }
public void AddBlock(Block block, IEnumerable <Transaction> transactions, BigInteger minimumFee) { /*if (CurrentEpoch != null && CurrentEpoch.IsSlashed(Timestamp.Now)) * { * return false; * }*/ 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}"); } } 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)) { throw new InvalidTransactionException(tx.Hash, $"invalid transaction with hash {tx.Hash}"); } } var changeSet = new StorageChangeSetContext(this.Storage); var oracle = Nexus.CreateOracleReader(); foreach (var tx in transactions) { byte[] result; try { if (tx.Execute(this, block.Timestamp, changeSet, block.Notify, oracle, minimumFee, out result)) { if (result != null) { block.SetResultForHash(tx.Hash, result); } } else { throw new InvalidTransactionException(tx.Hash, $"execution failed"); } } catch (Exception e) { if (e.InnerException != null) { e = e.InnerException; } throw new InvalidTransactionException(tx.Hash, e.Message); } } block.MergeOracle(oracle); // from here on, the block is accepted _blockHeightMap[block.Height] = block.Hash; _blocks[block.Hash] = block; _blockChangeSets[block.Hash] = changeSet; changeSet.Execute(); Dictionary <string, BigInteger> synchMap = null; foreach (Transaction tx in transactions) { _transactions[tx.Hash] = tx; _transactionBlockMap[tx.Hash] = block.Hash; var evts = block.GetEventsForTransaction(tx.Hash); foreach (var evt in evts) { if (evt.Kind == EventKind.TokenMint || evt.Kind == EventKind.TokenBurn || evt.Kind == EventKind.TokenReceive || evt.Kind == EventKind.TokenSend) { var eventData = evt.GetContent <TokenEventData>(); var token = Nexus.GetTokenInfo(eventData.symbol); if (!token.IsFungible) { // TODO support this continue; } if (token.IsCapped) { BigInteger balance; if (synchMap == null) { synchMap = new Dictionary <string, BigInteger>(); balance = 0; } else { balance = synchMap.ContainsKey(eventData.symbol) ? synchMap[eventData.symbol] : 0; } if (evt.Kind == EventKind.TokenBurn || evt.Kind == EventKind.TokenSend) { balance -= eventData.value; } else { balance += eventData.value; } synchMap[eventData.symbol] = balance; } } } } if (synchMap != null) { SynchronizeSupplies(synchMap); } var blockValidator = GetValidatorForBlock(block); if (blockValidator.IsNull) { throw new BlockGenerationException("no validator for this block"); } Nexus.PluginTriggerBlock(this, block); }
public RuntimeVM(byte[] script, Chain chain, Block block, Transaction transaction, StorageChangeSetContext changeSet, bool readOnlyMode, bool delayPayment = false) : base(script) { Throw.IfNull(chain, nameof(chain)); Throw.IfNull(changeSet, nameof(changeSet)); // NOTE: block and transaction can be null, required for Chain.InvokeContract //Throw.IfNull(block, nameof(block)); //Throw.IfNull(transaction, nameof(transaction)); this.GasPrice = 0; this.UsedGas = 0; this.PaidGas = 0; this.MaxGas = 10000; // a minimum amount required for allowing calls to Gas contract etc this.DelayPayment = delayPayment; this.Chain = chain; this.Block = block; this.Transaction = transaction; this.ChangeSet = changeSet; this.readOnlyMode = readOnlyMode; this.FeeTargetAddress = Address.Null; if (this.Chain != null && !Chain.IsRoot) { var parentName = chain.Nexus.GetParentChainByName(chain.Name); this.ParentChain = chain.Nexus.FindChainByName(parentName); } else { this.ParentChain = null; } Chain.RegisterInterop(this); }