private bool AddClaims(JArray claimableOutput, ref Fixed8 runningTotal,
                               Snapshot snapshot, DataCache <UInt256, SpentCoinState> storeSpentCoins,
                               KeyValuePair <UserSystemAssetCoinOutputsKey, UserSystemAssetCoinOutputs> claimableInTx)
        {
            foreach (var claimTransaction in claimableInTx.Value.AmountByTxIndex)
            {
                var utxo = new JObject();
                var txId = claimableInTx.Key.TxHash.ToString().Substring(2);
                utxo["txid"] = txId;
                utxo["n"]    = claimTransaction.Key;
                var spentCoinState = storeSpentCoins.TryGet(claimableInTx.Key.TxHash);
                var startHeight    = spentCoinState.TransactionHeight;
                var endHeight      = spentCoinState.Items[claimTransaction.Key];
                CalculateClaimable(snapshot, claimTransaction.Value, startHeight, endHeight, out var generated,
                                   out var sysFee);
                var unclaimed = generated + sysFee;
                utxo["value"]        = (double)(decimal)claimTransaction.Value;
                utxo["start_height"] = startHeight;
                utxo["end_height"]   = endHeight;
                utxo["generated"]    = (double)(decimal)generated;
                utxo["sys_fee"]      = (double)(decimal)sysFee;
                utxo["unclaimed"]    = (double)(decimal)unclaimed;
                runningTotal        += unclaimed;
                claimableOutput.Add(utxo);
            }

            return(true);
        }
        private JObject ProcessGetClaimableSpents(JArray parameters)
        {
            // UInt160 scriptHash =  GetScriptHashFromParam(parameters[0].AsString());
            UInt256 scriptHash = UInt256.Parse(parameters[0].AsString());
            var     dbCache    = new DbCache <UserSystemAssetCoinOutputsKey, UserSystemAssetCoinOutputs>(
                _db, null, null, SystemAssetSpentUnclaimedCoinsPrefix);

            JObject json      = new JObject();
            JArray  claimable = new JArray();

            json["claimable"] = claimable;
            //json["address"] = scriptHash.ToAddress();

            Fixed8 totalUnclaimed = Fixed8.Zero;

            using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var    storeSpentCoins = snapshot.SpentCoins;
                byte[] prefix = new[] { (byte)1 }.Concat(scriptHash.ToArray()).ToArray();
                foreach (var claimableInTx in dbCache.Find(prefix))
                {
                    if (!AddClaims(claimable, ref totalUnclaimed, snapshot, storeSpentCoins,
                                   claimableInTx))
                    {
                        break;
                    }
                }
            }
            json["unclaimed"] = (double)(decimal)totalUnclaimed;
            return(json);
        }
예제 #3
0
        private void RecordTransferHistory(Snapshot snapshot, UInt160 scriptHash, UInt160 from, UInt160 to, BigInteger amount, UInt256 txHash, ref ushort transferIndex)
        {
            if (!_shouldTrackHistory)
            {
                return;
            }
            Header header = snapshot.GetHeader(snapshot.Height);

            if (_recordNullAddressHistory || from != UInt160.Zero)
            {
                _transfersSent.Add(new Nep5TransferKey(from, header.Timestamp, scriptHash, transferIndex),
                                   new Nep5Transfer
                {
                    Amount         = amount,
                    UserScriptHash = to,
                    BlockIndex     = snapshot.Height,
                    TxHash         = txHash
                });
            }

            if (_recordNullAddressHistory || to != UInt160.Zero)
            {
                _transfersReceived.Add(new Nep5TransferKey(to, header.Timestamp, scriptHash, transferIndex),
                                       new Nep5Transfer
                {
                    Amount         = amount,
                    UserScriptHash = from,
                    BlockIndex     = snapshot.Height,
                    TxHash         = txHash
                });
            }
            transferIndex++;
        }
        private void CalculateClaimable(Snapshot snapshot, Fixed8 value, uint startHeight, uint endHeight, out Fixed8 generated, out Fixed8 sysFee)
        {
            uint amount = 0;
            uint ustart = startHeight / Blockchain.DecrementInterval;

            if (ustart < Blockchain.GenerationAmount.Length)
            {
                uint istart = startHeight % Blockchain.DecrementInterval;
                uint uend   = endHeight / Blockchain.DecrementInterval;
                uint iend   = endHeight % Blockchain.DecrementInterval;
                if (uend >= Blockchain.GenerationAmount.Length)
                {
                    uend = (uint)Blockchain.GenerationAmount.Length;
                    iend = 0;
                }
                if (iend == 0)
                {
                    uend--;
                    iend = Blockchain.DecrementInterval;
                }
                while (ustart < uend)
                {
                    amount += (Blockchain.DecrementInterval - istart) * Blockchain.GenerationAmount[ustart];
                    ustart++;
                    istart = 0;
                }
                amount += (iend - istart) * Blockchain.GenerationAmount[ustart];
            }

            Fixed8 fractionalShare = value / 100000000;

            generated = fractionalShare * amount;
            sysFee    = fractionalShare * (GetSysFeeAmountForHeight(snapshot.Blocks, endHeight - 1) -
                                           (startHeight == 0 ? 0 : GetSysFeeAmountForHeight(snapshot.Blocks, startHeight - 1)));
        }
        public void OnPersist(Snapshot snapshot, IReadOnlyList <Blockchain.ApplicationExecuted> applicationExecutedList)
        {
            if (snapshot.PersistingBlock.Index > _lastPersistedBlock + 1)
            {
                ProcessSkippedBlocks(snapshot);
            }

            _shouldPersistBlock = ProcessBlock(snapshot, snapshot.PersistingBlock);
        }
