private static ExecutionState Runtime_DeployContract(RuntimeVM vm) { var tx = vm.Transaction; Throw.IfNull(tx, nameof(tx)); var pow = tx.Hash.GetDifficulty(); vm.Expect(pow >= (int)ProofOfWork.Minimal, "expected proof of work"); vm.ExpectStackSize(1); var from = vm.PopAddress(); vm.Expect(from.IsUser, "address must be user"); if (vm.Nexus.HasGenesis) { //Runtime.Expect(org != DomainSettings.ValidatorsOrganizationName, "cannot deploy contract via this organization"); vm.Expect(vm.IsStakeMaster(from), "needs to be master"); } vm.Expect(vm.IsWitness(from), "invalid witness"); var contractName = vm.PopString("contractName"); var contractAddress = SmartContract.GetAddressForName(contractName); var deployed = vm.Chain.IsContractDeployed(vm.Storage, contractAddress); // TODO if (vm.ProtocolVersion >= 2) { vm.Expect(!deployed, $"{contractName} is already deployed"); } else if (deployed) { return(ExecutionState.Running); } byte[] script; ContractInterface abi; bool isNative = Nexus.IsNativeContract(contractName); if (isNative) { if (contractName == "validator" && vm.GenesisAddress == Address.Null) { vm.Nexus.Initialize(from); } script = new byte[] { (byte)Opcode.RET }; var contractInstance = vm.Nexus.GetNativeContractByAddress(contractAddress); abi = contractInstance.ABI; } else { if (ValidationUtils.IsValidTicker(contractName)) { throw new VMException(vm, "use createToken instead for this kind of contract"); } else { vm.Expect(ValidationUtils.IsValidIdentifier(contractName), "invalid contract name"); } var isReserved = ValidationUtils.IsReservedIdentifier(contractName); if (isReserved && vm.IsWitness(vm.GenesisAddress)) { isReserved = false; } vm.Expect(!isReserved, $"name '{contractName}' reserved by system"); script = vm.PopBytes("contractScript"); var abiBytes = vm.PopBytes("contractABI"); abi = ContractInterface.FromBytes(abiBytes); var fuelCost = vm.GetGovernanceValue(Nexus.FuelPerContractDeployTag); // governance value is in usd fiat, here convert from fiat to fuel amount fuelCost = vm.GetTokenQuote(DomainSettings.FiatTokenSymbol, DomainSettings.FuelTokenSymbol, fuelCost); // burn the "cost" tokens vm.BurnTokens(DomainSettings.FuelTokenSymbol, from, fuelCost); } // ABI validation ValidateABI(vm, contractName, abi, isNative); var success = vm.Chain.DeployContractScript(vm.Storage, from, contractName, contractAddress, script, abi); vm.Expect(success, $"deployment of {contractName} failed"); var constructor = abi.FindMethod(SmartContract.ConstructorName); if (constructor != null) { vm.CallContext(contractName, constructor, from); } vm.Notify(EventKind.ContractDeploy, from, contractName); return(ExecutionState.Running); }
private static ExecutionState Runtime_UpgradeContract(RuntimeVM vm) { var tx = vm.Transaction; Throw.IfNull(tx, nameof(tx)); var pow = tx.Hash.GetDifficulty(); vm.Expect(pow >= (int)ProofOfWork.Minimal, "expected proof of work"); vm.ExpectStackSize(1); var from = vm.PopAddress(); vm.Expect(from.IsUser, "address must be user"); vm.Expect(vm.IsStakeMaster(from), "needs to be master"); vm.Expect(vm.IsWitness(from), "invalid witness"); var contractName = vm.PopString("contractName"); var contractAddress = SmartContract.GetAddressForName(contractName); var deployed = vm.Chain.IsContractDeployed(vm.Storage, contractAddress); vm.Expect(deployed, $"{contractName} does not exist"); byte[] script; ContractInterface abi; bool isNative = Nexus.IsNativeContract(contractName); vm.Expect(!isNative, "cannot upgrade native contract"); bool isToken = ValidationUtils.IsValidTicker(contractName); script = vm.PopBytes("contractScript"); var abiBytes = vm.PopBytes("contractABI"); abi = ContractInterface.FromBytes(abiBytes); var fuelCost = vm.GetGovernanceValue(Nexus.FuelPerContractDeployTag); // governance value is in usd fiat, here convert from fiat to fuel amount fuelCost = vm.GetTokenQuote(DomainSettings.FiatTokenSymbol, DomainSettings.FuelTokenSymbol, fuelCost); // burn the "cost" tokens vm.BurnTokens(DomainSettings.FuelTokenSymbol, from, fuelCost); // ABI validation ValidateABI(vm, contractName, abi, isNative); SmartContract oldContract; if (isToken) { oldContract = vm.Nexus.GetTokenContract(vm.Storage, contractName); } else { oldContract = vm.Chain.GetContractByName(vm.Storage, contractName); } vm.Expect(oldContract != null, "could not fetch previous contract"); vm.Expect(abi.Implements(oldContract.ABI), "new abi does not implement all methods of previous abi"); vm.Expect(vm.InvokeTrigger(false, script, contractName, abi, AccountTrigger.OnUpgrade.ToString(), from) == TriggerResult.Success, "OnUpgrade trigger failed"); if (isToken) { vm.Nexus.UpgradeTokenContract(vm.RootStorage, contractName, script, abi); } else { vm.Chain.UpgradeContract(vm.Storage, contractName, script, abi); } vm.Notify(EventKind.ContractUpgrade, from, contractName); return(ExecutionState.Running); }
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 (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); // 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 OracleReader(Nexus nexus) { this.Nexus = nexus; }
public BigInteger GetTokenBalance(StorageContext storage, string symbol, Address address) { var token = Nexus.GetTokenInfo(storage, symbol); return(GetTokenBalance(storage, token, address)); }
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 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 DummyOracle(Nexus nexus) : base(nexus) { }
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 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)); } } }
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 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 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); }
public bool Submit(Transaction tx) { if (this.CurrentState != State.Running) { return(false); } Throw.IfNull(tx, nameof(tx)); var chain = Nexus.FindChainByName(tx.ChainName); Throw.IfNull(chain, nameof(chain)); if (tx.Signatures == null || tx.Signatures.Length < 1) { RejectTransaction(tx, "at least one signature required"); } var currentTime = Timestamp.Now; if (tx.Expiration <= currentTime) { RejectTransaction(tx, "already expired"); } var diff = tx.Expiration - currentTime; if (diff > MaxExpirationTimeDifferenceInSeconds) { RejectTransaction(tx, "expire date too big"); } if (tx.NexusName != this.Nexus.Name) { RejectTransaction(tx, "invalid nexus name"); } if (_hashMap.ContainsKey(tx.Hash)) { throw new MempoolSubmissionException("already in mempool"); } var entry = new MempoolEntry() { transaction = tx, timestamp = Timestamp.Now }; List <MempoolEntry> list; lock (_entries) { if (_entries.ContainsKey(chain.Name)) { list = _entries[chain.Name]; } else { list = new List <MempoolEntry>(); _entries[chain.Name] = list; } list.Add(entry); _hashMap[tx.Hash] = chain.Name; } Interlocked.Increment(ref _size); OnTransactionAdded?.Invoke(tx); return(true); }
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 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 Chain(Nexus nexus, string name, IEnumerable <SmartContract> contracts, Logger log = null, Chain parentChain = null, Block parentBlock = null) { Throw.IfNull(nexus, "nexus required"); Throw.If(contracts == null || !contracts.Any(), "contracts required"); if (parentChain != null) { Throw.IfNull(parentBlock, "parent block required"); Throw.IfNot(nexus.ContainsChain(parentChain), "invalid chain"); //Throw.IfNot(parentChain.ContainsBlock(parentBlock), "invalid block"); // TODO should this be required? } var bytes = System.Text.Encoding.UTF8.GetBytes(name.ToLower()); var hash = CryptoExtensions.SHA256(bytes); this.Address = new Address(hash); // init stores _transactions = new KeyValueStore <Transaction>(this.Address, "txs", KeyStoreDataSize.Medium, nexus.CacheSize); _blocks = new KeyValueStore <Block>(this.Address, "blocks", KeyStoreDataSize.Medium, nexus.CacheSize); _transactionBlockMap = new KeyValueStore <Hash>(this.Address, "txbk", KeyStoreDataSize.Small, nexus.CacheSize); _epochMap = new KeyValueStore <Epoch>(this.Address, "epoch", KeyStoreDataSize.Medium, nexus.CacheSize); foreach (var contract in contracts) { if (this._contracts.ContainsKey(contract.Name)) { throw new ChainException("Duplicated contract name: " + contract.Name); } this._contracts[contract.Name] = contract; this._contractContexts[contract.Name] = new NativeExecutionContext(contract); } this.Name = name; this.Nexus = nexus; this.ParentChain = parentChain; this.ParentBlock = parentBlock; if (nexus.CacheSize == -1) { this.Storage = new MemoryStorageContext(); } else { this.Storage = new DiskStorageContext(this.Address, "data", KeyStoreDataSize.Medium); } this.Log = Logger.Init(log); if (parentChain != null) { parentChain._childChains[name] = this; _level = ParentChain.Level + 1; } else { _level = 1; } }