Exemple #1
0
        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);
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        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);
        }
Exemple #4
0
 public OracleReader(Nexus nexus)
 {
     this.Nexus = nexus;
 }
Exemple #5
0
        public BigInteger GetTokenBalance(StorageContext storage, string symbol, Address address)
        {
            var token = Nexus.GetTokenInfo(storage, symbol);

            return(GetTokenBalance(storage, token, address));
        }
Exemple #6
0
        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);
        }
Exemple #7
0
        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);
        }
Exemple #8
0
 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);
        }
Exemple #10
0
        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));
                }
            }
        }
Exemple #11
0
        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);
        }
Exemple #12
0
        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);
        }
Exemple #13
0
        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);
        }
Exemple #14
0
        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);
        }
Exemple #15
0
        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;
            }
        }