예제 #6
0
        public void OnCommit(Snapshot snapshot)
        {
            _balances.Commit();
            if (_shouldTrackHistory)
            {
                _transfersSent.Commit();
                _transfersReceived.Commit();
            }

            _db.Write(WriteOptions.Default, _writeBatch);
        }
        private JObject ProcessGetUnclaimed(JArray parameters)
        {
            UInt160 scriptHash = GetScriptHashFromParam(parameters[0].AsString());
            JObject json       = new JObject();

            Fixed8 available   = Fixed8.Zero;
            Fixed8 unavailable = Fixed8.Zero;
            var    spentsCache = new DbCache <UserSystemAssetCoinOutputsKey, UserSystemAssetCoinOutputs>(
                _db, null, null, SystemAssetSpentUnclaimedCoinsPrefix);
            var unspentsCache = new DbCache <UserSystemAssetCoinOutputsKey, UserSystemAssetCoinOutputs>(
                _db, null, null, SystemAssetUnspentCoinsPrefix);

            using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var    storeSpentCoins = snapshot.SpentCoins;
                byte[] prefix = new[] { (byte)1 }.Concat(scriptHash.ToArray()).ToArray();
                foreach (var claimableInTx in spentsCache.Find(prefix))
                {
                    var spentCoinState = storeSpentCoins.TryGet(claimableInTx.Key.TxHash);
                    foreach (var claimTxIndex in claimableInTx.Value.AmountByTxIndex)
                    {
                        var startHeight = spentCoinState.TransactionHeight;
                        var endHeight   = spentCoinState.Items[claimTxIndex.Key];
                        CalculateClaimable(snapshot, claimTxIndex.Value, startHeight, endHeight, out var generated,
                                           out var sysFee);
                        available += generated + sysFee;
                    }
                }

                var transactionsCache = snapshot.Transactions;
                foreach (var claimableInTx in unspentsCache.Find(prefix))
                {
                    var transaction = transactionsCache.TryGet(claimableInTx.Key.TxHash);

                    foreach (var claimTxIndex in claimableInTx.Value.AmountByTxIndex)
                    {
                        var startHeight = transaction.BlockIndex;
                        var endHeight   = Blockchain.Singleton.Height;
                        CalculateClaimable(snapshot, claimTxIndex.Value, startHeight, endHeight,
                                           out var generated,
                                           out var sysFee);
                        unavailable += generated + sysFee;
                    }
                }
            }

            json["available"]   = (double)(decimal)available;
            json["unavailable"] = (double)(decimal)unavailable;
            json["unclaimed"]   = (double)(decimal)(available + unavailable);
            return(json);
        }
        private void ProcessSkippedBlocks(Snapshot snapshot)
        {
            for (uint blockIndex = _lastPersistedBlock + 1; blockIndex < snapshot.PersistingBlock.Index; blockIndex++)
            {
                var skippedBlock = Blockchain.Singleton.Store.GetBlock(blockIndex);
                if (skippedBlock.Transactions.Length <= 1)
                {
                    _lastPersistedBlock = skippedBlock.Index;
                    continue;
                }

                _shouldPersistBlock = ProcessBlock(snapshot, skippedBlock);
                OnCommit(snapshot);
            }
        }
 public void OnCommit(Snapshot snapshot)
 {
     if (!_shouldPersistBlock)
     {
         return;
     }
     _userUnspentCoins.Commit();
     if (_shouldTrackUnclaimed)
     {
         _userSpentUnclaimedCoins.Commit();
     }
     if (_shouldTrackHistory)
     {
         _transfersSent.Commit();
         _transfersReceived.Commit();
     }
     _db.Write(WriteOptions.Default, _writeBatch);
 }
        private JArray ProcessGetUnclaimedTransactions(UInt160 scriptHash)
        {
            JArray json = new JArray();

            var unspentsCache = new DbCache <UserSystemAssetCoinOutputsKey, UserSystemAssetCoinOutputs>(
                _db, null, null, SystemAssetUnspentCoinsPrefix);

            using (Snapshot snapshot = Blockchain.Singleton.GetSnapshot())
            {
                byte[] prefix = new[] { (byte)1 }.Concat(scriptHash.ToArray()).ToArray();

                var transactionsCache = snapshot.Transactions;
                foreach (var claimableInTx in unspentsCache.Find(prefix))
                {
                    var transaction = transactionsCache.TryGet(claimableInTx.Key.TxHash);
                    var obj         = new JObject();
                    obj["txid"] = transaction.Transaction.Hash.ToString();
                    obj["vout"] = transaction.Transaction.Outputs.Select((p, i) => p.ToJson((ushort)i)).Count();
                    json.Add(obj);
                }
            }
            return(json);
        }
        private bool ProcessBlock(Snapshot snapshot, Block block)
        {
            if (block.Transactions.Length <= 1)
            {
                _lastPersistedBlock = block.Index;
                return(false);
            }

            ResetBatch();

            var r = snapshot.Assets.Find();

            var transactionsCache = snapshot.Transactions;

            foreach (Transaction tx in block.Transactions)
            {
                ushort outputIndex = 0;
                foreach (TransactionOutput output in tx.Outputs)
                {
                    byte idToken          = GetTokenID(r, output.AssetId, transactionsCache);
                    bool isGoverningToken = output.AssetId.Equals(Blockchain.GoverningToken.Hash);
                    //  if (isGoverningToken || output.AssetId.Equals(Blockchain.UtilityToken.Hash))
                    if (idToken != 255)
                    {
                        // Add new unspent UTXOs by account script hash.
                        UserSystemAssetCoinOutputs outputs = _userUnspentCoins.GetAndChange(
                            new UserSystemAssetCoinOutputsKey(idToken, output.ScriptHash, tx.Hash),
                            () => new UserSystemAssetCoinOutputs());
                        outputs.AddTxIndex(outputIndex, output.Value);
                    }
                    outputIndex++;
                }

                // Iterate all input Transactions by grouping by common input hashes.
                foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash))
                {
                    TransactionState txPrev = transactionsCache[group.Key];
                    // For each input being spent by this transaction.
                    foreach (CoinReference input in group)
                    {
                        // Get the output from the previous transaction that is now being spent.
                        var outPrev = txPrev.Transaction.Outputs[input.PrevIndex];

                        byte idToken          = GetTokenID(r, outPrev.AssetId, transactionsCache);
                        bool isGoverningToken = outPrev.AssetId.Equals(Blockchain.GoverningToken.Hash);
                        // if (isGoverningToken || outPrev.AssetId.Equals(Blockchain.UtilityToken.Hash))
                        if (idToken != 255)
                        {
                            // Remove spent UTXOs for unspent outputs by account script hash.
                            var userCoinOutputsKey =
                                new UserSystemAssetCoinOutputsKey(idToken, outPrev.ScriptHash, input.PrevHash);
                            UserSystemAssetCoinOutputs outputs = _userUnspentCoins.GetAndChange(
                                userCoinOutputsKey, () => new UserSystemAssetCoinOutputs());
                            outputs.RemoveTxIndex(input.PrevIndex);
                            if (outputs.AmountByTxIndex.Count == 0)
                            {
                                _userUnspentCoins.Delete(userCoinOutputsKey);
                            }

                            if (_shouldTrackUnclaimed && isGoverningToken)
                            {
                                UserSystemAssetCoinOutputs spentUnclaimedOutputs = _userSpentUnclaimedCoins.GetAndChange(
                                    userCoinOutputsKey, () => new UserSystemAssetCoinOutputs());
                                spentUnclaimedOutputs.AddTxIndex(input.PrevIndex, outPrev.Value);
                            }
                        }
                    }
                }

                if (_shouldTrackUnclaimed && tx is ClaimTransaction claimTransaction)
                {
                    foreach (CoinReference input in claimTransaction.Claims)
                    {
                        TransactionState txPrev = transactionsCache[input.PrevHash];
                        var outPrev             = txPrev.Transaction.Outputs[input.PrevIndex];

                        var claimedCoinKey =
                            new UserSystemAssetCoinOutputsKey(1, outPrev.ScriptHash, input.PrevHash);
                        UserSystemAssetCoinOutputs spentUnclaimedOutputs = _userSpentUnclaimedCoins.GetAndChange(
                            claimedCoinKey, () => new UserSystemAssetCoinOutputs());
                        spentUnclaimedOutputs.RemoveTxIndex(input.PrevIndex);
                        if (spentUnclaimedOutputs.AmountByTxIndex.Count == 0)
                        {
                            _userSpentUnclaimedCoins.Delete(claimedCoinKey);
                        }

                        if (snapshot.SpentCoins.TryGet(input.PrevHash)?.Items.Remove(input.PrevIndex) == true)
                        {
                            snapshot.SpentCoins.GetAndChange(input.PrevHash);
                        }
                    }
                }

                if (_shouldTrackHistory)
                {
                    // treat each input as a "sent"
                    for (int i = 0; i < tx.Inputs.Length; i++)
                    {
                        var input = tx.Inputs[i];
                        // find the previous tx
                        TransactionState  prevTx  = transactionsCache[input.PrevHash];
                        TransactionOutput prevOut = prevTx.Transaction.Outputs[input.PrevIndex];
                        if (prevOut.AssetId.Equals(Blockchain.GoverningToken.Hash) || prevOut.AssetId.Equals(Blockchain.UtilityToken.Hash))
                        {
                            _transfersSent.Add(new UserSystemAssetTransferKey(prevOut.ScriptHash, prevOut.AssetId, block.Timestamp, tx.Hash, (ushort)i),
                                               new UserSystemAssetTransfer
                            {
                                BlockIndex = block.Index,
                                Amount     = prevOut.Value
                            });
                        }
                    }

                    // treat each output as a "received"
                    for (int i = 0; i < tx.Outputs.Length; i++)
                    {
                        var output = tx.Outputs[i];
                        if (output.AssetId.Equals(Blockchain.GoverningToken.Hash) || output.AssetId.Equals(Blockchain.UtilityToken.Hash))
                        {
                            _transfersReceived.Add(new UserSystemAssetTransferKey(output.ScriptHash, output.AssetId, block.Timestamp, tx.Hash, (ushort)i),
                                                   new UserSystemAssetTransfer
                            {
                                BlockIndex = block.Index,
                                Amount     = output.Value
                            });
                        }
                    }
                }
            }

            // Write the current height into the key of the prefix itself
            _writeBatch.Put(SystemAssetUnspentCoinsPrefix, block.Index);
            _lastPersistedBlock = block.Index;
            return(true);
        }
