private void RecordTransferHistory(Snapshot snapshot, UInt160 scriptHash, UInt160 from, UInt160 to, BigInteger amount, UInt256 txHash, ref ushort transferIndex) { if (!_shouldTrackHistory) { return; } if (_recordNullAddressHistory || from != UInt160.Zero) { _transfersSent.Add(new Nep5TransferKey(from, snapshot.GetHeader(snapshot.Height).Timestamp, scriptHash, transferIndex), new Nep5Transfer { Amount = amount, UserScriptHash = to, BlockIndex = snapshot.Height, TxHash = txHash }); } if (_recordNullAddressHistory || to != UInt160.Zero) { _transfersReceived.Add(new Nep5TransferKey(to, snapshot.GetHeader(snapshot.Height).Timestamp, scriptHash, transferIndex), new Nep5Transfer { Amount = amount, UserScriptHash = from, BlockIndex = snapshot.Height, TxHash = txHash }); } transferIndex++; }
private bool AddClaims(JArray claimableOutput, ref Fixed8 runningTotal, int maxClaims, 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); if (claimableOutput.Count > maxClaims) { return(false); } } return(true); }
private JObject ProcessGetClaimableSpents(JArray parameters) { UInt160 scriptHash = GetScriptHashFromParam(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, _rpcMaxUnspents, 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) { Header header = snapshot.GetHeader(snapshot.Height); JObject transfer = new JObject(); transfer["from"] = from.ToAddress(); transfer["to"] = to.ToAddress(); transfer["time"] = header.Timestamp; transfer["scripthash"] = scriptHash.ToString(); transfer["amount"] = amount.ToString(); transfer["block"] = snapshot.Height; transfer["txid"] = txHash.ToString(); transfer["transferindex"] = $"{snapshot.Height}.{transferIndex}"; Document a = Document.FromJson(transfer.ToString()); TransfersTable.UpdateItemAsync(a); transferIndex++; // store the asset metadata if this is the first time we're seeing this scripthash if (Assets.Count == 0) { PreloadAssets(); } if (!Assets.Contains(scriptHash.ToString())) { AddAsset(snapshot, scriptHash); } }
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); }
public void OnCommit(Snapshot snapshot) { if (!_shouldPersistBlock) { return; } _userUnspentCoins.Commit(); if (_shouldTrackUnclaimed) { _userSpentUnclaimedCoins.Commit(); } _db.Write(WriteOptions.Default, _writeBatch); }
public void PreProcess(HttpContext context, string method, JArray _params) { if (method == "sendrawtransaction") { JObject res = new JObject(); Transaction tx = Transaction.DeserializeFrom(_params[0].AsString().HexToBytes()); Snapshot snapshot = Blockchain.Singleton.GetSnapshot(); MemoryPool MemPool = Blockchain.Singleton.MemPool; PreVerify(tx, snapshot, MemPool.GetVerifiedTransactions()); } }
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 VerifyWitnesses(IVerifiable verifiable, Snapshot snapshot) { UInt160[] hashes; try { hashes = verifiable.GetScriptHashesForVerifying(snapshot); } catch (InvalidOperationException) { throw new RpcException(-500, "InvalidOperationException getting script hashes for verifying"); } if (hashes.Length != verifiable.Witnesses.Length) { throw new RpcException(-500, "Number of script hashes doesn't match number of witnesses"); } for (int i = 0; i < hashes.Length; i++) { byte[] verification = verifiable.Witnesses[i].VerificationScript; if (verification.Length == 0) { using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitAppCall(hashes[i].ToArray()); verification = sb.ToArray(); } } else { if (hashes[i] != verifiable.Witnesses[i].ScriptHash) { throw new RpcException(-500, $"Script hash {i} didn't match witness hash {i}"); } } using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, Fixed8.Zero)) { engine.LoadScript(verification); engine.LoadScript(verifiable.Witnesses[i].InvocationScript); engine.Execute(); if (engine.State.HasFlag(VMState.FAULT)) { throw new RpcException(-500, "VM returned FAULT during verification trigger (invalid signature data)"); } if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) { throw new RpcException(-500, "VM returned false from verification trigger (bad signature)"); } } } }
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) { _balances.Commit(snapshot.Height, EnumDataTpye.nep5); foreach (var k in _balances.dictionary.Keys) { Nep5State nep5State = new Nep5State() { Address = k.UserScriptHash, AssetHash = k.AssetScriptHash, Balance = _balances.TryGet(k)?.Balance ?? 0, LastUpdatedBlock = _balances.TryGet(k)?.LastUpdatedBlock ?? snapshot.Height }; RecordToMongo(nep5State); } if (_shouldTrackHistory) { _transfersSent.Commit(snapshot.Height, EnumDataTpye.nep5); _transfersReceived.Commit(snapshot.Height, EnumDataTpye.nep5); } _db.Write(WriteOptions.Default, _writeBatch); }
private void AddAsset(Snapshot snapshot, UInt160 scriptHash) { JObject asset = new JObject(); string n = GetAssetString(snapshot, scriptHash, "name"); string s = GetAssetString(snapshot, scriptHash, "symbol"); BigInteger d = GetAssetInteger(snapshot, scriptHash, "decimals"); if (n.Length > 0 && s.Length > 0) { ContractState contract = snapshot.Contracts.TryGet(scriptHash); asset["scripthash"] = scriptHash.ToString(); asset["firstseen"] = snapshot.Height; asset["name"] = n; asset["symbol"] = s; asset["decimals"] = d.ToString(); asset["state"] = contract.ToJson(); } Document b = Document.FromJson(asset.ToString()); AssetsTable.UpdateItemAsync(b); Assets.Add(scriptHash.ToString()); }
/* * private void RefreshContractList(Snapshot snapshot) * { * Contracts = new HashSet<string>(); * int idx = 0; * foreach (KeyValuePair<UInt160, ContractState> dc in snapshot.Contracts.Find()) * { * Console.WriteLine($"Adding contract {idx}: {dc.Key.ToString()}"); * JObject c = dc.Value.ToJson(); * c["idx"] = idx; * * Document b = Document.FromJson(c.ToString()); * ContractsTable.UpdateItemAsync(b); * Contracts.Add(dc.Key.ToString()); * * idx++; * } * ContractCount = idx; * } */ private void AddNewContracts(Snapshot snapshot) { int idx = Contracts.Count; foreach (KeyValuePair <UInt160, ContractState> dc in snapshot.Contracts.Find()) { string cs = dc.Key.ToString(); if (!Contracts.Contains(cs)) { Console.WriteLine($"Adding contract {idx}: {cs}"); JObject c = dc.Value.ToJson(); c["idx"] = idx; c["block"] = snapshot.Height; c["time"] = snapshot.PersistingBlock.Timestamp; Document b = Document.FromJson(c.ToString()); ContractsTable.UpdateItemAsync(b); Contracts.Add(cs); idx++; } } }
private void AddBlock(Snapshot snapshot) { Block block = snapshot.PersistingBlock; ulong idx = block.Index; Console.WriteLine($"Adding block {idx} to DynamoDB table"); ulong blocktime = 0; if (block.Index > 0) { ulong timestamp = block.Timestamp; Header header = snapshot.GetHeader((uint)idx - 1); ulong lasttimestamp = header.Timestamp; blocktime = timestamp - lasttimestamp; } JObject j = block.ToJson(); j["blocktime"] = blocktime; Document d = Document.FromJson(j.ToString()); BlocksTable.UpdateItemAsync(d); }
public BigInteger GetAssetInteger(Snapshot snapshot, UInt160 scripthash, string operation) { byte[] script; using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitAppCall(scripthash, operation, scripthash.ToArray()); script = sb.ToArray(); } ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, extraGAS: 100000000); if (engine.State.HasFlag(VMState.FAULT)) { return(0); } if (engine.ResultStack.Count <= 0) { return(0); } return(engine.ResultStack.Pop().GetBigInteger()); }
private void PreVerify(Transaction tx, Snapshot snapshot, IEnumerable <Transaction> mempool) { if (tx.Size > Transaction.MaxTransactionSize) { throw new RpcException(-500, "Size exceeds maximum transaction size"); } for (int i = 1; i < tx.Inputs.Length; i++) { for (int j = 0; j < i; j++) { if (tx.Inputs[i].PrevHash == tx.Inputs[j].PrevHash && tx.Inputs[i].PrevIndex == tx.Inputs[j].PrevIndex) { throw new RpcException(-500, "Duplicate inputs"); } } } if (mempool.Where(p => p != tx).SelectMany(p => p.Inputs).Intersect(tx.Inputs).Count() > 0) { throw new RpcException(-500, "Transaction already in mempool"); } CheckDoubleSpend(snapshot, tx); foreach (var group in tx.Outputs.GroupBy(p => p.AssetId)) { AssetState asset = snapshot.Assets.TryGet(group.Key); if (asset == null) { throw new RpcException(-500, "Asset value is null"); } if (asset.Expiration <= snapshot.Height + 1 && asset.AssetType != AssetType.GoverningToken && asset.AssetType != AssetType.UtilityToken) { throw new RpcException(-500, "Asset registration has expired"); } foreach (TransactionOutput output in group) { if (output.Value.GetData() % (long)Math.Pow(10, 8 - asset.Precision) != 0) { throw new RpcException(-500, "Invalid precision for asset output"); } } } TransactionResult[] results = tx.GetTransactionResults()?.ToArray(); if (results == null) { throw new RpcException(-500, "No TransactionResults found"); } TransactionResult[] results_destroy = results.Where(p => p.Amount > Fixed8.Zero).ToArray(); if (results_destroy.Length > 1) { throw new RpcException(-500, "TransactionResults destroy length > 1"); } if (results_destroy.Length == 1 && results_destroy[0].AssetId != Blockchain.UtilityToken.Hash) { throw new RpcException(-500, "TransactionResults destroy length equals 1 but asset is not GAS"); } if (tx.SystemFee > Fixed8.Zero && (results_destroy.Length == 0 || results_destroy[0].Amount < tx.SystemFee)) { throw new RpcException(-500, "System fee > 0 but TransactionResults destroy length is zero or amount is less than fee"); } TransactionResult[] results_issue = results.Where(p => p.Amount < Fixed8.Zero).ToArray(); switch (tx.Type) { case TransactionType.MinerTransaction: case TransactionType.ClaimTransaction: if (results_issue.Any(p => p.AssetId != Blockchain.UtilityToken.Hash)) { throw new RpcException(-500, "ClaimTransaction but asset is not GAS"); } break; case TransactionType.IssueTransaction: if (results_issue.Any(p => p.AssetId == Blockchain.UtilityToken.Hash)) { throw new RpcException(-500, "IssueTransaction but asset hash matches GAS"); } break; case TransactionType.InvocationTransaction: InvocationTransaction it = (InvocationTransaction)tx; if (it.Gas.GetData() % 100000000 != 0) { throw new RpcException(-500, "Gas parameter is non-integer"); } break; default: if (results_issue.Length > 0) { throw new RpcException(-500, "No assets issued"); } break; } if (tx.Attributes.Count(p => p.Usage == TransactionAttributeUsage.ECDH02 || p.Usage == TransactionAttributeUsage.ECDH03) > 1) { throw new RpcException(-500, "Multiple occurances of ECDH02/ECDH03 transaction attribute usages"); } VerifyWitnesses(tx, snapshot); }
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 (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); }
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) { // Executions that fault won't modify storage, so we can skip them. if (appExecuted.VMState.HasFlag(VMState.FAULT)) { continue; } foreach (var notifyEventArgs in appExecuted.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(); } ApplicationEngine engine = ApplicationEngine.Run(script, snapshot); 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); }
private void HandleNotification(Snapshot snapshot, Transaction transaction, UInt160 scriptHash, VM.Types.Array stateItems, Dictionary <Nep5BalanceKey, Nep5Balance> nep5BalancesChanged, ref ushort transferIndex) { // 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()); // Only care about transfers if (eventName != "transfer") { return; } if (!(stateItems[1] is VM.Types.ByteArray)) { return; } if (!(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); var fromKey = new Nep5BalanceKey(from, scriptHash); if (!nep5BalancesChanged.ContainsKey(fromKey)) { nep5BalancesChanged.Add(fromKey, new Nep5Balance()); } var toKey = new Nep5BalanceKey(to, scriptHash); if (!nep5BalancesChanged.ContainsKey(toKey)) { nep5BalancesChanged.Add(toKey, new Nep5Balance()); } if (!_shouldTrackHistory) { return; } BigInteger amount = amountItem.GetBigInteger(); _transfersSent.Add(new Nep5TransferKey(from, snapshot.GetHeader(snapshot.Height).Timestamp, scriptHash, transferIndex), new Nep5Transfer { Amount = amount, UserScriptHash = to, BlockIndex = snapshot.Height, TxHash = transaction.Hash }); _transfersReceived.Add(new Nep5TransferKey(to, snapshot.GetHeader(snapshot.Height).Timestamp, scriptHash, transferIndex), new Nep5Transfer { Amount = amount, UserScriptHash = from, BlockIndex = snapshot.Height, TxHash = transaction.Hash }); transferIndex++; }
public void OnPersist(Snapshot snapshot, IReadOnlyList <Blockchain.ApplicationExecuted> applicationExecutedList) { initDatabase(); Dictionary <Nep5BalanceKey, Nep5Balance> nep5BalancesChanged = new Dictionary <Nep5BalanceKey, Nep5Balance>(); ushort transferIndex = 0; foreach (var appExec in applicationExecutedList) { // Add transaction to DynamoDB AddTransaction(appExec.Transaction, snapshot.PersistingBlock.Timestamp, snapshot.Height); // Extract transfer notifications and executed contracts if (!appExec.VMState.HasFlag(VMState.FAULT)) { foreach (var notifyEventArgs in appExec.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); } } // Add application log to DynamoDB JObject json = new JObject(); json["txid"] = appExec.Transaction?.Hash.ToString(); json["trigger"] = appExec.Trigger.ToString(); json["vmstate"] = appExec.VMState.ToString(); json["gas_consumed"] = appExec.GasConsumed.ToString(); try { json["stack"] = appExec.Stack.Select(q => q.ToParameter().ToJson()).ToArray(); } catch (InvalidOperationException) { json["stack"] = "error: recursive reference"; } json["notifications"] = appExec.Notifications.Select(q => { JObject notification = new JObject(); notification["contract"] = q.ScriptHash.ToString(); try { notification["state"] = q.State.ToParameter().ToJson(); } catch (InvalidOperationException) { notification["state"] = "error: recursive reference"; } return(notification); }).ToArray(); string n = json.ToString(); Document a = Document.FromJson(n); if (json["txid"] != null) { ApplicationLogsTable.UpdateItemAsync(a); } if (Contracts.Count == 0) { PreloadContracts(); } int newCount = snapshot.Contracts.Find().Count(); if (newCount != Contracts.Count) { Console.WriteLine($"contract count: snapshot:{newCount} != memory:{Contracts.Count}"); AddNewContracts(snapshot); } } // process all balance changes foreach (var nep5BalancePair in nep5BalancesChanged) { // get guaranteed-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(); } ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, extraGAS: 100000000); 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; JObject balance = new JObject(); balance["address"] = nep5BalancePair.Key.UserScriptHash.ToAddress(); balance["asset"] = nep5BalancePair.Key.AssetScriptHash.ToString(); balance["balance"] = nep5BalancePair.Value.Balance.ToString(); balance["lastupdatedblock"] = nep5BalancePair.Value.LastUpdatedBlock; Document a = Document.FromJson(balance.ToString()); AddressesTable.UpdateItemAsync(a); } }
private bool ProcessBlock(Snapshot snapshot, Block block) { if (block.Transactions.Length <= 1) { _lastPersistedBlock = block.Index; return(false); } ResetBatch(); var transactionsCache = snapshot.Transactions; foreach (Transaction tx in block.Transactions) { ushort outputIndex = 0; foreach (TransactionOutput output in tx.Outputs) { bool isGoverningToken = output.AssetId.Equals(Blockchain.GoverningToken.Hash); if (isGoverningToken || output.AssetId.Equals(Blockchain.UtilityToken.Hash)) { // Add new unspent UTXOs by account script hash. UserSystemAssetCoinOutputs outputs = _userUnspentCoins.GetAndChange( new UserSystemAssetCoinOutputsKey(isGoverningToken, 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 the previous transaction that is now being spent. var outPrev = txPrev.Transaction.Outputs[input.PrevIndex]; bool isGoverningToken = outPrev.AssetId.Equals(Blockchain.GoverningToken.Hash); if (isGoverningToken || outPrev.AssetId.Equals(Blockchain.UtilityToken.Hash)) { // Remove spent UTXOs for unspent outputs by account script hash. var userCoinOutputsKey = new UserSystemAssetCoinOutputsKey(isGoverningToken, 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(true, 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); } } } } // Write the current height into the key of the prefix itself _writeBatch.Put(SystemAssetUnspentCoinsPrefix, block.Index); _lastPersistedBlock = block.Index; return(true); }
public void OnCommit(Snapshot snapshot) { AddBlock(snapshot); }
public IEnumerable <ECPoint> GetValidators(IEnumerable <Transaction> others) { Snapshot snapshot = Clone(); foreach (Transaction tx in others) { foreach (TransactionOutput output in tx.Outputs) { AccountState account = snapshot.Accounts.GetAndChange(output.ScriptHash, () => new AccountState(output.ScriptHash)); if (account.Balances.ContainsKey(output.AssetId)) { account.Balances[output.AssetId] += output.Value; } else { account.Balances[output.AssetId] = output.Value; } if (output.AssetId.Equals(Blockchain.GoverningToken.Hash) && account.Votes.Length > 0) { foreach (ECPoint pubkey in account.Votes) { snapshot.Validators.GetAndChange(pubkey, () => new ValidatorState(pubkey)).Votes += output.Value; } snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] += output.Value; } } foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash)) { Transaction tx_prev = snapshot.GetTransaction(group.Key); foreach (CoinReference input in group) { TransactionOutput out_prev = tx_prev.Outputs[input.PrevIndex]; AccountState account = snapshot.Accounts.GetAndChange(out_prev.ScriptHash); if (out_prev.AssetId.Equals(Blockchain.GoverningToken.Hash)) { if (account.Votes.Length > 0) { foreach (ECPoint pubkey in account.Votes) { ValidatorState validator = snapshot.Validators.GetAndChange(pubkey); validator.Votes -= out_prev.Value; if (!validator.Registered && validator.Votes.Equals(Fixed8.Zero)) { snapshot.Validators.Delete(pubkey); } } snapshot.ValidatorsCount.GetAndChange().Votes[account.Votes.Length - 1] -= out_prev.Value; } } account.Balances[out_prev.AssetId] -= out_prev.Value; } } switch (tx) { #pragma warning disable CS0612 case EnrollmentTransaction tx_enrollment: snapshot.Validators.GetAndChange(tx_enrollment.PublicKey, () => new ValidatorState(tx_enrollment.PublicKey)).Registered = true; break; #pragma warning restore CS0612 case StateTransaction tx_state: foreach (StateDescriptor descriptor in tx_state.Descriptors) { switch (descriptor.Type) { case StateType.Account: Blockchain.ProcessAccountStateDescriptor(descriptor, snapshot); break; case StateType.Validator: Blockchain.ProcessValidatorStateDescriptor(descriptor, snapshot); break; } } break; } } int count = (int)snapshot.ValidatorsCount.Get().Votes.Select((p, i) => new { Count = i, Votes = p }).Where(p => p.Votes > Fixed8.Zero).ToArray().WeightedFilter(0.25, 0.75, p => p.Votes.GetData(), (p, w) => new { p.Count, Weight = w }).WeightedAverage(p => p.Count, p => p.Weight); count = Math.Max(count, Blockchain.StandbyValidators.Length); HashSet <ECPoint> sv = new HashSet <ECPoint>(Blockchain.StandbyValidators); ECPoint[] pubkeys = snapshot.Validators.Find().Select(p => p.Value).Where(p => (p.Registered && p.Votes > Fixed8.Zero) || sv.Contains(p.PublicKey)).OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(count).ToArray(); IEnumerable <ECPoint> result; if (pubkeys.Length == count) { result = pubkeys; } else { HashSet <ECPoint> hashSet = new HashSet <ECPoint>(pubkeys); for (int i = 0; i < Blockchain.StandbyValidators.Length && hashSet.Count < count; i++) { hashSet.Add(Blockchain.StandbyValidators[i]); } result = hashSet; } return(result.OrderBy(p => p)); }