private void Persist(Block block)
        {
            bool change_cm_merkle_tree = false;

            WriteBatch batch = new WriteBatch();
            DbCache <UInt160, AccountState>     accounts     = new DbCache <UInt160, AccountState>(db, DataEntryPrefix.ST_Account);
            DbCache <UInt256, UnspentCoinState> unspentcoins = new DbCache <UInt256, UnspentCoinState>(db, DataEntryPrefix.ST_Coin);
            DbCache <UInt256, SpentCoinState>   spentcoins   = new DbCache <UInt256, SpentCoinState>(db, DataEntryPrefix.ST_SpentCoin);
            DbCache <ECPoint, ValidatorState>   validators   = new DbCache <ECPoint, ValidatorState>(db, DataEntryPrefix.ST_Validator);
            DbCache <UInt256, AssetState>       assets       = new DbCache <UInt256, AssetState>(db, DataEntryPrefix.ST_Asset);
            DbCache <UInt160, ContractState>    contracts    = new DbCache <UInt160, ContractState>(db, DataEntryPrefix.ST_Contract);
            DbCache <StorageKey, StorageItem>   storages     = new DbCache <StorageKey, StorageItem>(db, DataEntryPrefix.ST_Storage);
            List <NotifyEventArgs> notifications             = new List <NotifyEventArgs>();
            long amount_sysfee = GetSysFeeAmount(block.PrevHash) + (long)block.Transactions.Sum(p => p.SystemFee);

            batch.Put(SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(block.Hash), SliceBuilder.Begin().Add(amount_sysfee).Add(block.Trim()));
            foreach (Transaction tx in block.Transactions)
            {
                batch.Put(SliceBuilder.Begin(DataEntryPrefix.DATA_Transaction).Add(tx.Hash), SliceBuilder.Begin().Add(block.Index).Add(tx.ToArray()));
                unspentcoins.Add(tx.Hash, new UnspentCoinState
                {
                    Items = Enumerable.Repeat(CoinState.Confirmed, tx.Outputs.Length).ToArray()
                });
                foreach (TransactionOutput output in tx.Outputs)
                {
                    AccountState account = 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;
                    }
                }
                foreach (var group in tx.Inputs.GroupBy(p => p.PrevHash))
                {
                    int         height;
                    Transaction tx_prev = GetTransaction(ReadOptions.Default, group.Key, out height);
                    foreach (CoinReference input in group)
                    {
                        unspentcoins.GetAndChange(input.PrevHash).Items[input.PrevIndex] |= CoinState.Spent;
                        if (tx_prev.Outputs[input.PrevIndex].AssetId.Equals(GoverningToken.Hash))
                        {
                            spentcoins.GetAndChange(input.PrevHash, () => new SpentCoinState
                            {
                                TransactionHash   = input.PrevHash,
                                TransactionHeight = (uint)height,
                                Items             = new Dictionary <ushort, uint>()
                            }).Items.Add(input.PrevIndex, block.Index);
                        }
                        accounts.GetAndChange(tx_prev.Outputs[input.PrevIndex].ScriptHash).Balances[tx_prev.Outputs[input.PrevIndex].AssetId] -= tx_prev.Outputs[input.PrevIndex].Value;
                    }
                }
                switch (tx.Type)
                {
                case TransactionType.RingConfidentialTransaction:
                {
                    if (tx is RingConfidentialTransaction ctx)
                    {
                        for (int i = 0; i < ctx.RingCTSig.Count; i++)
                        {
                            // Add the I Commitment to blockchain.
                            for (int j = 0; j < ctx.RingCTSig[i].MG.II.Count; j++)
                            {
                                batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_RingCTCommitment).Add(ctx.RingCTSig[i].MG.II[j]), SliceBuilder.Begin().Add(ctx.RingCTSig[i].AssetID));
                            }
                        }
                    }
                }
                break;

                case TransactionType.AnonymousContractTransaction:
                {
                    if (tx is AnonymousContractTransaction ctx)
                    {
                        for (int jsIndex = 0; jsIndex < ctx.byJoinSplit.Count; jsIndex++)
                        {
                            batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_Nullifier).Add(ctx.Nullifiers(jsIndex)[0]), SliceBuilder.Begin().Add(ctx.Asset_ID(jsIndex)));
                            batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_Nullifier).Add(ctx.Nullifiers(jsIndex)[1]), SliceBuilder.Begin().Add(ctx.Asset_ID(jsIndex)));

                            SnarkDllApi.AppendCommitment(gCmMerkleTree, ctx.Commitments(jsIndex)[0].ToArray());
                            SnarkDllApi.AppendCommitment(gCmMerkleTree, ctx.Commitments(jsIndex)[1].ToArray());
                        }

                        change_cm_merkle_tree = true;
                    }
                }
                break;

                case TransactionType.RegisterTransaction:
                {
#pragma warning disable CS0612
                    RegisterTransaction rtx = (RegisterTransaction)tx;
                    assets.Add(tx.Hash, new AssetState
                        {
                            AssetId    = rtx.Hash,
                            AssetType  = rtx.AssetType,
                            Name       = rtx.Name,
                            Amount     = rtx.Amount,
                            Available  = Fixed8.Zero,
                            Precision  = rtx.Precision,
                            Fee        = rtx.T_Fee,
                            FeeMin     = rtx.T_Fee_Min,
                            FeeMax     = rtx.T_Fee_Max,
                            AFee       = rtx.A_Fee,
                            FeeAddress = new UInt160(),
                            Owner      = rtx.Owner,
                            Admin      = rtx.Admin,
                            Issuer     = rtx.Admin,
                            Expiration = block.Index + 2 * 2000000,
                            IsFrozen   = false
                        });
#pragma warning restore CS0612
                }
                break;

                case TransactionType.IssueTransaction:
                    foreach (TransactionResult result in tx.GetTransactionResults().Where(p => p.Amount < Fixed8.Zero))
                    {
                        assets.GetAndChange(result.AssetId).Available -= result.Amount;
                    }
                    break;

                case TransactionType.ClaimTransaction:
                    foreach (CoinReference input in ((ClaimTransaction)tx).Claims)
                    {
                        if (spentcoins.TryGet(input.PrevHash)?.Items.Remove(input.PrevIndex) == true)
                        {
                            spentcoins.GetAndChange(input.PrevHash);
                        }
                    }
                    break;

                case TransactionType.EnrollmentTransaction:
                {
#pragma warning disable CS0612
                    EnrollmentTransaction enroll_tx = (EnrollmentTransaction)tx;
                    validators.GetOrAdd(enroll_tx.PublicKey, () => new ValidatorState
                        {
                            PublicKey = enroll_tx.PublicKey
                        });
#pragma warning restore CS0612
                }
                break;

                case TransactionType.PublishTransaction:
                {
#pragma warning disable CS0612
                    PublishTransaction publish_tx = (PublishTransaction)tx;
                    contracts.GetOrAdd(publish_tx.ScriptHash, () => new ContractState
                        {
                            Script        = publish_tx.Script,
                            ParameterList = publish_tx.ParameterList,
                            ReturnType    = publish_tx.ReturnType,
                            HasStorage    = publish_tx.NeedStorage,
                            Name          = publish_tx.Name,
                            CodeVersion   = publish_tx.CodeVersion,
                            Author        = publish_tx.Author,
                            Email         = publish_tx.Email,
                            Description   = publish_tx.Description
                        });
#pragma warning restore CS0612
                }
                break;

                case TransactionType.InvocationTransaction:
                {
                    InvocationTransaction itx          = (InvocationTransaction)tx;
                    CachedScriptTable     script_table = new CachedScriptTable(contracts);
                    StateMachine          service      = new StateMachine(accounts, validators, assets, contracts, storages);
                    ApplicationEngine     engine       = new ApplicationEngine(TriggerType.Application, itx, script_table, service, itx.Gas);
                    engine.LoadScript(itx.Script, false);
                    if (engine.Execute())
                    {
                        service.Commit();
                        notifications.AddRange(service.Notifications);
                    }
                }
                break;
                }
            }

            if (change_cm_merkle_tree == true)
            {
                int[] outLen = new int[1];
                outLen[0] = 0;
                IntPtr ptrTree = SnarkDllApi.GetCMTreeInBinary(gCmMerkleTree, outLen);

                byte[] byTree = new byte[outLen[0]];
                System.Runtime.InteropServices.Marshal.Copy(ptrTree, byTree, 0, outLen[0]);

                IntPtr ptrRt1 = SnarkDllApi.GetCMRoot(gCmMerkleTree);

                byte[] by_rt = new byte[32];
                System.Runtime.InteropServices.Marshal.Copy(ptrRt1, by_rt, 0, 32);

                UInt256 current_rt = new UInt256(by_rt);

                db.Put(WriteOptions.Default, SliceBuilder.Begin(DataEntryPrefix.AM_CmMerkleTree), byTree);

                mCmMerkleRoots.Add(current_rt);

                while ((int)mCmMerkleRoots.Count - 5 >= stored_cm_root_count)
                {
                    using (MemoryStream ms = new MemoryStream())
                        using (BinaryWriter w = new BinaryWriter(ms))
                        {
                            w.Write(mCmMerkleRoots.Skip((int)stored_cm_root_count).Take(5).ToArray());
                            w.Flush();
                            batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_MerkleRoot).Add(stored_cm_root_count), ms.ToArray());
                        }
                    stored_cm_root_count += 5;
                }

                if (mCmMerkleRoots.Count > stored_cm_root_count)
                {
                    using (MemoryStream ms = new MemoryStream())
                        using (BinaryWriter w = new BinaryWriter(ms))
                        {
                            w.Write(mCmMerkleRoots.Skip((int)stored_cm_root_count).ToArray());
                            w.Flush();
                            batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_MerkleRoot).Add(stored_cm_root_count), ms.ToArray());
                        }
                }
            }

            if (notifications.Count > 0)
            {
                OnNotify(block, notifications.ToArray());
            }
            accounts.DeleteWhere((k, v) => !v.IsFrozen && v.Votes.Length == 0 && v.Balances.All(p => p.Value <= Fixed8.Zero));
            accounts.Commit(batch);
            unspentcoins.DeleteWhere((k, v) => v.Items.All(p => p.HasFlag(CoinState.Spent)));
            unspentcoins.Commit(batch);
            spentcoins.DeleteWhere((k, v) => v.Items.Count == 0);
            spentcoins.Commit(batch);
            validators.Commit(batch);
            assets.Commit(batch);
            contracts.Commit(batch);
            storages.Commit(batch);
            batch.Put(SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentBlock), SliceBuilder.Begin().Add(block.Hash).Add(block.Index));
            db.Write(WriteOptions.Default, batch);
            current_block_height = block.Index;
        }
        public LevelDBBlockchain(string path)
        {
            header_index.Add(GenesisBlock.Hash);
            Version version;
            Slice   value;

            db = DB.Open(path, new Options {
                CreateIfMissing = true
            });

            gCmMerkleTree = SnarkDllApi.CmMerkleTree_Create();

            Slice cmValue;

            if (db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.AM_CmMerkleTree), out cmValue))
            {
                byCMMerkleTree = cmValue.ToArray();
                SnarkDllApi.SetCMTreeFromBinary(gCmMerkleTree, byCMMerkleTree, byCMMerkleTree.Length);
            }

            if (db.TryGet(ReadOptions.Default, SliceBuilder.Begin(DataEntryPrefix.SYS_Version), out value) &&
                Version.TryParse(value.ToString(), out version) && version >= Version.Parse("1.1"))
            {
                ReadOptions options = new ReadOptions {
                    FillCache = false
                };
                value = db.Get(options, SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentBlock));
                UInt256 current_header_hash = new UInt256(value.ToArray().Take(32).ToArray());
                this.current_block_height = value.ToArray().ToUInt32(32);
                uint current_header_height = current_block_height;
                if (db.TryGet(options, SliceBuilder.Begin(DataEntryPrefix.SYS_CurrentHeader), out value))
                {
                    current_header_hash   = new UInt256(value.ToArray().Take(32).ToArray());
                    current_header_height = value.ToArray().ToUInt32(32);
                }
                foreach (UInt256 hash in db.Find(options, SliceBuilder.Begin(DataEntryPrefix.IX_HeaderHashList), (k, v) =>
                {
                    using (MemoryStream ms = new MemoryStream(v.ToArray(), false))
                        using (BinaryReader r = new BinaryReader(ms))
                        {
                            return(new
                            {
                                Index = k.ToArray().ToUInt32(1),
                                Hashes = r.ReadSerializableArray <UInt256>()
                            });
                        }
                }).OrderBy(p => p.Index).SelectMany(p => p.Hashes).ToArray())
                {
                    if (!hash.Equals(GenesisBlock.Hash))
                    {
                        header_index.Add(hash);
                    }
                    stored_header_count++;
                }
                if (stored_header_count == 0)
                {
                    Header[] headers = db.Find(
                        options, SliceBuilder.Begin(DataEntryPrefix.DATA_Block), (k, v) =>
                        Header.FromTrimmedData(v.ToArray(), sizeof(long))
                        ).OrderBy(p => p.Index).ToArray();
                    for (int i = 1; i < headers.Length; i++)
                    {
                        header_index.Add(headers[i].Hash);
                    }
                }
                else if (current_header_height >= stored_header_count)
                {
                    for (UInt256 hash = current_header_hash; hash != header_index[(int)stored_header_count - 1];)
                    {
                        Header header = Header.FromTrimmedData(db.Get(options, SliceBuilder.Begin(DataEntryPrefix.DATA_Block).Add(hash)).ToArray(), sizeof(long));
                        header_index.Insert((int)stored_header_count, hash);
                        hash = header.PrevHash;
                    }
                }

                foreach (UInt256 hash in db.Find(options, SliceBuilder.Begin(DataEntryPrefix.ST_MerkleRoot), (k, v) =>
                {
                    using (MemoryStream ms = new MemoryStream(v.ToArray(), false))
                        using (BinaryReader r = new BinaryReader(ms))
                        {
                            return(new
                            {
                                Index = k.ToArray().ToUInt32(1),
                                Hashes = r.ReadSerializableArray <UInt256>()
                            });
                        }
                }).OrderBy(p => p.Index).SelectMany(p => p.Hashes).ToArray())
                {
                    mCmMerkleRoots.Add(hash);
                    stored_cm_root_count = (uint)(mCmMerkleRoots.Count / 5) * 5;
                }
            }
            else
            {
                WriteBatch  batch   = new WriteBatch();
                ReadOptions options = new ReadOptions {
                    FillCache = false
                };
                using (Iterator it = db.NewIterator(options))
                {
                    for (it.SeekToFirst(); it.Valid(); it.Next())
                    {
                        batch.Delete(it.Key());
                    }
                }
                db.Write(WriteOptions.Default, batch);
                Persist(GenesisBlock);
                db.Put(WriteOptions.Default, SliceBuilder.Begin(DataEntryPrefix.SYS_Version), GetType().GetTypeInfo().Assembly.GetName().Version.ToString());
            }
            thread_persistence      = new Thread(PersistBlocks);
            thread_persistence.Name = "LevelDBBlockchain.PersistBlocks";
            thread_persistence.Start();
        }