예제 #12
0
        public void OnPersist(Snapshot snapshot, IReadOnlyList <Blockchain.ApplicationExecuted> applicationExecutedList)
        {
            // Start freshly with a new DBCache for each block.
            ResetBatch();
            Dictionary <Nep5BalanceKey, Nep5Balance> nep5BalancesChanged = new Dictionary <Nep5BalanceKey, Nep5Balance>();

            ushort transferIndex = 0;

            foreach (Blockchain.ApplicationExecuted appExecuted in applicationExecutedList)
            {
                foreach (var executionResults in appExecuted.ExecutionResults)
                {
                    // Executions that fault won't modify storage, so we can skip them.
                    if (executionResults.VMState.HasFlag(VMState.FAULT))
                    {
                        continue;
                    }
                    foreach (var notifyEventArgs in executionResults.Notifications)
                    {
                        if (!(notifyEventArgs?.State is VM.Types.Array stateItems) || stateItems.Count == 0 ||
                            !(notifyEventArgs.ScriptContainer is Transaction transaction))
                        {
                            continue;
                        }
                        HandleNotification(snapshot, transaction, notifyEventArgs.ScriptHash, stateItems,
                                           nep5BalancesChanged, ref transferIndex);
                    }
                }
            }

            foreach (var nep5BalancePair in nep5BalancesChanged)
            {
                // get guarantee accurate balances by calling balanceOf for keys that changed.
                byte[] script;
                using (ScriptBuilder sb = new ScriptBuilder())
                {
                    sb.EmitAppCall(nep5BalancePair.Key.AssetScriptHash, "balanceOf",
                                   nep5BalancePair.Key.UserScriptHash.ToArray());
                    script = sb.ToArray();
                }

                using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.Clone(), extraGAS: maxGas))
                {
                    if (engine.State.HasFlag(VMState.FAULT))
                    {
                        continue;
                    }
                    if (engine.ResultStack.Count <= 0)
                    {
                        continue;
                    }
                    nep5BalancePair.Value.Balance = engine.ResultStack.Pop().GetBigInteger();
                }

                nep5BalancePair.Value.LastUpdatedBlock = snapshot.Height;
                if (nep5BalancePair.Value.Balance == 0)
                {
                    _balances.Delete(nep5BalancePair.Key);
                    continue;
                }
                var itemToChange = _balances.GetAndChange(nep5BalancePair.Key, () => nep5BalancePair.Value);
                if (itemToChange != nep5BalancePair.Value)
                {
                    itemToChange.FromReplica(nep5BalancePair.Value);
                }
            }
        }
