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); }
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); }
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); }
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); } } }
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); }