예제 #13
0
        private void HandleNotification(Snapshot snapshot, Transaction transaction, UInt160 scriptHash,
                                        VM.Types.Array stateItems,
                                        Dictionary <Nep5BalanceKey, Nep5Balance> nep5BalancesChanged, ref ushort transferIndex)
        {
            if (stateItems.Count == 0)
            {
                return;
            }
            // Event name should be encoded as a byte array.
            if (!(stateItems[0] is VM.Types.ByteArray))
            {
                return;
            }
            var eventName = Encoding.UTF8.GetString(stateItems[0].GetByteArray());

            if (_shouldTrackNonStandardMintTokensEvent && eventName == "mintTokens")
            {
                if (stateItems.Count < 4)
                {
                    return;
                }
                // This is not an official standard but at least one token uses it, and so it is needed for proper
                // balance tracking to support all tokens in use.
                if (!(stateItems[2] is VM.Types.ByteArray))
                {
                    return;
                }
                byte[] mintToBytes = stateItems[2].GetByteArray();
                if (mintToBytes.Length != 20)
                {
                    return;
                }
                var mintTo = new UInt160(mintToBytes);

                var mintAmountItem = stateItems[3];
                if (!(mintAmountItem is VM.Types.ByteArray || mintAmountItem is VM.Types.Integer))
                {
                    return;
                }

                var toKey = new Nep5BalanceKey(mintTo, scriptHash);
                if (!nep5BalancesChanged.ContainsKey(toKey))
                {
                    nep5BalancesChanged.Add(toKey, new Nep5Balance());
                }
                RecordTransferHistory(snapshot, scriptHash, UInt160.Zero, mintTo, mintAmountItem.GetBigInteger(), transaction.Hash, ref transferIndex);
                return;
            }
            if (eventName != "transfer")
            {
                return;
            }
            if (stateItems.Count < 4)
            {
                return;
            }

            if (!(stateItems[1] is null) && !(stateItems[1] is VM.Types.ByteArray))
            {
                return;
            }
            if (!(stateItems[2] is null) && !(stateItems[2] is VM.Types.ByteArray))
            {
                return;
            }
            var amountItem = stateItems[3];

            if (!(amountItem is VM.Types.ByteArray || amountItem is VM.Types.Integer))
            {
                return;
            }
            byte[] fromBytes = stateItems[1]?.GetByteArray();
            if (fromBytes?.Length != 20)
            {
                fromBytes = null;
            }
            byte[] toBytes = stateItems[2]?.GetByteArray();
            if (toBytes?.Length != 20)
            {
                toBytes = null;
            }
            if (fromBytes == null && toBytes == null)
            {
                return;
            }
            var from = new UInt160(fromBytes);
            var to   = new UInt160(toBytes);

            if (fromBytes != null)
            {
                var fromKey = new Nep5BalanceKey(from, scriptHash);
                if (!nep5BalancesChanged.ContainsKey(fromKey))
                {
                    nep5BalancesChanged.Add(fromKey, new Nep5Balance());
                }
            }

            if (toBytes != null)
            {
                var toKey = new Nep5BalanceKey(to, scriptHash);
                if (!nep5BalancesChanged.ContainsKey(toKey))
                {
                    nep5BalancesChanged.Add(toKey, new Nep5Balance());
                }
            }
            RecordTransferHistory(snapshot, scriptHash, from, to, amountItem.GetBigInteger(), transaction.Hash, ref transferIndex);
        }