<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Niki</title>
        <link>https://virgool.io/feed/@hootan09</link>
        <description>کانال تلگرامی https://t.me/pcbooks جهت خواندن کتاب های تخصصی کامپیوتر</description>
        <language>fa</language>
        <pubDate>2026-04-14 22:19:28</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/38385/avatar/ik0ik5.png?height=120&amp;width=120</url>
            <title>Niki</title>
            <link>https://virgool.io/@hootan09</link>
        </image>

                    <item>
                <title>20 سوال و پاسخ برتر مصاحبه در حوزه یادگیری عمیق</title>
                <link>https://virgool.io/@hootan09/20-%D8%B3%D9%88%D8%A7%D9%84-%D9%88-%D9%BE%D8%A7%D8%B3%D8%AE-%D8%A8%D8%B1%D8%AA%D8%B1-%D9%85%D8%B5%D8%A7%D8%AD%D8%A8%D9%87-%D8%AF%D8%B1-%D8%AD%D9%88%D8%B2%D9%87-%DB%8C%D8%A7%D8%AF%DA%AF%DB%8C%D8%B1%DB%8C-%D8%B9%D9%85%DB%8C%D9%82-zohpg4ltmad1</link>
                <description>دنبال راهنمایی برای رسیدن به شغل رویایی‌تان و بهبود مهارت‌های یادگیری عمیق خود هستید؟ دیگر لازم نیست دنبال چیزی بگردید. این مقاله پاسخ ۲۰ سوال رایج مصاحبه برای موقعیت‌های یادگیری عمیق را ارائه می‌دهد و به شما کمک می‌کند تا با کمی تمرین در مصاحبه‌ها عالی عمل کنید.سوالات عمومی مصاحبه یادگیری عمیقسوالات عمومی یادگیری عمیق معمولاً مربوط به درک این حوزه، وجه تمایز آن از سایر حوزه‌های هوش مصنوعی، ارتباط مسائل دنیای واقعی با راه‌حل‌های یادگیری عمیق و درک چالش‌ها و محدودیت‌های آنها است.۱. یادگیری عمیق چیست؟یادگیری عمیق زیرمجموعه‌ای از یادگیری ماشین و هوش مصنوعی به طور کلی است. این شامل آموزش مدل‌های بزرگ مبتنی بر شبکه‌های عصبی مصنوعی بر روی داده‌ها است.مدل‌ها یاد می‌گیرند که وظایف پیش‌بینی و استنتاج چالش‌برانگیز (مانند طبقه‌بندی، رگرسیون، تشخیص اشیاء در تصاویر و غیره) را با کشف خودکار الگوها و ویژگی‌های پیچیده در داده‌ها حل کنند. این کار با تقلید از ساختارهای داخلی پیچیده در مغز انسان انجام می‌شود.دوره آموزشی «درک هوش مصنوعی» ما، حوزه‌ها و وظایف قابل حل هوش مصنوعی، از جمله یادگیری عمیق، را به طور جامع رمزگشایی می‌کند.حوزه‌های مرتبط با هوش مصنوعی | ایوان پالومارس۲. چه زمانی باید یادگیری عمیق را به جای راه‌حل‌های یادگیری ماشینی انتخاب کنید؟راه‌حل‌های یادگیری عمیق یا Deep Learning در مسائلی که داده‌ها پیچیدگی بالایی دارند، مثلاً در داده‌های بدون ساختار یا با ابعاد بالا یا high-dimensional، برجسته هستند.همچنین این روش انتخاب ارجح برای مسائلی با حجم انبوه داده‌ها یا نیازمند ثبت الگوهای ظریف است: اغلب اوقات، آن‌ها می‌توانند در استخراج و درک ویژگی‌های معنادار داده‌ها که رویکردهای یادگیری ماشین ممکن است نتوانند آن‌ها را پیدا کنند، موفق شوند.در اینجا چند نمونه از مسائلی که با راه‌حل‌های یادگیری عمیق قابل حل هستند، آورده شده است:طبقه‌بندی تصاویر گونه‌های جانوری یا گیاهیپیش‌بینی بلندمدت قیمت‌های بازار سهامتشخیص چهره در تصاویروظایف پردازش زبان طبیعی مانند تشخیص صدا، ترجمه زبان و موارد دیگربا این حال، توجه داشته باشید که برای بسیاری از وظایف و مجموعه داده‌های ساده‌تر، مدل‌های یادگیری ماشین سبک مانند درخت‌های تصمیم‌گیری و رگرسیون‌ها ممکن است بیش از حد کافی باشند، و این امر آنها را به دلیل آسان‌تر و ارزان‌تر بودن آموزش و استقرار، به انتخاب بهتری نسبت به مدل‌های یادگیری عمیق تبدیل می‌کند.مدل‌های یادگیری عمیق معمولاً به مهارت‌های کامل آماده‌سازی و پردازش داده‌ها نیاز دارند؛ از این رو، ممکن است مقاله ِ موجود در لینک روبرو، در مورد مصاحبه‌های مهندسی داده برای شما مفید باشد.۳. چگونه رویکرد یادگیری عمیق مناسب را برای مسئله و داده‌های خود انتخاب می‌کنید؟تصمیم‌گیری در مورد یک رویکرد یادگیری عمیق مناسب به عوامل مختلفی مانند ماهیت داده‌ها، پیچیدگی مسئله و منابع محاسباتی موجود بستگی دارد.مراحل زیر دستورالعملی ساده اما مؤثر برای کمک به شما در انجام این انتخاب مهم هستند:1- یک تحلیل کامل از ویژگی‌های داده‌های خود انجام دهید. آیا ساختار یافته هستند یا بدون ساختار؟ آیا وابستگی‌های زمانی یا مکانی وجود دارد؟ متغیر(های) هدفی که می‌خواهید با مدل خود پیش‌بینی کنید کدامند؟2 - بر اساس تحلیل داده‌ها، مناسب‌ترین نوع معماری یادگیری عمیق را انتخاب کنید. به عنوان مثال، شبکه‌های عصبی کانولوشن (CNN) در پردازش داده‌های بصری عالی هستند، در حالی که شبکه‌های عصبی بازگشتی (RNN) به ویژه در داده‌های ترتیبی مؤثر هستند.3 - عوامل دیگری مانند تفسیرپذیری مدل، مقیاس‌پذیری و در دسترس بودن داده‌های برچسب‌گذاری شده برای آموزش را در نظر بگیرید. معماری‌های مختلف یادگیری عمیق این جنبه‌ها را تا درجات مختلفی در نظر می‌گیرند.سه معماری رایج یادگیری عمیق و کاربردهای آنها | ایوان پالومارس۴. چگونه یک راهکار یادگیری عمیق برای طبقه‌بندی طراحی می‌کنید؟طراحی یک معماری یادگیری عمیق متناسب با یک کار خاص مانند طبقه‌بندی (classification) شامل انتخاب تعداد و اندازه مناسب لایه‌های نورون‌ها و همچنین انتخاب توابع فعال‌سازی یا activation functions مناسب است.این تصمیمات معمولاً بر اساس ویژگی‌های داده‌ها گرفته می‌شوند. برای طبقه‌بندی تصویر، ممکن است از مجموعه‌ای از لایه‌های کانولوشنی در معماری خود برای ثبت الگوهای بصری مانند رنگ‌ها (یا ترکیبی از رنگ‌ها)، شکل‌ها، لبه‌ها و غیره استفاده کنید.لایه بالایی که در انتهای معماری یادگیری عمیق شما (سر مدل) قرار دارد نیز به وظیفه شما بستگی دارد، زیرا باید برای تولید خروجی مطلوب طراحی شود.به عنوان مثال، برای یک مسئله طبقه‌بندی تصویر مانند طبقه‌بندی تصاویر جوجه‌ها به گونه‌های پرنده، این لایه‌های آخر باید یک تابع فعال‌سازی softmax داشته باشند که احتمالات کلاس را برای تعیین محتمل‌ترین کلاس گونه‌های پرنده‌ای که تصویر مورد تجزیه و تحلیل به آن تعلق دارد، خروجی می‌دهد.معماری یادگیری عمیق برای طبقه‌بندی تصویر | ایوان پالومارس۵. برخی از چالش‌های رایج در مدل‌های یادگیری عمیق چیست و چگونه می‌توانید بر آنها غلبه کنید؟چالش‌های رایجی که ممکن است مانع از کاربرد موفقیت‌آمیز مدل‌های یادگیری عمیق در دنیای واقعی شوند، شامل بیش‌برازش یا overfitting، گرادیان‌های ناپدیدشونده و انفجاری یا vanishing and exploding gradients و لزوم وجود مقادیر زیادی از داده‌های برچسب‌گذاری‌شده برای آموزش هستند. خبر خوب این است که به لطف تلاش‌های تحقیقاتی مداوم، رویکردهایی برای رسیدگی به آنها وجود دارد.بیش‌برازش (Overfitting) زمانی اتفاق می‌افتد که یک مدل به گونه‌ای یاد می‌گیرد که «بیش از حد» نحوه ظاهر شدن داده‌های آموزشی را به خاطر می‌سپارد، بنابراین بعداً برای انجام استنتاج‌های صحیح در مورد هرگونه داده آینده و نادیده، دچار مشکل می‌شود. برای رفع این مشکل، تکنیک‌هایی وجود دارد که بر کاهش پیچیدگی مدل، مانند منظم‌سازی (regularization) یا محدود کردن میزان یادگیری مدل از داده‌ها، مثلاً از طریق توقف زودهنگام (early stopping)، متمرکز هستند.گرادیان‌های ناپدید شونده و انفجاری (Vanishing and exploding gradients) مربوط به مسائل همگرایی به سمت راه‌حل‌های غیربهینه در طول فرآیند به‌روزرسانی وزن هستند که زیربنای فرآیند آموزش است. برش گرادیان(Gradient clipping) و توابع فعال‌سازی (activation functions) پیشرفته می‌توانند به کاهش این مشکل کمک کنند.اگر چالش در داده‌های برچسب‌گذاری شده محدود است، به ترتیب تکنیک‌های یادگیری انتقالی (transfer learning) و افزودن داده (data augmentation) را برای مهار مدل‌های از پیش آموزش دیده یا تولید داده‌های مصنوعی بررسی کنید.علاوه بر این راه‌حل‌های خاص برای هر مشکل، مطمئن شوید که سازوکارهای منظم نظارت و تنظیم دقیق مدل را ایجاد کرده‌اید تا عملکرد خوب در دراز مدت تضمین شود.سوالات مصاحبه یادگیری عمیق برای فارغ التحصیلان جدیدتوابع فعال‌سازی (Activation functions) ، توابع ریاضی هستند که در تمام معماری‌های مدرن شبکه‌های عصبی عمیق استفاده می‌شوند. این توابع در سطح نورون، در طول فرآیند نگاشت چندین ورودی نورون به یک مقدار خروجی که به نورون‌های لایه بعدی ارسال می‌شود، رخ می‌دهند.آنها در مدل‌های یادگیری عمیق بسیار مهم هستند زیرا غیرخطی بودن را معرفی می‌کنند، که برای قادر ساختن آنها به یادگیری روابط و الگوهای پیچیده در داده‌ها در طول آموزش حیاتی است.در غیر این صورت، آنها با اعمال ترکیب‌های خطی متوالی از ورودی‌ها، چیزی بیش از الگوهای خطی از داده‌ها یاد نمی‌گیرند - درست مانند مدل‌های رگرسیون خطی کلاسیک!عملکرد فعال سازی در داخل یک نورون مصنوعی | ایوان پالومارسنمونه‌هایی از توابع فعال‌سازی محبوب عبارتند از تابع فعال‌سازی لجستیک (logit)، تانژانت هیپربولیک (tanh) و واحد خطی یکسو شده (ReLU)، همانطور که در زیر نشان داده شده است.نمونه‌هایی از توابع فعال‌سازی | تصویر از اورلین گرون (O’Reilly)۲. عملکرد یک مدل یادگیری عمیق را چگونه ارزیابی می‌کنید؟عملکرد مدل‌های یادگیری عمیق را می‌توان با استفاده از معیارهای رایج یادگیری ماشین وابسته به وظیفه ارزیابی کرد.برای طبقه‌بندی، معیارهایی مانند دقت، صحت، یادآوری، امتیاز F1 و مساحت زیر منحنی (AUC) را در نظر بگیرید. در همین حال، برای رگرسیون، می‌توانیم از معیارهای خطا مانند خطای جذر میانگین مربعات (RMSE) - Root Mean Square Error استفاده کنیم.این معیارها باید برای مقایسه پیش‌بینی‌های مدل با برچسب‌های واقعی یا مدل‌های مرجع استفاده شوند. برای مدل‌ها و کاربردهای پیشرفته‌تر مانند NLP، طیف وسیعی از معیارهای خاص وظیفه زبانی مانند امتیاز BLEU برای ترجمه، امتیاز ROUGE برای خلاصه‌سازی و غیره وجود دارد.۳. چند نمونه از کاربرد یادگیری عمیق در تجارت و صنعت را نام ببرید؟یادگیری عمیق در طیف گسترده‌ای از کاربردهای دنیای واقعی استفاده می‌شود، که برخی از آنها عبارتند از:تشخیص تصویر و اشیاء در جاده‌ها برای وسایل نقلیه خودرانپردازش و درک زبان طبیعی برای چت‌بات‌های پشتیبانی مشتریتحلیل‌های پیش‌بینی‌کننده برای توصیه‌های شخصی‌سازی‌شده در خرده‌فروشیتشخیص پزشکی بر اساس تصاویر اشعه ایکسسوالات مصاحبه مهندسی یادگیری عمیقسوالات مصاحبه برای یک نقش یادگیری عمیق با محوریت مهندسی، بر جنبه‌هایی مانند چارچوب‌های برنامه‌نویسی، کتابخانه‌ها و ابزارها تمرکز خواهد داشت.۱. چگونه از TensorFlow برای ساخت یک شبکه عصبی feedforward ساده برای طبقه‌بندی تصویر استفاده می‌کنید؟برای ساخت یک شبکه عصبی feedforward ساده برای طبقه‌بندی تصویر در Tensorflow، می‌توانیم با تعریف لایه به لایه معماری مدل با استفاده از Tensorflow Sequential API شروع کنیم.این شامل تعیین تعداد مناسب نورون‌ها و توابع فعال‌سازی در هر لایه و تعریف لایه نهایی (لایه خروجی) با فعال‌سازی softmax می‌شود.سپس، مدل را با مشخص کردن یک تابع زیان مناسب مانندآنتروپی متقاطع دسته‌بندی‌شده (categorical cross-entropy) ، یک بهینه‌ساز مانند Adam و معیارهای اعتبارسنجی، قبل از آموزش آن بر روی داده‌های آموزشی در طول تعداد مشخصی از دوره‌ها، کامپایل می‌کنیم. پس از ساخت مدل، عملکرد آن بر روی مجموعه اعتبارسنجی قابل ارزیابی است.Tensorflow معمولاً همراه با Keras API استفاده می‌شود کهپس از تکمیل دوره Advanced Deep Learning with Keras می‌توانید بر آن مسلط شوید.۲. رویکرد خود را برای مدیریت feedforward در یک مدل یادگیری عمیق از طریق تکنیک‌های منظم‌سازی در PyTorch شرح دهید.برای مدیریت feedforward در یک مدل یادگیری عمیق که با PyTorch پیاده‌سازی شده است، یک استراتژی رایج، استفاده از تکنیک‌های منظم‌سازی مانند منظم‌سازی L1 یا L2 با اضافه کردن عبارات جریمه به تابع زیان است.از طرف دیگر، می‌توان لایه‌های حذف را برای غیرفعال کردن تصادفی نورون‌ها در طول آموزش معرفی کرد؛ این کار از اتکای بیش از حد مدل به ویژگی‌های خاص استخراج شده از داده‌ها جلوگیری می‌کند.این دو استراتژی را می‌توان با توقف زودهنگام برای نهایی کردن آموزش، زمانی که عملکرد اعتبارسنجی شروع به کاهش می‌کند، ترکیب کرد.آیا علاقه‌مند به تقویت مهارت‌های PyTorch خود هستید؟ پس حتماً این مقدمه‌ای بر یادگیری عمیق را در دوره PyTorch بررسی کنید.۳. مثالی از استفاده از یادگیری انتقالی یا transfer learning برای تنظیم دقیق یک مدل یادگیری عمیق از پیش آموزش دیده (pre-trained) برای یک کار جدید ارائه دهید.VGG، BERT یا ResNet نمونه‌های شناخته‌شده‌ای از مدل‌های از پیش آموزش‌دیده (pre-trained) هستند که می‌توانند برای اهداف یادگیری انتقالی و تنظیم دقیق بارگذاری شوند. به‌طور خاص، این فرآیند شامل جایگزینی سر مدل، یعنی لایه طبقه‌بندی نهایی، با یک لایه جدید متناسب با وظیفه هدف است.پس از این تغییر ساختاری جزئی در معماری مدل، ما آن را با استفاده از نرخ یادگیری پایین، روی یک مجموعه داده جدید آموزش مجدد می‌دهیم تا وزن‌های مدل را با وظیفه جدید تطبیق دهیم، در حالی که ویژگی‌های اصلی که در ابتدا توسط مدل‌های از پیش آموزش‌ دیده آموخته شده‌اند، عمدتاً حفظ می‌شوند.سوالات مصاحبه در مورد یادگیری عمیق برای بینایی کامپیوتردر ادامه سوالات احتمالی که مصاحبه‌کننده ممکن است برای موقعیتی که شامل ساخت یا مدیریت راه‌حل‌های یادگیری عمیق در بینایی کامپیوتر مانند برنامه‌های پردازش تصویر است، از شما بپرسند، آورده شده است.۱. شبکه‌های عصبی کانولوشن را توضیح دهید و این مفهوم را در سه مورد استفاده معمول به کار ببرید.CNNها معماری‌های تخصصی یادگیری عمیق برای پردازش داده‌های بصری هستند. لایه‌های کانولوشن روی هم قرار داده شده و عملیات اساسی آنها روی داده‌های تصویر به گونه‌ای طراحی شده‌اند که از قشر بینایی در مغز حیوانات تقلید کنند.CNNها در کارهایی مانند طبقه‌بندی تصویر، تشخیص اشیا و تقسیم‌بندی تصویر عالی عمل می‌کنند. در اینجا شرح مختصری از موارد استفاده برای هر یک از این کارها آمده است:طبقه‌بندی تصویر یا Image classification : مشخص می‌کند که آیا یک تصویر سگ است یا گربه، تا سیستم‌های شناسایی و نظارت خودکار حیوانات خانگی بتوانند از آن استفاده کنند.تشخیص شیء یا Object detection : امکان مکان‌یابی و شناسایی عابران پیاده را به صورت بلادرنگ توسط وسایل نقلیه خودران فراهم می‌کند.قطعه بندی تصویر یا Image segmentation: مرزهای تومور را در تصاویر پزشکی مشخص می‌کند تا بیماران سرطانی را به طور دقیق تشخیص داده و درمان کند.۲. نقش لایه‌های کانولوشن و Pooling را در CNNها شرح دهید.لایه‌های کانولوشن (Convolution layers) در CNNها مسئول استخراج ویژگی از تصاویر ورودی هستند. آن‌ها مجموعه‌ای از وزن‌های قابل یادگیری به نام فیلتر یا هسته (kernels) را برای تشخیص الگوها و ویژگی‌هایی مانند لبه‌ها، شکل‌ها و بافت‌ها به همراه اطلاعات و روابط مکانی آن‌ها اعمال می‌کنند و از این طریق نمایش‌های بصری سلسله مراتبی را یاد می‌گیرند.در همین حال، لایه‌های ادغام یا pooling layers ، نقشه‌های ویژگی (نمایش‌های تصویر میانی) خروجی توسط لایه‌های کانولوشن را نمونه‌برداری می‌کنند. به عبارت دیگر، ابعاد یا وضوح مکانی اصلی آن‌ها کاهش می‌یابد در حالی که اطلاعات مهم استخراج شده حفظ می‌شوند.ترکیب لایه‌های کانولوشن متوالی با لایه‌های ادغام در CNN به افزایش مقاومت در برابر تغییرات ورودی‌ها، کاهش پیچیدگی محاسباتی در زمان آموزش و استنتاج و جلوگیری از مسائلی مانند بیش‌برازش یا overfitting کمک می‌کند.۳. برخی از چالش‌های رایج مدل‌های یادگیری عمیق که برای انجام وظایف بینایی کامپیوتر آموزش دیده‌اند، چیست؟در میان چالش‌ها و محدودیت‌های معمول در مدل‌های یادگیری عمیق، مثال‌های زیر به ویژه در مدل‌های بینایی کامپیوتر مانند CNNها برجسته می‌شوند:کمیت و کیفیت داده‌ها: مدل‌های یادگیری عمیق برای بینایی کامپیوتر برای آموزش صحیح به مجموعه داده‌های برچسب‌گذاری شده بسیار بزرگی نیاز دارند. این داده‌ها همچنین باید کیفیت کافی داشته باشند: تصاویر با وضوح بالا و بدون نویز، عاری از مشکلاتی مانند تاری یا نوردهی بیش از حد و غیره.برازش بیش از حد یا Overfitting : CNNها می‌توانند مستعد به خاطر سپردن نویز یا جزئیات خاص (گاهی اوقات نامربوط) در داده‌های آموزش بصری باشند که منجر به تعمیم ضعیف می‌شود.منابع محاسباتی: آموزش معماری‌های CNN عمیق به دلیل تعداد زیاد لایه‌ها و پارامترهای قابل آموزش، به منابع محاسباتی قابل توجهی نیاز دارد. بسیاری از آنها برای آموزش روان به GPUها و ظرفیت‌های حافظه زیاد نیاز دارند.قابلیت تفسیر: درک چگونگی پیش‌بینی مدل‌ها (به ویژه پیش‌بینی‌های اشتباه) در کارهای پیچیده‌ای مانند تشخیص تصویر، همچنان یک چالش است.سوالات مصاحبه در مورد یادگیری عمیق برای NLPدر اینجا چند سوال احتمالی وجود دارد که مصاحبه‌کننده ممکن است برای موقعیتی که شامل استفاده از فناوری‌های یادگیری عمیق در برنامه‌های NLP است، از شما بپرسد.۱. چگونه مکانیسم‌های توجه، عملکرد مدل‌های یادگیری عمیق را برای وظایف پردازش زبان طبیعی (NLP) بهبود می‌بخشند؟ترنسفورمرها (Transformers) خانواده‌ای پیشرفته از معماری‌های یادگیری عمیق و پیشرفته‌ترین فناوری‌های فعلی برای پرداختن به طیف وسیعی از مسائل چالش‌برانگیز NLP هستند. مکانیسم‌های توجه (Attention mechanisms) برای معماری‌های ترنسفورمر که زیربنای مدل‌های زبان بزرگ (LLM) مانند BERT و GPT هستند، محوری هستند و تا حد زیادی مسئول موفقیت آنها می‌باشند.ترنسفورمرها توانایی ثبت الگوهای پیچیده، اطلاعات زمینه‌ای و وابستگی‌های دوربرد بین عناصر یک متن ورودی را دارند و به طور قابل توجهی بر مشکلات موجود در راه‌حل‌های قبلی مانند حافظه محدود در RNNها (شبکه‌های عصبی بازگشتی) غلبه می‌کنند.مکانیزم‌های توجه یا Attention mechanisms اساساً اهمیت هر نشانه را در یک توالی، بدون نیاز به پردازش آن به صورت نشانه به نشانه، می‌سنجند.این جزء مهم ترنسفورمرها منجر به پیشرفت‌های قابل توجهی در کارهایی مانند طبقه‌بندی متن، تولید زبان و خلاصه‌سازی متن شده است و حوزه LLMها و هوش مصنوعی را به طور کلی متحول کرده است.دوره Datacamp در مورد مقدمه‌ای بر LLMها در پایتون، شما را نه تنها به مهارت‌های عملی برای ساخت و بهره‌برداری از LLMها، بلکه به درک کاملی از مفاهیم اصلی پیرامون LLMها و معماری‌های ترانسفورماتور مجهز می‌کند.۲. پیش‌آموزش مدل (pre-training) و تنظیم دقیق مدل (fine-tuning) برای معماری‌ها و داده‌های یادگیری عمیق در زمینه پردازش زبان طبیعی (NLP) چه معنایی دارند؟مدل پیش‌آموزش شده (Model pre-training) ، یک مدل یادگیری عمیق، مانند BERT، را برای طبقه‌بندی متن، روی مجموعه‌ای بزرگ از داده‌های متنی (میلیون‌ها تا میلیاردها متن نمونه) آموزش می‌دهد تا نمایش‌های زبانی را برای درک زبان عمومی یاد بگیرد.مدل تنظیم دقیق شده  (Model fine-tuning) از سوی دیگر، شامل گرفتن یک مدل از پیش آموزش‌دیده و تنظیم دقیق پارامترهای آموخته‌شده آن در یک برنامه NLP زمینه ای خاص، مانند تحلیل احساسات نظرات هتل‌ها، و ... است.از آنجایی که تنظیم دقیق (fine-tuning) معمولاً برای موارد استفاده خاص انجام می‌شود، نیاز به استفاده از یک مجموعه داده کوچک و مختص به دامنه از نمونه‌های متنی برچسب‌گذاری شده دارد، در نتیجه وزن‌ها در بخش‌هایی از مدل برای یادگیری جزئیات وظیفه هدف و بهبود عملکرد مدل در آن زمینه خاص بدون هزینه محاسباتی و الزامات داده‌ای آموزش یک مدل از ابتدا، تطبیق داده می‌شوند.نمونه‌ای از pre-training مدل و تنظیم دقیق (fine-tuning) یک LLM برای خلاصه‌سازی مقاله شیمی | ایوان پالومارس۳. آیا می‌توانید معماری یک مدل تبدیل‌کننده (transformer) مانند BERT و انواع وظایف NLP که می‌تواند به آنها بپردازد را شرح دهید؟معماری BERT نوع ساده‌شده‌ای از تانسفورمر رمزگذار-رمزگشای (encoder-decoder) اصلی استکه ترنسفورمر فقط انکد شده (encoder-only transformer) نامیده می‌شود.معماری اصلی ترنسفورمر، تقسیم شده به پشته‌های (stacks) رمزگذار و رمزگشا | مقدمه‌ای بر LLMها در پایتون (ایوان پالومارس)معماری ترنسفورمر فقط انکد کننده که توسط مدل‌هایی مانند BERT استفاده می‌شود | مقدمه‌ای بر LLMها در پایتون (ایوان پالومارس)پشته انکد (encoder stack) از چندین لایه انکد تشکیل شده است (به نمودار بالا مراجعه کنید). در هر لایه، (زیر)لایه‌هایی از مکانیسم‌های خودتوجهی (self-attention) و شبکه‌های عصبی feedforward وجود دارد. کلید فرآیند درک زبان که توسط BERT انجام می‌شود، در جریان توجه یا attention دو طرفه است که به صورت تکراری در انکدر اعمال می‌شود، به طوری که وابستگی‌های بین کلمات در یک توالی ورودی در هر دو جهت ثبت می‌شوند. اتصالات عصبی feedforward ، این وابستگی‌های آموخته شده را به الگوهای زبانی پیچیده‌تر &quot;به هم پیوند می‌دهند&quot;.علاوه بر این ویژگی کلی مبدل‌های فقط کد شده، BERT با ترکیبیک رویکرد مدل‌سازی زبان ماسک شده Masked Language Modelling (MLM) مشخص می‌شود. این مکانیسم در طول پیش‌آموزش (pre-training) برای ماسک کردن (mask) برخی از کلمات به صورت تصادفی استفاده می‌شود و در نتیجه مدل را مجبور می‌کند تا پیش‌بینی کلمات ماسک شده (masked) را بر اساس درک زمینه اطراف آنها یاد بگیرد.سوالات مصاحبه پیشرفته یادگیری عمیقبیایید با سه سوالی که ممکن است در صورت درخواست برای یک نقش پیشرفته یادگیری عمیق از شما پرسیده شود، بحث را به پایان برسانیم.۱. چگونه یک مدل یادگیری عمیق را برای مسئله‌ای با داده‌های برچسب‌گذاری‌شده‌ی محدود طراحی و پیاده‌سازی می‌کنید؟استراتژی ترکیبی زیر را می‌توان برای به حداکثر رساندن عملکرد مدل یادگیری عمیق در مواجهه با مشکل رایج داده‌های آموزشی محدود پیاده‌سازی کرد:با تنظیم دقیق مدل‌های از پیش آموزش‌دیده (fine-tuning pre-trained) روی یک وظیفه مشابه با دامنه خاص که مدل اصلی برای آن آموزش دیده است، از یادگیری انتقالی transfer learning بهره ببرید.یادگیری نیمه‌نظارتی semi-supervised و خودنظارتی self-supervised را برای به دست آوردن مجموعه داده‌های بدون برچسب که بیشترین تعداد را دارند، بررسی کنید.مدل‌های افزودن داده Data augmentation و مولد generative models می‌توانند به تولید نمونه‌های داده مصنوعی برای غنی‌سازی مجموعه برچسب‌گذاری شده اصلی کمک کنند.روش‌های یادگیری فعال Active learning را می‌توان برای پرس‌وجو از کاربران برای به دست آوردن نمونه‌های برچسب‌گذاری شده اضافی بر روی مجموعه‌ای از داده‌های بدون برچسب استفاده کرد.۲. آیا می‌توانید سه نکته‌ی مهم را هنگام استقرار مدل‌های یادگیری عمیق در مقیاس بزرگ در محیط‌های تولیدی دنیای واقعی فهرست کنید؟استقرار راهکارهای یادگیری عمیق در مقیاس وسیع در محیط‌های تولیدی نیازمند توجه به ملاحظات متعددی است که سه مورد از آنها عبارتند از:مقیاس‌پذیری و عملکرد مدل یا Model scalability and performance بسیار مهم هستند و نیازمند معماری‌های کارآمد و رویکردهای بهینه‌سازی برای مقابله با جریان‌های داده بزرگ در زمان واقعی می‌باشند.استحکام و قابلیت اطمینان یا Robustness and reliability در ایجاد رویه‌های آزمایش و اعتبارسنجی برای تضمین عملکرد پایدار در طول زمان و بین سناریوهای مختلف بسیار مهم هستند.تضمین استانداردهای حریم خصوصی و امنیت داده‌ها یا data privacy and security standards و همچنین رعایت مقررات یا regulatory compliance برای محافظت از اطلاعات حساس. اینها جنبه‌های حیاتی برای رعایت رفتار مسئولانه سیستم و استفاده از سیستم یادگیری عمیق و جلب اعتماد کاربران هستند.سایر ملاحظات مهم برای استقرار مدل‌های یادگیری عمیق در محیط عملیاتی شامل زیرساخت‌های سخت‌افزاری و شبکه، نظارت بر سیستم و ادغام یکپارچه با چارچوب‌های نرم‌افزاری موجود است.۳. به نظر شما پیشرفت‌های اخیر در یادگیری عمیق چگونه می‌تواند آینده این حوزه را از نظر کاربردها و تأثیر گسترده‌تر شکل دهد؟پیشرفت‌های اخیر در یادگیری عمیق، پتانسیل تغییر عمیق صنایع متعدد و حتی ایجاد صنایع جدید را دارند.مکانیسم‌های توجه یا Attention mechanisms مورد استفاده در معماری‌های ترنسفورمر transformer architectures پشت LLMها، انقلابی در حوزه‌ی NLP ایجاد می‌کنند، مرزهای وظایف NLP را به طور قابل توجهی جابجا می‌کنند و تعاملات پیچیده‌تر انسان و ماشین را از طریق راه‌حل‌های هوش مصنوعی مکالمه‌ای، پاسخ به پرسش و موارد دیگر امکان‌پذیر می‌سازند. گنجاندن اخیر Retrieval Augmented Generation (RAG) در این معادله، به LLMها در تولید زبان صادقانه و مبتنی بر شواهد کمک بیشتری می‌کند.یادگیری تقویتی یا Reinforcement learning یکی از نویدبخش‌ترین روندهای هوش مصنوعی است، زیرا اصول آن از اصول اولیه یادگیری انسان تقلید می‌کند. ادغام آن با معماری‌های یادگیری عمیق، به ویژه مدل‌های مولد (generative models) مانند شبکه‌های مولد تخاصمی یا generative adversarial networks ، امروزه در خط مقدم تحقیقات علمی قرار دارد. نام‌های بزرگی مانند OpenAI، گوگل و مایکروسافت، این دو حوزه هوش مصنوعی را در جدیدترین راه‌حل‌های نوآورانه خود که قادر به تولید محتوای «شبیه به واقعیت» در قالب‌های مختلف هستند، ترکیب می‌کنند.این پیشرفت‌ها و سایر پیشرفت‌های اخیر، استفاده از هوش مصنوعی توسط جامعه را به طرز چشمگیری دموکراتیک کرده‌اند، از این رو، می‌توان اخلاق، جنبه‌های قانونی و مقررات هوش مصنوعی را به عنوان یک موضوع کلیدی برای کار بر روی آن در نظر گرفت تا تأثیر مفید راه‌حل‌های یادگیری عمیق در سراسر جامعه تضمین شود.جمع‌بندیبرای نتیجه‌گیری از این بررسی سوالات رایج مصاحبه یادگیری عمیق، یک نکته واضح این است که کلید موفقیت به ترکیبی از مبانی نظری، تسلط قوی بر مهارت‌های عملی و به‌روز ماندن با آخرین پیشرفت‌ها بستگی دارد.یادگیری عمیق فقط مربوط به الگوریتم‌ها، مدل‌ها و انتخاب‌های طراحی معماری نیست. این در مورد شناسایی بهترین راه‌حل‌ها برای حل مشکلات داده‌های دنیای واقعی است.و یادگیری مداوم کلید اصلی است. دیتاکمپ یک مسیر کامل مهارت‌های یادگیری عمیق در پایتون و همچنین دو مسیر یادگیری عمیق با محوریت کاربرد ارائه می‌دهد: پردازش تصویر و پردازش زبان طبیعی. این مسیرها یک نقشه راه ساختاریافته برای افزایش مهارت‌های یادگیری عمیق شما ارائه می‌دهند و آمادگی برای مصاحبه بعدی شما را تضمین می‌کنند.در نهایت، اگر به دنبال دستورالعمل‌های کلی در مورد نحوه آماده شدن برای مصاحبه یادگیری عمیق خود هستید، بخش‌های پایانی این مقاله در مورد سوالات مصاحبه یادگیری ماشین را بررسی کنید. از آنجایی که یادگیری عمیق زیرمجموعه‌ای از یادگیری ماشین است، ترکیب این دستورالعمل‌های کلی با 20 سوال یادگیری عمیق که در اینجا بررسی کرده‌ایم، مطمئناً به آماده‌سازی شما کمک خواهد کرد.نویسنده: Iván Palomares Carrascosaمنبع: لینک datacamp.com</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Fri, 08 Aug 2025 10:19:00 +0330</pubDate>
            </item>
                    <item>
                <title>تبدیل متن به تصویر با هوش مصنوعی ،مروری بر imagen و Diffusion Model</title>
                <link>https://virgool.io/@hootan09/%D8%AA%D8%A8%D8%AF%DB%8C%D9%84-%D9%85%D8%AA%D9%86-%D8%A8%D9%87-%D8%AA%D8%B5%D9%88%DB%8C%D8%B1-%D8%A8%D8%A7-%D9%87%D9%88%D8%B4-%D9%85%D8%B5%D9%86%D9%88%D8%B9%DB%8C-imagen-%DA%86%DA%AF%D9%88%D9%86%D9%87-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D8%AF-jchvsie0iapi</link>
                <description>یک توضیح مختصر از یک صحنه بدهید، Imagen می تواند تصاویر واقعی و با وضوح بالا از آن صحنه ایجاد کند. همه چیزهایی را که باید در مورد Imagen و نحوه عملکرد آن بدانید در این راهنمای ساده بیاموزید.در حالی که دنیای یادگیری ماشین هنوز در حال کنار آمدن با نتایج چشمگیر DALL-E 2 بود که در اوایل سال 2022 منتشر شد، گوگل با انتشار مدل تبدیل متن به تصویر خود Imagen ، به نظر می‌رسد مرزهای تولید تصویر از متن را جابجا می‌کند و حتی جلوتر می برد.این Imagen که همین ماه گذشته (مقاله برای سال 2022 است ) منتشر شد، می‌تواند تصاویری با کیفیت بالا و وضوح بالا تولید کند که تنها با توضیحی از یک صحنه ارائه می‌شود، صرف نظر از اینکه چنین صحنه‌ای در دنیای واقعی چقدر منطقی یا قابل قبول است. در زیر می توانید چندین نمونه از این تصاویر را با شرح زیر مشاهده کنید:تصاویر ایجاد شده توسط Imagen با توجه به شرح‌های مربوطه (اقتباس از منبع)این نتایج چشمگیر بدون شک باعث تعجب بسیاری از نحوه عملکرد Imagen شده است. در این مقاله نحوه عملکرد Imagen در چندین سطح را توضیح خواهیم داد.ابتدا Imagen را از دید بالا به پایین بررسی می کنیم تا اجزای سطح بالای آن و چگونگی ارتباط آنها با یکدیگر را درک کنیم. سپس به جزئیات بیشتری در مورد این مؤلفه‌ها، که هر کدام دارای زیربخش خاص خود هستند، خواهیم پرداخت تا نحوه عملکرد آنها را درک کنیم. در نهایت، ما یک بررسی عمیق در Imagen انجام خواهیم داد که برای محققان، دانشجویان و متخصصان یادگیری ماشین در نظر گرفته شده است.بیایید بررسی کنیم!مقدمهدر چند سال گذشته، پیشرفت قابل توجهی در حوزه تبدیل متن به تصویر در یادگیری ماشین صورت گرفته است. یک مدل متن به تصویر یک توصیف متنی کوتاه از یک صحنه را می گیرد و سپس تصویری تولید می کند که صحنه توصیف شده را بازتاب می دهد. به عنوان مثال توضیح ورودی (یا &quot;Caption&quot;) و تصویر خروجی را می توان در زیر مشاهده کرد:ورودی و خروجی مدل متن به تصویر (تصویر و توضیحات برگرفته از منبع)توجه به این نکته مهم است که مدل‌های متن به تصویر با کارایی بالا، لزوماً می‌توانند مفاهیم و اشیاء نامرتبط را به روش‌های معنایی قابل قبولی ترکیب کنند. بنابراین، این مدل‌ها باید بر چالش‌هایی مانند گرفتن روابط فضایی، درک کاردینالیته و تفسیر درست نحوه ارتباط کلمات در توضیحات با یکدیگر غلبه کنند. اگر تصویر بالا را با دقت بیشتری بررسی کنیم، می بینیم که Imagen در اولین موشکافی در این موراد عملکرد خوبی دارد.این Imagen رابطه معنایی و منطقی را به خوبی ثبت می کند (تصویر اقتباس شده از منبع)اغراق آمیز است اگر بگوییم این مدل ها تحسین برانگیز اند. آنها از پایگاه داده برای جستجو و بازگرداندن تصویری که با توضیحات مطابقت دارد استفاده نمی کنند، و حتی تصاویر فرعی از قبل موجود را به روشی تمیز به هم نمی چسبانند تا نتیجه با عنوان مطابقت داشته باشد. آنها در عوض تصاویر کاملاً جدیدی تولید می کنند که به صورت بصری اطلاعات معنایی موجود در متن را منتقل می کنند.آیا می خواهید یاد بگیرید که چگونه Imagen بسازید؟ مقاله MinImagen ما را بررسی کنید که در آن یاد می‌گیریم که چگونه یک پیاده‌سازی مینیمال Imagen را به صورت گام به گام، با یک مخزن کامل بسازیم!اکنون که متوجه شدیم مدل های متن به تصویر به طور کلی چیست، می توانیم نگاهی به نحوه عملکرد مدل متن به تصویر Imagen با نگاه بالا و کلی بیندازیم.در کل Imagen چگونه کار می کند: نمای کلیدر این بخش، ما یاد خواهیم گرفت که اجزای برجسته Imagen چه کار می کنند و چگونه با یکدیگر ارتباط دارند. ابتدا، معماری فراگیر Imagen را با توضیحی در سطح بالا از نحوه عملکرد آن بررسی خواهیم کرد و سپس هر جزء را با دقت بیشتری در زیر بخش های زیر بررسی خواهیم کرد.در اینجا یک ویدیوی کوتاه وجود دارد که نشان می‌دهد Imagen چگونه کار می‌کند، به همراه توضیح آنچه که در ویدیو زیر می‌گذرد: https://www.aparat.com/v/3qxNz ابتدا متن به عنوان ورودی در یک انکدر متن وارد می شود. این انکدر، متن را به یک نمایش عددی تبدیل می کند که اطلاعات معنایی درون متن را کپسوله و محصور می کند.در مرحله بعد، یک مدل تولید تصویر با شروع از نویز یا برفک تلویزیون (&quot;TV Static&quot;)، تصویری را ایجاد می کند و به آرامی آن را به یک تصویر خروجی تبدیل می کند. برای هدایت این فرآیند، مدل تولید تصویر متن انکد شده را به عنوان ورودی دریافت می‌کند، که به مدل می‌گوید که چه چیزی در عنوان وجود دارد تا بتواند تصویر مربوطه را ایجاد کند. خروجی یک تصویر کوچک است که به صورت بصری عنوانی را که ما به انکدر متن وارد کردیم منعکس می کند.سپس تصویر کوچک به یک مدل با وضوح فوق العاده (super-resolution model) منتقل می شود، که تصویر را به وضوح بالاتر می برد. این مدل همچنین متن انکد شده را به عنوان ورودی می‌گیرد، که به مدل کمک می‌کند تا تصمیم بگیرد که چگونه رفتار کند زیرا «شکاف‌های» اطلاعات گمشده را که لزوماً از چهار برابر شدن اندازه تصویر ما ناشی می‌شود، پر می‌کند. نتیجه یک تصویر با اندازه متوسط از آنچه ما می خواهیم است.در نهایت، این تصویر با اندازه متوسط به مدل دیگری با وضوح فوق‌العاده (another super-resolution) منتقل می‌شود، که تقریباً مشابه نمونه قبلی عمل می‌کند، با این تفاوت که این بار تصویر با اندازه متوسط ما را می‌گیرد و آن را به تصویری با وضوح بالا تبدیل می‌کند. نتیجه تصویر 1024 در  1024 پیکسل است که به صورت بصری بازتاب دهنده ی معنایی است که ما به عنوان متن ورودی وارد کردیم.در بالاترین سطح، این تمام چیزی است که وجود دارد! برای نگاهی جزئی تر به هر یک از اجزای بزرگ Imagen، بخش های فرعی زیر را بررسی کنید. در غیر این صورت، می‌توانید به ادامه جزئیات بپردازید تا با نحوه عملکرد Imagen آشنا شوید، یا مستقیماً به انتها یا سخن نهایی  بروید.انکدر متن یا Text Encoderدر این بخش، نگاهی دقیق‌تر به انکدر متن Imagen خواهیم داشت. همانطور که از ویدیوی بالا می توان فهمید، انکدر متن برای عملکرد Imagen بسیار مهم است. تمام اجزای دیگر Imagen را مشروط می کند و وظیفه انکد کردن عنوان متنی را به روشی مفید بر عهده دارد.انکد متن در Imagen یک Transformer است. اگر با Transformers آشنا نیستید، نگران نباشید. مهمترین جزئیات در اینجا این است که چنین انکدری تضمین می کند که متن انکد شده و نحوه ارتباط کلمات درون عنوان را با یکدیگر درک می کند (با روشی به نام &quot;توجه به خود&quot; یا self-attention ). این بسیار مهم است زیرا زبان انگلیسی اطلاعات را از طریق ساختار نحوی خود انکد می کند که بر معنی دادن معنایی (semantic meaning) یک جمله داده شده تأثیر می گذارد.اگر Imagen فقط به تک تک کلمات توجه می‌کرد و نه نحوه ارتباط آنها با یکدیگر، می‌توانستیم تصاویر باکیفیتی داشته باشیم که تک تک عناصر متن را به تصویر بکشند، اما آنها را به گونه‌ای به تصویر نمی‌کشند که معنی دادن معنایی کلمات را به درستی منعکس کند. ما می توانیم این تفاوت را در مثال زیر مشاهده کنیم، جایی که عدم توجه به نحوه ارتباط کلمات با یکدیگر می تواند نتیجه بسیار ضعیف (هر چند خنده دار) داشته باشد:نکته: در حالی که مثال بالا به منظور برجسته کردن نیاز به انکدر - توجه به خود در Transformer است، نتایجی حتی به خوبی تصویر &quot;نادرست&quot; بالا نیز ممکن است نباشد. برای مثال، بدون ابزاری برای درک رابطه نحوی بین کلمات، مدل نمی‌فهمد که «ایستادن روی» هم به اشیاء مستقیم و هم غیرمستقیم نیاز دارد و بنابراین نمی‌تواند این مفهوم را به درستی به تصویر بکشد.انکد کردن متن در طول آموزش ثابت می باشد (frozen)، به این معنی که مدل نحوه ایجاد انکدرها را یاد نمی‌گیرد یا تغییر نمی‌دهد. این فقط برای تولید انکدرهایی استفاده می‌شود که به بقیه مدل‌هایی که آموزش داده می‌شوند، تغذیه شوند (به عنوان استفاده کننده در مدل های دیگر بکار گرفته میشود).مولد تصویر یا Image Generatorانکدر متن یک توضیح مفید از متن ورودی به Imagen را تولید می کند، اما ما هنوز باید روشی برای تولید تصویری که از این توضیح استفاده می کند، ابداع کنیم. برای انجام این کار، Imagen از مدل Diffusion استفاده می کند، که نوعی مدل مولد است که در سال های اخیر به دلیل عملکرد پیشرفته خود در چندین کار، محبوبیت قابل توجهی به دست آورده است. قبل از حرکت به جلو، اجازه دهید در حال حاضر به خلاصه ای از مدل های Diffusion نگاه کنیم.مدل های Diffusion چه هستند؟مدل‌های Diffusion روشی برای ایجاد داده‌هایی هستند که شبیه به مجموعه‌ای از داده‌های آموزشی است. آنها با از بین بردن داده های آموزشی از طریق اضافه کردن نویز، آموزش می بینند و سپس یاد می گیرند که داده ها را با معکوس کردن این فرآیند و از بین بردن نویز بازیابی کنند. با توجه به یک تصویر ورودی، مدل Diffusion  به طور مکرر تصویر را با نویز گاوسی در یک سری مراحل زمانی خراب می‌کند، و در نهایت نویز گاوسی خالص یا &quot;برفک تلویزیونی&quot; باقی می‌ماند.Diffusion فرآیند نویز تکراری مدل‌های سپس مدل Diffusion به سمت عقبگرد کار می کند و یاد می گیرد که چگونه نویز را در هر مرحله جدا کرده و حذف کند و روند تخریب را که به تازگی رخ داده است، خنثی کند.پس از آموزش، مدل را می‌توان به نصف تقسیم کرد و می‌توانیم از نویز گاوسی نمونه‌برداری تصادفی شروع کنیم که از مدل Diffusion برای حذف تدریجی نویز برای تولید تصویر استفاده می‌کنیم.در زیر می‌توان نمونه‌ای از ارقام دست‌نویسی را مشاهده کرد که از نویز خالص گاوسی تولید می‌شوند:یک مدل دیفیوژن می‌تواند ارقام دست‌نویس جدیدی را از نویز گاوسی تولید کند (منبع)شرطی سازی عنوان یا Caption Conditioningبه طور خلاصه، یک مدل Diffusion آموزش دیده با نویز گاوسی شروع می شود و سپس به طور تکراری تصویری شبیه به تصاویری که روی آن آموزش داده شده است تولید می کند. در این مرحله ممکن است آشکار باشد که ما هیچ کنترلی بر روی اینکه چه تصویری واقعاً خروجی است نداریم - ما به سادگی نویز گاوسی را به مدل وارد می‌کنیم و یک تصویر تصادفی را بیرون می‌ریزد که به نظر می‌رسد می‌تواند متعلق به مجموعه داده آموزشی باشد. به یاد بیاورید که هدف ما ایجاد تصاویری است که اطلاعات معنایی عنوانی را که در Imagen وارد می‌کنیم در خود محصور کند، بنابراین ما به روشی برای ترکیب عنوان در فرآیند Diffusion نیاز داریم. ما چطور میتونیم این را انجام بدیم؟از بالا به یاد بیاورید که انکدر متن ما یک انکدر شرح دهنده عنوان را تولید کرد. این &quot;انکد کننده&quot; در واقع دنباله ای از بردارها است. برای تزریق این اطلاعات انکد شده به مدل Diffusion ، این بردارها را با هم ترکیب می کنیم و مدل Diffusion خود را بر اساس آنها شرط می کنیم. با شرطی کردن این بردار، مدل Diffusion یاد می‌گیرد که چگونه روش حذف نویز خود را به منظور ایجاد تصویری که به خوبی با عنوان همسو باشد، تطبیق دهد. این فرآیند را می توان در ویدیو زیر مشاهده کرد:وضوح تصویر فوق العاده یا Image Super-Resolutionمولد تصویر، یا مدل &quot;پایه&quot;، یک تصویر کوچک 64x64 را خروجی می دهد. برای نمونه‌برداری از این مدل به نسخه نهایی 1024x1024، از یک مدل با وضوح فوق‌العاده (super-resolution model) برای نمونه‌برداری هوشمندانه تصویر استفاده می‌کنیم.برای مدل با وضوح فوق العاده، Imagen دوباره از یک مدل Diffusion استفاده می کند. فرآیند کلی اساساً مشابه مدل پایه است. به جز اینکه به جای شرطی کردن صرفاً انکد کردن عنوان، تصویر کوچکتری را که نمونه اولیه است را نیز شرط می کنیم. روند کلی را می توان در ویدیوی زیر مشاهده کرد:خروجی این مدل با وضوح فوق العاده در واقع خروجی نهایی ما نیست بلکه یک تصویر با اندازه متوسط است. برای ارتقاء این تصویر به رزولوشن نهایی 1024x1024، مدل دیگری با وضوح فوق العاده استفاده شده است. دو معماری سوپر-رزولوشن تقریباً معادل هستند، بنابراین ما با پرداختن به جزئیات مدل دوم با وضوح فوق‌العاده به این موضوع اهمیت نمی‌دهیم.خروجی دومین مدل فوق رزولوشن خروجی نهایی Imagen است.خلاصهبه طور خلاصه، عنوان به یک انکدر ترانسفورماتور از قبل آموزش دیده و منجمد-ثابت وارد می شود که دنباله ای از بردارها (انکد کننده ی متن) را خروجی می دهد. این بردارها مهم هستند زیرا آنها نحوه ارتباط کلمات در عنوان با یکدیگر را انکد می کنند و به عنوان اطلاعات شرطی برای سایر اجزای مدل عمل می کنند.سپس متن انکد شده به یک مدل Diffusion تصویر، منتقل می‌شوند، که با نویز گاوسی شروع می‌شود و سپس به تدریج نویز را حذف می‌کند تا تصویر جدیدی تولید کند که اطلاعات معنایی درون عنوان را منعکس می‌کند. خروجی این مدل یک تصویر 64x64 پیکسل است.پس از این، دو مدل Diffusion دیگر برای تفکیک فوق‌العاده این تصویر به اندازه نهایی 1024x1024 استفاده می‌شود که مجدداً مشروط به انکدهای متن (و همچنین تصاویر با وضوح پایین‌تر) است.اکنون که درک از بالا به پایین از Imagen داریم، می توانیم در بخش بعدی به جزئیات بپردازیم. از طرف دیگر، به راحتی به نتایج و تجزیه و تحلیل یا سخن نهایی بروید.نسخه کامل مقاله در اینجا</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Fri, 28 Jul 2023 21:00:17 +0330</pubDate>
            </item>
                    <item>
                <title>چگونه به پولدارترین فرد کره زمین تبدیل شویم (و یادگیری کمی گولنگ در مسیر)</title>
                <link>https://virgool.io/@hootan09/%DA%86%DA%AF%D9%88%D9%86%D9%87-%D8%A8%D9%87-%D9%BE%D9%88%D9%84%D8%AF%D8%A7%D8%B1%D8%AA%D8%B1%DB%8C%D9%86-%D9%81%D8%B1%D8%AF-%DA%A9%D8%B1%D9%87-%D8%B2%D9%85%DB%8C%D9%86-%D8%AA%D8%A8%D8%AF%DB%8C%D9%84-%D8%B4%D9%88%DB%8C%D9%85-%D9%88-%DB%8C%D8%A7%D8%AF%DA%AF%DB%8C%D8%B1%DB%8C-%DA%A9%D9%85%DB%8C-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%AF%D8%B1-%D9%85%D8%B3%DB%8C%D8%B1-t4cfpznzmokl</link>
                <description>خُب صادقانه بگویم این نوشته بیشتر درباره قسمت دوم عنوان (یعنی گولنگ) خواهد بود.ما یک شبیه سازی بازار با حداقل کد Go خواهیم ساخت و نشان خواهیم داد که چگونه ثروتمندان حتی زمانی که اصلا حریص نیستند ثروتمندتر می شوند.نابرابری ثروتدر حالی که چندی پیش، با مقاله ای در Spektrum der Wissenschaft، نسخه آلمانی Scientific American مواجه شدم (اینجا پیوندی به مقاله اصلی در SA است، جایی که نویسنده، پروفسور بروس ام. بوقوسیان، مدل اقتصادی توزیع ثروت را توضیح می دهد. این مدل یک شبیه‌سازی نسبتاً ساده از معاملات بین عوامل اقتصادی است و به نظر نمی‌رسد مکانیزم مدل به هیچ وجه به نفع عوامل ثروتمندتر باشد، در واقع قوانین به نفع  عامل فقیرتر از این دو عامل (یعنی فقیر و ثروتمند) ساخته شده است. با این حال، در این مدل، پول به سمت اشخاص کمتر و کمتری جریان می‌یابد که ثروتمندتر و ثروتمندتر می‌شوند. در پایان، بیشتر ثروت متعلق به یک نماینده واحد یا تعداد بسیار کمی از نمایندگان است و بقیه اشخاص تقریباً مالک هیچ چیز نیستند.چگونه می تواند این باشد؟وارد شبیه سازی های بازار شوید: مدل Yard Sale (یا حراج خانگی)توضیح اولیه مدل فروش حیاط است که توسط Anirban Chakraborti توسعه یافته است (به توزیع پول در بازارهای مدل اقتصاد مراجعه کنید). فرض اصلی این است که یک خریدار هرگز دقیقاً به اندازه ارزش کالا پرداخت نمی کند. گاهی اوقات، خریدار بیشتر و گاهی کمتر از ارزش واقعی کالای معامله شده پرداخت می کند.به عنوان مثال، تصور کنید که از یک حیاط فروشی بازدید می کنید و یک Yokutlix زیبا را کشف می کنید. همانطور که همه می دانند Yokutlixe ها دارای ارزش اقتصادی 147 دلار هستند. پس از چانه زنی زیاد، فروشنده موافقت می کند که آن را تنها با 122 دلار به شما بدهد.پس از معامله، ثروت شخصی شما 147 تا 122 دلار یا 25 دلار افزایش یافت، در حالی که ثروت فروشنده به همان میزان کاهش یافت.در فروش بعدی، یک ویجت القاء چمدان رباتیک پیدا می‌کنید که به نظر می‌رسد نسبتاً استفاده شده است، اما همچنان کار می‌کند، و از آنجایی که از کودکی می‌خواستید یک ویجت محرک چمدان رباتیک داشته باشید، فوراً آن را با قیمت 67 دلار خریداری می‌کنید. (و اعتراف کنید: شما حتی تا 97.50 دلار برای این کار پرداخت می کردید، اینطور نیست؟) اکنون ارزش اقتصادی واقعی این ویجت نه چندان جدید ویجت القاء چمدان رباتیک فقط 50 دلار است، بنابراین این بار ثروت شما کاهش می یابد. 17 دلار، و ثروت فروشنده بر این اساس افزایش می یابد.بیایید با آن روبرو شویم، شما مهارت استثنایی در چانه زنی ندارید و با هر یک از معاملاتتان، به نظر می رسد ثروت به طور تصادفی به سمت شما یا از شما شناور می شود. اما شما از قبل این را می دانستید، درست است؟اگر همه مردم یک بازار مثل شما باشند چه؟ اگر همه افراد در یک بازار مهارت های معاملاتی یکسانی داشته باشند، ثروت باید کمابیش به طور مساوی توزیع شود، شاید مانند توزیع نویز سفید.با این حال، در شبیه‌سازی Yard Sale، حتی اگر همه شرکت‌کنندگان احتمال یکسانی برای به دست آوردن یا از دست دادن ثروت در یک معامله دارند، ثروت به ناچار به سمت چند نفر (یا حتی یک نفر واحد) جریان می‌یابد و بقیه را در فقر می‌گذارد.دوباره - چگونه این اتفاق می افتد؟مدل کازینوبرای پاسخ به این، بیایید نگاهی دقیق تر به مدل Bruce M. Boghosian بیندازیم. من آن را «مدل کازینو» می‌نامم زیرا بوقوسیان از استعاره کازینو برای توصیف ماهیت یک معامله استفاده می‌کند: یک روی سکه تصمیم می‌گیرد که آیا یک نماینده در یک معامله ثروت به دست آورد یا از دست بدهد. تنظیم در واقع بسیار ساده است، چند قانون برای توصیف مدل کافی است. (توجه: اینها دقیقاً همان قوانینی نیستند که در مقاله ای که من به آن اشاره کردم. چند مدل مشابه در اطراف وجود دارد، و من برای کدنویسی ساده تر چند جنبه را تطبیق دادم، همانطور که بعداً خواهیم دید.)1- شبیه سازی شامل یک بازار با تعداد ثابت عامل و مقدار ثابت پول است.2- همه نمایندگان با یک مقدار پول شروع می کنند.3- شبیه سازی در دور (گردش) پیشرفت می کند. در هر دور، دو عامل به طور تصادفی انتخاب شده وارد یک تراکنش می شوند.4- در هر تراکنش، از آنجایی که پول پرداخت شده برای یک کالا هرگز با ارزش واقعی کالا یکسان نیست، ثروت به طور تصادفی از یک عامل به نماینده دیگر جریان می یابد. (مثلاً در شرط‌بندی کازینو، با چرخاندن سکه مشخص می‌شود.)5- جابجایی ثروت همیشه تنها کسری از ثروت فقیرتر از این دو عامل (فقیر و ثروتمند) است.6- اگر ثروت از فقیرتر به عامل ثروتمندتر سرازیر شود، f باید کوچکتر از حالت مخالف باشد، یعنی زمانی که ثروت به سمت عامل فقیرتر سرازیر شود.7- هیچ بدهی مجاز نیست. از این رو نمایندگان نمی توانند بیش از آنچه که دارند ضرر کنند.قانون 6 به خصوص جالب است. این قانون بدیهی است که به نماینده فقیرتر مزیتی می دهد. برای توضیح این موضوع، اگر عامل فقیرتر ثروت را به دست آورد، اجازه دهید f را بر روی 20٪ و اگر عامل فقیرتر ثروت را از دست بدهد، 17٪ تعیین کنیم. به نظر یک مزیت واقعی برای مامور فقیرتر است، درست است؟همانطور که خواهیم دید، حتی این مزیت نیز مانع از نابرابری ثروت نمی شود.کد برنامهمن سعی کردم با کمترین کد برای پیاده سازی این شبیه سازی کنار بیایم.عوامل بازار فقط تکه ای از شناورها هستند که نشان دهنده ثروت هر نماینده است. من عمداً سعی نکردم چند مدل بازیگر مستقل فانتزی با ساختار و روش بسازم. و ممکن است تعجب کنید که آیا این انتخاب خوبی است یا خیر. به هر حال، این یک مدل شبیه‌سازی است، و بنابراین طبیعی به نظر می‌رسد که بازیگران در این شبیه‌سازی به‌عنوان موجودیت‌های مستقل با رفتاری کاملاً تعریف‌شده و وضعیت درونی طراحی شوند. اما فقط برش ها؟!بله، فقط برش ها. من فکر می کنم مهم است که از دام طراحی بیش از حد یا معماری بیش از حد یک راه حل جلوگیری کنیم. به KISS فکر کنید (Keep It Simple, Stupid) - آن را ساده نگه دارید، احمقانه.و، به‌ویژه وقتی وسوسه می‌شوید که لایه‌هایی از انتزاع بسازید، زیرا فکر می‌کنید باید راه‌حل خود را تعمیم دهید تا بتوانید از آن برای مشکلات مشابه در آینده استفاده مجدد کنید، این احتمال وجود دارد که تو به آن نیاز نخواهی داشت.اینجا چه نیازی داریم؟ما به یک حلقه شبیه‌سازی نیاز داریم که به دو عامل اجازه می‌دهد معامله کنند و مقداری ثروت را ببرند یا از دست بدهند. این حلقه را می توان به دو بخش بیشتر تقسیم کرد.ابتدا باید دو عامل را به صورت تصادفی انتخاب کنیم. سپس، دو عامل وارد معامله می شوند و یکی از آنها مقداری ارزش به دست می آورد و دیگری همان مقدار را از دست می دهد. کدام یک از این دو برنده و کدام یک بازنده کاملا تصادفی است. همانطور که در مقاله ساینتیفیک امریکن توضیح داده شد، می‌توانیم یک سکه را برای آن برگردانیم. اما صبر کنید، ما قبلاً این دو نماینده را به صورت تصادفی انتخاب کردیم. بنابراین می توانیم به سادگی تعریف کنیم که اولین انتخاب شده کسی است که ثروت خود را در تجارت از دست می دهد، در حالی که دیگری کسی است که ثروت به دست می آورد. یک قدم کمتر برای مراقبت.سپس، معامله انجام می شود. ما باید مشخص کنیم که کدام یک از این دو عامل فقیرتر از این دو است، زیرا ثروتی که بین نمایندگان منتقل می شود به ثروت عامل فقیرتر (قانون شماره 5) و به مزیتی که به عامل فقیرتر اعطا می شود (قانون شماره 6) بستگی دارد.این تمام چیزی است که نیاز دارد. با این تفاوت که هنوز باید نتیجه را تجسم کنیم! هیچ شبیه سازی خوبی بدون تجسم وجود ندارد.من می توانم به دنبال یک کتابخانه گراف مانند مثل کتابخانه نمایش نمودار gonum بروم و یک هیستوگرام توزیع ثروت را در پایان شبیه سازی ترسیم کنم. اما من واقعاً ترجیح می دهم در حین اجرای شبیه سازی مقداری خروجی را زنده ببینم. و من بسته‌ای می‌خواهم که پیاده‌سازی آن فوق‌العاده آسان باشد و کد را بیهوده افزایش ندهد. یاد مقاله ای در مورد رابط های کاربری مبتنی بر متن افتادم که چندی پیش نوشتم و از بسته های TUI که در آن زمان تست کردم، termui را انتخاب کردم. این یک ویجت نمودار میله ای را ارائه می دهد و می تواند با چند خط کد تنظیم شود.اما با این حال، یک پیچیدگی جزئی وجود دارد که باید به آن پرداخت. termui یک پوشش موقت روی ترمینال ایجاد می کند، مشابه دستور less در سیستم های لینوکسی . هنگامی که برنامه به پایان می رسد، رابط کاربری ناپدید می شود و ترمینال به محتویات قبلی خود برمی گردد. با این حال، من می خواهم نمودار میله ای را پس از پایان حلقه شبیه سازی قابل مشاهده نگه دارم. termui کنترل رویدادهای صفحه کلید را در دست می گیرد، بنابراین من نمی توانم از رویکرد استاندارد os.Signal برای منتظر ماندن Ctrl-C استفاده کنم. در عوض، من از تابع termui’s PollEventsبرای خواندن رویدادهای صفحه کلید و خروج از هر کلیدی استفاده می‌کنم. با فرستادن کانال انجام شده (done channel) نیز به حلقه شبیه سازی، می توانم شبیه سازی را با فشردن کلید در صورتی که برای مدت طولانی اجرا شود قطع کنم.بسیار خوب، نظریه کافی است، این کد است:package main

import (
	&amp;quotfmt&amp;quot
	&amp;quotlog&amp;quot
	&amp;quotmath/rand&amp;quot
	&amp;quottime&amp;quot

	ui &amp;quotgithub.com/gizak/termui/v3&amp;quot
	&amp;quotgithub.com/gizak/termui/v3/widgets&amp;quot
)

const (
	// Number of agents in the market
	numOfAgents = 10
	// Initial amount of money that each agent owns
	initialWealth = 100.0
	// How many trades to simulate
	rounds = 10000
	// If the poorer agent gains wealth it is this percentage of their total wealth.
	percentGain = 0.20
	// If the poorer agent loses wealth, it is this percentage of their total wealth.
	percentLoss = 0.17
)

// Agents are defined by the amount of money, or wealth, they have.
type agents []float64

// pickTwoRandomAgents generates two random numbers `sender` and `receiver` between 0 and numOfAgents-1
// and ensures that `sender` and `receiver` are not equal. (After all, agents would not trade with themselves.)
// Note the use of named return values that saves an extra declaration of `receiver` outside the loop
// (to avoid that `receiver` exists only in the scope of the loop).
func pickTwoRandomAgents() (sender, receiver int) {
	sender = rand.Intn(numOfAgents)
	receiver = sender

	// Generate a random`receiver`. Repeat until `receiver` != `sender`
	for receiver == sender {
		receiver = rand.Intn(numOfAgents)
	}
	return sender, receiver
}

// The trading formula assumes that agents sometimes pay either more or less than the traded good is worth.
// Because of this, wealth flows from one agent to another.
// As both agents `sender`, `receiver` were already chosen randomly, we can decide at this point that agent `sender` always loses
// wealth, and agent `receiver` always gains wealth in this transaction.
// Note: the agents
func trade(a agents, sender, receiver int) {
	// Wealth flows from sender to `receiver` in this transaction.
	// The amount that flows from sender to `receiver` is always a given percentage of the poorer agent.

	// If`receiver` is the poorer agent, the gain is `percentGain` of `receiver`&#039;s total wealth.
	transfer := a[receiver] * percentGain

	// If `sender` is the poorer agent, the loss is `percentLoss` of `sender`&#039;s total wealth.
	if a[sender] &lt; a[receiver] {
		transfer = a[sender] * percentLoss
	}
	// It&#039;s a deal!
	a[sender] -= transfer
	a[receiver] += transfer
}

// Draw a bar chart of the current wealth of all agents
func drawChart(a agents, bc *widgets.BarChart) {
	bc.Data = a
	// Scale the bar chart dynamically, to better see
	// the distribution when the current maximum wealth is
	// much smaller than the maximum possible wealth.
	maxPossibleWealth := initialWealth * numOfAgents
	currentMaxWealth, _ := ui.GetMaxFloat64FromSlice(a)
	bc.MaxVal = currentMaxWealth + (maxPossibleWealth-currentMaxWealth)*0.05
	ui.Render(bc)
}

// Run the simulation
func run(a agents, bc *widgets.BarChart, done &lt;-chan struct{}) {
	for n := 0; n &lt; rounds; n++ {
		// Pick two different agents.
		sender, receiver := pickTwoRandomAgents()
		// Have them do a trade.
		trade(a, sender, receiver)
		// Update the chart
		drawChart(a, bc)
		// Try to read a value from channel `done`.
		// The read shall not block, hence it is enclosed in a
		// select block with a default clause.
		select {
		case &lt;-done:
			// At this point, the done channel has unblocked and emitted a zero value. Leave the simulation loop.
			return
		default:
		}
	}
}

func main() {
	// Setup

	// Pre-allocate the slice, to avoid allocations during the simulation
	a := make(agents, numOfAgents)

	// Set a random seed
	rand.Seed(time.Now().UnixNano())

	for i := range a {
		// All agents start with the same amount of money.
		a[i] = initialWealth
	}

	// UI setup. `gizak/termui` makes rendering a bar chart in a terminal super easy.
	err := ui.Init()
	if err != nil {
		log.Fatalln(err)
	}
	defer ui.Close()
	bc := widgets.NewBarChart()
	bc.Title = &amp;quotAgents&#039; Wealth&amp;quot
	bc.BarWidth = 5
	bc.SetRect(5, 5, 10+(bc.BarWidth+1)*numOfAgents, 25)
	bc.LabelStyles = []ui.Style{ui.NewStyle(ui.ColorBlue)}
	bc.NumStyles = []ui.Style{ui.NewStyle(ui.ColorBlack)}
	bc.NumFormatter = func(n float64) string {
		return fmt.Sprintf(&amp;quot%3.1f&amp;quot, n)
	}
	// Start rendering.
	ui.Render(bc)

	// `termui` has its own event polling.
	// We use this here to watch for a key press
	// to end the simulation
	done := make(chan struct{})
	go func(done chan&lt;- struct{}) {
		for e := range ui.PollEvents() {
			if e.Type == ui.KeyboardEvent {
				// Unblock the channel by closing it
				// After closing the channel, it emits zero values upon reading.
				close(done)
				return
			}
		}
	}(done)

	// Start the simulation!
	run(a, bc, done)

	// After the simulation, wait for a key press
	// so that the final chart remains visible.
	&lt;-done
}نحوه دریافت و اجرای کدبرای اجرای مستقیم کد، اجرا کنیدgo install github.com/appliedgo/rich@latestاین دستور پروژه را در قسمت کش ماژول دانلود می کند، آن را کامپایل می کند و یک باینری به نام rich را در $(go env GOBIN) یا $(go env GOPATH)/bin اگر GOBIN تنظیم نشده است قرار می دهد. می توانید با فراخوانی rich در اعلان شل (shell)، باینری را اجرا کنید.با این حال، بازی با کد سرگرم کننده تر است. برای انجام این کار، پروژه را روی دیسک خود کلون (clone) کنید و آن را به صورت محلی اجرا کنید:سپس در سورس کد منبع  قرار گیرید (با دستور cd)، وابستگی ها را دریافت کنید، کد را به دلخواه تغییر دهید و آن را اجرا کنید:cd rich 
go run rich.go(اگر خطاهایی در مورد عدم وابستگی دریافت کردید، مطمئن شوید که محیط Go شما در حالت Go Modules است و go mod tidy یا go mod download را اجرا کنید.)پارامترهایی مانند درصد برد یا باخت، تعداد نمایندگان یا ثروت اولیه را تغییر دهید و ببینید نتایج چگونه تغییر می کند.به عنوان یک چالش اضافی، نمودار میله ای را تغییر دهید تا توزیع ثروت را در سطل به جای نمایندگان فردی نشان دهد. سپس شبیه سازی را با مثلاً 1000 عامل در 100000 دور اجرا کنید.توجه داشته باشید که به دلیل الزامات بسته termui، کد در Go Playground اجرا نمی شود.درس های آموخته شدهبرای سیاستمداران، اقتصاددانان و همه کسانی که با فقر مبارزه می کنند:با چند خط کد، نشان داده‌ایم که ثروت همیشه از فقرا به ثروتمندان سرازیر می‌شود، مهم نیست که همه مهارت‌های معاملاتی یکسانی داشته باشند. به نظر می رسد برخی افراد خوش شانس هستند و در ابتدا مقداری ثروت به دست می آورند، که پس از آن جمع آوری ثروت بیشتر را آسان تر می کند. مطمئنا، دنیای واقعی بسیار پیچیده تر و متنوع تر است. اما این مدل ساده است، اما نشان می دهد که نابرابری شدید ثروت نمی تواند ناشی از چیزی جز مکانیسم های اساسی بازار آزاد باشد، حتی در غیاب بازیگران حریص و بد اندیش.برای Gopher ها:یک حلقه کوچک و یک بسته تجسمی تمام چیزی است که برای نوشتن شبیه سازی نیاز دارید. دفعه بعد که با یک مدل تکراری واقعیت مواجه شدید، یک بسته UI/graph/plot بگیرید، یک حلقه بنویسید و فرضیه پشت مدل را تأیید کنید.Happy coding!منبع</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Mon, 17 Oct 2022 21:21:53 +0330</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت هفتم (شبکه)</title>
                <link>https://virgool.io/Solidity/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D9%87%D9%81%D8%AA%D9%85-%D8%B4%D8%A8%DA%A9%D9%87-rutd1lpnwbmr</link>
                <description>مقدمهتا کنون، ما یک بلاکچین ساخته‌ایم که همه ویژگی‌های کلیدی را دارد: آدرس‌های ناشناس، امن و به‌طور تصادفی تولید شده. ذخیره سازی داده های بلاکچین؛ سیستم اثبات کار؛ روشی قابل اعتماد برای ذخیره تراکنش ها در حالی که این ویژگی ها بسیار مهم هستند، کافی نیست. چیزی که باعث می‌شود این ویژگی‌ها واقعاً بدرخشند و رمزارزها را ممکن می‌سازد، شبکه است. اجرای چنین پیاده‌سازی بلاکچین فقط روی یک رایانه چه فایده ای دارد؟ وقتی فقط یک کاربر وجود دارد، این ویژگی‌های مبتنی بر رمزنگاری چه کاربردی دارند؟ این شبکه است که باعث می شود همه این مکانیسم ها کار کنند و مفید باشند.شما می توانید آن ویژگی های بلاکچین را به عنوان قوانینی در نظر بگیرید، شبیه به قوانینی که افراد زمانی که می خواهند با هم زندگی کنند و پیشرفت کنند، وضع می کنند. نوعی ترتیبات اجتماعی. شبکه بلاکچین جامعه ای از برنامه هاست که از قوانین مشابهی پیروی می کنند و این پیروی از قوانین است که شبکه را زنده می کند. به همین ترتیب، وقتی مردم ایده‌های یکسانی دارند، قوی‌تر می‌شوند و می‌توانند با هم زندگی بهتری بسازند. اگر افرادی وجود داشته باشند که از مجموعه قوانین متفاوتی پیروی کنند، در یک جامعه جداگانه (ایالت، مزرعه اشتراکی  و غیره) زندگی خواهند کرد. به طور مشابه، اگر گره های بلاکچینی وجود داشته باشند که از قوانین متفاوتی پیروی می کنند، یک شبکه جداگانه تشکیل می دهند.این بسیار مهم است: بدون شبکه و بدون اکثریت گره هایی که قوانین یکسانی را به اشتراک می گذارند، این قوانین بی فایده هستند!سلب مسئولیت: متأسفانه، من زمان کافی برای پیاده سازی یک نمونه اولیه شبکه P2P واقعی را نداشتم. در این مقاله من رایج ترین سناریو را نشان خواهم داد که شامل گره هایی از انواع مختلف است. بهبود این سناریو و تبدیل آن به یک شبکه P2P می تواند چالش و تمرین خوبی برای شما باشد! همچنین من نمی توانم تضمین کنم که سناریوهای دیگر به جز آنچه در این مقاله اجرا شده است، کار می کنند. متاسفم!این بخش تغییرات قابل توجهی را در کد ایجاد می کند، بنابراین توضیح همه آنها در اینجا بی معنی است. لطفاً برای مشاهده تمام تغییرات از آخرین مقاله به این صفحه مراجعه کنید.شبکه بلاکچینشبکه بلاکچین غیرمتمرکز است، به این معنی که هیچ سروری وجود ندارد که کارها را انجام دهد و همین طور کلاینت هایی که از سرورها برای دریافت یا پردازش داده ها استفاده می کنند. در شبکه بلاکچین گره هایی وجود دارد و هر گره عضوی کامل از شبکه است. یک گره همه چیز است: هم مشتری و هم سرور است. این بسیار مهم است که به خاطر داشته باشید، زیرا با برنامه های وب معمولی بسیار متفاوت است.شبکه بلاکچین یک شبکه P2P (Peer-to-Peer) است، به این معنی که گره ها مستقیماً به یکدیگر متصل می شوند. توپولوژی آن مسطح (flat) است، زیرا هیچ سلسله مراتبی در نقش های گره وجود ندارد. در اینجا نمایش شماتیک آن:گره ها در چنین شبکه ای برای پیاده سازی دشوارتر هستند، زیرا آنها باید عملیات زیادی را انجام دهند. هر گره باید با چندین گره دیگر تعامل داشته باشد، باید وضعیت گره دیگر را درخواست کند، آن را با وضعیت خود مقایسه کند و زمانی که قدیمی است وضعیت خود را به روز کند.نقش های گرهگره های بلاکچین علیرغم کامل بودن، می توانند نقش های متفاوتی را در شبکه ایفا کنند. اینجا اند:    ۱- معدن کار.    چنین گره هایی بر روی سخت افزار قدرتمند یا تخصصی (مانند ASIC) اجرا می شوند و تنها هدف آنها استخراج بلوک های جدید در سریع ترین زمان ممکن است. ماینرها فقط در بلاکچین هایی که از Proof-of-Work استفاده می کنند امکان پذیر است، زیرا ماینینگ در واقع به معنای حل پازل های PoW است. برای مثال، در بلاکچین های Proof-of-Stake، ماینینگ وجود ندارد.    ۲- گره کامل    این گره ها بلوک های استخراج شده توسط ماینرها را تایید می کنند و تراکنش ها را تایید می کنند. برای انجام این کار، آنها باید کل نسخه بلاکچین را داشته باشند. همچنین، چنین گره هایی چنین عملیات مسیریابی را انجام می دهند، مانند کمک به سایر گره ها برای کشف یکدیگر.    برای شبکه بسیار مهم است که تعداد زیادی گره کامل داشته باشد، زیرا این گره ها هستند که تصمیم می گیرند: آنها تصمیم می گیرند که آیا یک بلوک یا تراکنش معتبر است یا خیر.۳- گره  SPV.این SPV مخفف عبارت Simplified Payment Verification است. این گره‌ها یک کپی کامل از بلاکچین را ذخیره نمی‌کنند، اما همچنان می‌توانند تراکنش‌ها را تأیید کنند (نه همه آن‌ها، بلکه یک زیرمجموعه، برای مثال، آنهایی که به آدرس خاصی ارسال شده‌اند). یک گره SPV به یک گره کامل برای دریافت داده بستگی دارد، و ممکن است گره های SPV زیادی به یک گره کامل متصل شوند. SPV برنامه های کیف پول را ممکن می کند: نیازی به دانلود کامل بلاکچین نیست، اما همچنان می تواند تراکنش های خود را تأیید کند.ساده سازی شبکهبرای پیاده سازی شبکه در بلاکچین، باید مواردی را ساده کنیم. مشکل این است که ما کامپیوترهای زیادی برای شبیه سازی یک شبکه با چندین گره نداریم. ما می‌توانستیم از ماشین‌های مجازی یا داکر برای حل این مشکل استفاده کنیم، اما می‌تواند همه چیز را دشوارتر کند: شما باید مشکلات احتمالی ماشین مجازی یا داکر را حل کنید، در حالی که هدف من تمرکز بر پیاده‌سازی بلاکچین است. بنابراین، ما می خواهیم چندین گره بلاک چین را روی یک ماشین اجرا کنیم و در همان زمان می خواهیم آدرس های متفاوتی داشته باشند. برای رسیدن به این هدف، به جای آدرس های IP، از پورت ها به عنوان شناسه گره ها استفاده می کنیم. به عنوان مثال، گره هایی با آدرس های: 127.0.0.1:3000، 127.0.0.1:3001، 127.0.0.1:3002 و غیره وجود خواهند داشت. ما شناسه گره پورت را فراخوانی می کنیم و از متغیر محیطی NODE_ID برای تنظیم آنها استفاده می کنیم. بنابراین، می توانید چندین پنجره ترمینال را باز کنید، NODE_ID های مختلف را تنظیم کنید و گره های مختلفی در حال اجرا داشته باشید.این رویکرد همچنین مستلزم داشتن بلاکچین ها و فایل های کیف پول متفاوت است. آنها اکنون باید به شناسه گره وابسته باشند و مانند blockchain_3000.db، blockchain_30001.db و wallet_3000.db، wallet_30001.db، و غیره نامگذاری شوند.پیاده سازیبنابراین، چه اتفاقی می افتد که مثلاً بیت کوین Core را دانلود کرده و برای اولین بار اجرا می کنید؟ برای دانلود آخرین وضعیت بلاکچین، باید به یک گره متصل شود. با توجه به اینکه رایانه شما از همه یا برخی از گره های بیت کوین آگاه نیست، این گره چیست؟هاردکد کردن آدرس گره در بیت کوین Core یک اشتباه است: گره ممکن است مورد حمله قرار گیرد یا بسته شود، که می تواند منجر به عدم امکان پیوستن گره های جدید به شبکه شود. در عوض، در Bitcoin Core، دانه های DNS هاردکد شده وجود دارد. اینها گره نیستند، بلکه سرورهای DNS هستند که آدرس برخی از گره ها را می دانند. هنگامی که یک هسته بیت کوین تمیز را راه اندازی می کنید، به یکی از دانه ها متصل می شود و لیستی از گره های کامل را دریافت می کند که سپس بلاکچین را از آن دانلود می کند.در اجرای ما، متمرکز سازی (centralization) وجود خواهد داشت. ما سه گره خواهیم داشت:    ۱- گره مرکزی. این همان گرهی است که تمام گره های دیگر به آن متصل می شوند و این گره ای است که داده ها را بین گره های دیگر ارسال می کند.    ۲- یک گره ماینر. این گره تراکنش های جدید را در mempool ذخیره می کند و زمانی که تراکنش های کافی وجود داشته باشد، یک بلوک جدید استخراج می کند.    ۳- یک گره کیف پول این گره برای ارسال سکه بین کیف پول ها استفاده خواهد شد. برخلاف گره‌های SPV، یک نسخه کامل از بلاک چین را ذخیره می‌کند.سناریوهدف این مقاله اجرای سناریوی زیر است:     ۱- گره مرکزی یک بلاکچین ایجاد می کند.     ۲- گره دیگر (کیف پول) به آن متصل شده و بلاک چین را دانلود می کند.     ۳- یک گره دیگر (ماینر) به گره مرکزی متصل می شود و بلاک چین را دانلود می کند.     ۴- گره کیف پول یک تراکنش ایجاد می کند.     ۵- گره های ماینر تراکنش را دریافت کرده و آن را در حوضه حافظه خود نگه می دارند.     ۶- هنگامی که تراکنش های کافی در استخر حافظه وجود دارد، ماینر شروع به استخراج یک بلوک جدید می کند.     ۷- هنگامی که یک بلوک جدید استخراج می شود، به گره مرکزی ارسال می شود.     ۸- گره کیف پول با گره مرکزی همگام می شود.     ۹- کاربر گره کیف پول چک می کند که پرداخت آنها موفقیت آمیز بوده است.این چیزی است که در بیت کوین به نظر می رسد. حتی اگر قرار نیست یک شبکه P2P واقعی بسازیم، یک مورد استفاده واقعی و اصلی و مهم بیت کوین را پیاده سازی خواهیم کرد.نسخه یا versionگره ها از طریق پیام ها ارتباط برقرار می کنند. هنگامی که یک گره جدید اجرا می شود، چندین گره از یک دانه DNS دریافت می کند و پیام نسخه یا ورژن را برای آنها ارسال می کند که در پیاده سازی ما به شکل زیر خواهد بود:type version struct {
    Version    int
    BestHeight int
    AddrFrom   string
}ما فقط یک نسخه بلاک چین داریم، بنابراین قسمت Version هیچ اطلاعات مهمی را حفظ نخواهد کرد. BestHeight طول بلاکچین گره را ذخیره می کند. AddFrom آدرس فرستنده را ذخیره می کند.گره ای که پیام نسخه یا ورژن را دریافت می کند چه کاری باید انجام دهد؟ با پیام نسخه خودش پاسخ خواهد داد. این یک نوع دست دادن است: هیچ تعامل دیگری بدون احوالپرسی قبلی امکان پذیر نیست. اما این فقط ادب نیست: نسخه برای یافتن یک بلاک چین طولانی تر استفاده می شود. هنگامی که یک گره یک پیام نسخه دریافت می کند، بررسی می کند که آیا بلاک چین گره از مقدار BestHeight طولانی تر است یا خیر. اگر اینطور نباشد، گره بلوک های گمشده را درخواست و دانلود می کند.برای دریافت پیام به سرور نیاز داریم:var nodeAddress string
var knownNodes = []string{&amp;quotlocalhost:3000&amp;quot}

func StartServer(nodeID, minerAddress string) {
    nodeAddress = fmt.Sprintf(&amp;quotlocalhost:%s&amp;quot, nodeID)
    miningAddress = minerAddress
    ln, err := net.Listen(protocol, nodeAddress)
    defer ln.Close()

    bc := NewBlockchain(nodeID)

    if nodeAddress != knownNodes[0] {
        sendVersion(knownNodes[0], bc)
    }

    for {
        conn, err := ln.Accept()
        go handleConnection(conn, bc)
    }
}ابتدا آدرس گره مرکزی را هاردکد می کنیم: هر گره باید ابتدا بداند که به کجا متصل شود. آرگومان minerAddress آدرسی را برای دریافت پاداش استخراج مشخص می کند. این قطعه:if nodeAddress != knownNodes[0] {
    sendVersion(knownNodes[0], bc)
}به این معنی که اگر گره فعلی، گره مرکزی نیست، باید پیام نسخه را به گره مرکزی ارسال کند تا متوجه شود که آیا بلاک چین قدیمی است یا خیر.func sendVersion(addr string, bc *Blockchain) {
    bestHeight := bc.GetBestHeight()
    payload := gobEncode(version{nodeVersion, bestHeight, nodeAddress})

    request := append(commandToBytes(&amp;quotversion&amp;quot), payload...)

    sendData(addr, request)
}پیام های ما، در سطح پایین تر، دنباله ای از بایت هستند. 12 بایت اول نام فرمان (&quot;نسخه&quot; در این مورد) را مشخص می کند، و بایت های دوم حاوی ساختار پیام با gob-encoded هستند. commandToBytes به شکل زیر است:func commandToBytes(command string) []byte {
    var bytes [commandLength]byte

    for i, c := range command {
        bytes[i] = byte(c)
    }

    return bytes[:]
}یک بافر 12 بایتی ایجاد می کند و آن را با نام فرمان پر می کند و باقی بایت های را خالی می گذارد. یک تابع مخالف وجود دارد:func bytesToCommand(bytes []byte) string {
    var command []byte

    for _, b := range bytes {
        if b != 0x0 {
            command = append(command, b)
        }
    }

    return fmt.Sprintf(&amp;quot%s&amp;quot, command)
}هنگامی که یک گره دستوری را دریافت می کند، bytesToCommand را برای استخراج نام فرمان اجرا می کند و بدنه فرمان را با کنترل کننده صحیح پردازش می کند:func handleConnection(conn net.Conn, bc *Blockchain) {
    request, err := ioutil.ReadAll(conn)
    command := bytesToCommand(request[:commandLength])
    fmt.Printf(&amp;quotReceived %s command\n&amp;quot, command)

    switch command {
    ...
    case &amp;quotversion&amp;quot:
        handleVersion(request, bc)
    default:
        fmt.Println(&amp;quotUnknown command!&amp;quot)
    }

    conn.Close()
}بسیار خوب، این چیزی است که کنترل کننده فرمان نسخه یا ورژن به نظر می رسد:func handleVersion(request []byte, bc *Blockchain) {
    var buff bytes.Buffer
    var payload verzion

    buff.Write(request[commandLength:])
    dec := gob.NewDecoder(&amp;buff)
    err := dec.Decode(&amp;payload)

    myBestHeight := bc.GetBestHeight()
    foreignerBestHeight := payload.BestHeight

    if myBestHeight &lt; foreignerBestHeight {
        sendGetBlocks(payload.AddrFrom)
    } else if myBestHeight &gt; foreignerBestHeight {
        sendVersion(payload.AddrFrom, bc)
    }

    if !nodeIsKnown(payload.AddrFrom) {
        knownNodes = append(knownNodes, payload.AddrFrom)
    }
}ابتدا باید درخواست را رمزگشایی کرده و payload را استخراج کنیم. این شبیه به همه کنترل‌کننده‌ها است، بنابراین من این قطعه را در کدهای آتی حذف خواهم کرد.سپس یک گره BestHeight خود را با یکی از پیام مقایسه می کند. اگر زنجیره بلاک گره طولانی تر باشد، با پیام نسخه پاسخ می دهد. در غیر این صورت، پیام getblock ارسال می کند.ساختار getblockstype getblocks struct {
    AddrFrom string
}این getblocks به معنای «به من نشان دهید چه بلاک هایی دارید» (در بیت کوین، پیچیده تر است). توجه کنید، نمی‌گوید «همه بلوک‌هایتان را به من بدهید»، در عوض فهرستی از هش‌های بلاک را درخواست می‌کند. این کار برای کاهش بار شبکه انجام می شود، زیرا بلوک ها را می توان از گره های مختلف دانلود کرد و ما نمی خواهیم ده ها گیگابایت از یک گره دانلود کنیم.مدیریت دستور به آسانی:func handleGetBlocks(request []byte, bc *Blockchain) {
    ...
    blocks := bc.GetBlockHashes()
    sendInv(payload.AddrFrom, &amp;quotblock&amp;quot, blocks)
}در پیاده سازی ساده ما، همه هش های بلوک را برمی گرداند.ساختارinvtype inv struct {
    AddrFrom string
    Type     string
    Items    [][]byte
}بیت کوین از inv استفاده می کند تا به سایر گره ها نشان دهد که گره فعلی چه بلوک ها یا تراکنش هایی دارد. باز هم، شامل بلوک‌ها و تراکنش‌های کامل نیست، فقط هش‌های آنها را شامل می‌شود. فیلد Type می گوید که آیا اینها بلوک هستند یا تراکنش.مدیریت inv دشوارتر است:func handleInv(request []byte, bc *Blockchain) {
    ...
    fmt.Printf(&amp;quotRecevied inventory with %d %s\n&amp;quot, len(payload.Items), payload.Type)

    if payload.Type == &amp;quotblock&amp;quot {
        blocksInTransit = payload.Items

        blockHash := payload.Items[0]
        sendGetData(payload.AddrFrom, &amp;quotblock&amp;quot, blockHash)

        newInTransit := [][]byte{}
        for _, b := range blocksInTransit {
            if bytes.Compare(b, blockHash) != 0 {
                newInTransit = append(newInTransit, b)
            }
        }
        blocksInTransit = newInTransit
    }

    if payload.Type == &amp;quottx&amp;quot {
        txID := payload.Items[0]

        if mempool[hex.EncodeToString(txID)].ID == nil {
            sendGetData(payload.AddrFrom, &amp;quottx&amp;quot, txID)
        }
    }
}اگر هش بلوک ها منتقل شوند، می خواهیم آنها را در متغیر blocksInTransit ذخیره کنیم تا بلوک های دانلود شده را ردیابی کنیم. این به ما امکان می دهد بلوک ها را از گره های مختلف دانلود کنیم. بلافاصله پس از قرار دادن بلوک ها در حالت ترانزیت، دستور getdata را به فرستنده پیام inv ارسال می کنیم و blocksInTransit را به روز می کنیم. در یک شبکه P2P واقعی، ما می خواهیم بلوک ها را از گره های مختلف منتقل کنیم.در اجرای خود، هرگز inv را با هش های متعدد ارسال نمی کنیم. به همین دلیل است که وقتی payload.Type == &quot;tx&quot; فقط اولین هش گرفته می شود. سپس بررسی می کنیم که آیا از قبل هش را در mempool خود داریم یا خیر، و اگر نه، پیام getdata ارسال می شود.ساختار getdatatype getdata struct {
    AddrFrom string
    Type     string
    ID       []byte
}این getdata یک درخواست برای بلوک یا تراکنش خاص است و می تواند فقط یک شناسه بلوک/تراکنش داشته باشد.func handleGetData(request []byte, bc *Blockchain) {
    ...
    if payload.Type == &amp;quotblock&amp;quot {
        block, err := bc.GetBlock([]byte(payload.ID))

        sendBlock(payload.AddrFrom, &amp;block)
    }

    if payload.Type == &amp;quottx&amp;quot {
        txID := hex.EncodeToString(payload.ID)
        tx := mempool[txID]

        sendTx(payload.AddrFrom, &amp;tx)
    }
}کنترل کننده ساده است: اگر آنها درخواست بلوک کردند، بلوک را برگردانید. اگر آنها درخواست معامله کردند، تراکنش را برگردانید. توجه داشته باشید که ما بررسی نمی کنیم که آیا واقعاً این بلوک یا تراکنش را داریم یا خیر. این یک عیب است :)بلوک و txtype block struct {
    AddrFrom string
    Block    []byte
}

type tx struct {
    AddFrom     string
    Transaction []byte
}این پیام ها هستند که در واقع داده ها را منتقل می کنند.مدیریت پیام بلوک آسان است:func handleBlock(request []byte, bc *Blockchain) {
    ...

    blockData := payload.Block
    block := DeserializeBlock(blockData)

    fmt.Println(&amp;quotRecevied a new block!&amp;quot)
    bc.AddBlock(block)

    fmt.Printf(&amp;quotAdded block %x\n&amp;quot, block.Hash)

    if len(blocksInTransit) &gt; 0 {
        blockHash := blocksInTransit[0]
        sendGetData(payload.AddrFrom, &amp;quotblock&amp;quot, blockHash)

        blocksInTransit = blocksInTransit[1:]
    } else {
        UTXOSet := UTXOSet{bc}
        UTXOSet.Reindex()
    }
}وقتی یک بلوک جدید دریافت کردیم، آن را در بلاکچین خود قرار می دهیم. اگر بلوک های بیشتری برای دانلود وجود دارد، آنها را از همان گره ای که بلوک قبلی دانلود کرده بودیم درخواست می کنیم. هنگامی که ما در نهایت همه بلوک ها را دانلود کردیم، مجموعه UTXO مجددا ایندکس می شود.یک TODO: به جای اعتماد بی قید و شرط، باید هر بلوک ورودی را قبل از اضافه کردن آن به بلاک چین اعتبارسنجی کنیم.یک TODOدیگر: به جای اجرای UTXOSet.Reindex()، باید از UTXOSet.Update(block) استفاده شود، زیرا اگر بلاک چین بزرگ باشد، فهرست مجدد کل مجموعه UTXO به زمان زیادی نیاز دارد.مدیریت پیام های tx سخت ترین بخش است:func handleTx(request []byte, bc *Blockchain) {
    ...
    txData := payload.Transaction
    tx := DeserializeTransaction(txData)
    mempool[hex.EncodeToString(tx.ID)] = tx

    if nodeAddress == knownNodes[0] {
        for _, node := range knownNodes {
            if node != nodeAddress &amp;&amp; node != payload.AddFrom {
                sendInv(node, &amp;quottx&amp;quot, [][]byte{tx.ID})
            }
        }
    } else {
        if len(mempool) &gt;= 2 &amp;&amp; len(miningAddress) &gt; 0 {
        MineTransactions:
            var txs []*Transaction

            for id := range mempool {
                tx := mempool[id]
                if bc.VerifyTransaction(&amp;tx) {
                    txs = append(txs, &amp;tx)
                }
            }

            if len(txs) == 0 {
                fmt.Println(&amp;quotAll transactions are invalid! Waiting for new ones...&amp;quot)
                return
            }

            cbTx := NewCoinbaseTX(miningAddress, &amp;quot&amp;quot)
            txs = append(txs, cbTx)

            newBlock := bc.MineBlock(txs)
            UTXOSet := UTXOSet{bc}
            UTXOSet.Reindex()

            fmt.Println(&amp;quotNew block is mined!&amp;quot)

            for _, tx := range txs {
                txID := hex.EncodeToString(tx.ID)
                delete(mempool, txID)
            }

            for _, node := range knownNodes {
                if node != nodeAddress {
                    sendInv(node, &amp;quotblock&amp;quot, [][]byte{newBlock.Hash})
                }
            }

            if len(mempool) &gt; 0 {
                goto MineTransactions
            }
        }
    }
}اولین کاری که باید انجام دهید این است که تراکنش جدید را در mempool قرار دهید (باز هم، تراکنش ها باید قبل از قرار گرفتن در mempool تأیید شوند). قطعه بعدی:if nodeAddress == knownNodes[0] {
    for _, node := range knownNodes {
        if node != nodeAddress &amp;&amp; node != payload.AddFrom {
            sendInv(node, &amp;quottx&amp;quot, [][]byte{tx.ID})
        }
    }
}بررسی می کند که آیا گره فعلی گره مرکزی است یا خیر. در پیاده سازی ما، گره مرکزی بلوک ها را استخراج نمی کند. در عوض، تراکنش‌های جدید را به گره‌های دیگر شبکه ارسال می‌کند.قطعه بزرگ بعدی فقط برای گره های ماینر است. بیایید آن را به قطعات کوچکتر تقسیم کنیم:if len(mempool) &gt;= 2 &amp;&amp; len(miningAddress) &gt; 0 {این miningAddress فقط روی گره‌های ماینر تنظیم می‌شود. هنگامی که 2 یا بیشتر تراکنش در ممپول گره فعلی (ماینر) وجود دارد، استخراج شروع می شود.for id := range mempool {
    tx := mempool[id]
    if bc.VerifyTransaction(&amp;tx) {
        txs = append(txs, &amp;tx)
    }
}

if len(txs) == 0 {
    fmt.Println(&amp;quotAll transactions are invalid! Waiting for new ones...&amp;quot)
    return
}ابتدا، تمام تراکنش‌های موجود در mempool تأیید می‌شوند. تراکنش های نامعتبر نادیده گرفته می شوند و در صورت عدم وجود تراکنش های معتبر، استخراج قطع می شود.cbTx := NewCoinbaseTX(miningAddress, &amp;quot&amp;quot)
txs = append(txs, cbTx)

newBlock := bc.MineBlock(txs)
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()

fmt.Println(&amp;quotNew block is mined!&amp;quot)تراکنش های تایید شده در یک بلوک و همچنین یک تراکنش کوین بیس همراه با پاداش قرار می گیرند. پس از استخراج بلوک، مجموعه UTXO دوباره ایندکس می شود.یک TODO: دوباره باید از UTXOSet.Update به جای UTXOSet.Reindex استفاده شود.for _, tx := range txs {
    txID := hex.EncodeToString(tx.ID)
    delete(mempool, txID)
}

for _, node := range knownNodes {
    if node != nodeAddress {
        sendInv(node, &amp;quotblock&amp;quot, [][]byte{newBlock.Hash})
    }
}

if len(mempool) &gt; 0 {
    goto MineTransactions
}پس از استخراج تراکنش، از mempool حذف می شود. هر گره دیگری که گره فعلی از آن آگاه است، پیام inv را با هش بلوک جدید دریافت می کند. آنها می توانند پس از رسیدگی به پیام، بلوک را درخواست کنند.نتیجهبیایید سناریویی را که قبلا تعریف کرده بودیم بازی کنیم.ابتدا NODE_ID را روی 3000 تنظیم کنید (export NODE_ID=3000) در اولین پنجره ترمینال. قبل از پاراگراف‌های بعدی از نشان‌هایی مانند NODE 3000 یا NODE 3001 استفاده خواهم کرد تا بدانید روی چه گره‌ای باید اقدامات انجام دهید.نود 3000یک کیف پول و یک بلاک چین جدید بسازید:$ blockchain_go createblockchain -address CENTREAL_NODE(برای وضوح و اختصار از آدرس های جعلی استفاده خواهم کرد)پس از آن، زنجیره بلوکی حاوی بلوک تک تک پیدایش خواهد بود. ما باید بلوک را ذخیره کنیم و از آن در گره های دیگر استفاده کنیم. بلوک‌های Genesis به عنوان شناسه‌های زنجیره‌های بلوکی عمل می‌کنند (در بیت‌کوین کور، بلوک پیدایش کدگذاری شده است).$ cp blockchain_3000.db blockchain_genesis.dbNODE 3001بعد، یک پنجره ترمینال جدید باز کنید و شناسه گره را روی 3001 تنظیم کنید. این یک گره کیف پول خواهد بود. برخی از آدرس‌ها را با بلاکچین_گو کیف پول ایجاد کنید، این آدرس‌ها را WALLET_1، WALLET_2، WALLET_3 می‌نامیم.نود 3000چند سکه به آدرس های کیف پول ارسال کنید:$ blockchain_go send -from CENTREAL_NODE -to WALLET_1 -amount 10 -mine$ blockchain_go send -from CENTREAL_NODE -to WALLET_2 -amount 10 -mineپرچم mine- به این معنی است که بلوک بلافاصله توسط همان گره استخراج می شود. ما باید این پرچم را داشته باشیم زیرا در ابتدا هیچ گره ماینری در شبکه وجود ندارد.گره را شروع کنید:$ blockchain_go startnodeگره باید تا پایان سناریو در حال اجرا باشد.گره 3001بلاک چین گره را با بلوک پیدایش ذخیره شده در بالا شروع کنید:$ cp blockchain_genesis.db blockchain_3001.dbگره را اجرا کنید$ blockchain_go startnodeتمام بلوک ها را از گره مرکزی دانلود می کند. برای بررسی اینکه همه چیز درست است، گره را متوقف کنید و تعادل را بررسی کنید:$ blockchain_go getbalance -address WALLET_1Balance of &#x27;WALLET_1&#x27;: 10$ blockchain_go getbalance -address WALLET_2Balance of &#x27;WALLET_2&#x27;: 10همچنین، می توانید تعادل آدرس CENTRAL_NODE را بررسی کنید، زیرا گره 3001 اکنون زنجیره بلوکی خود را دارد:$ blockchain_go getbalance -address CENTRAL_NODEBalance of &#x27;CENTRAL_NODE&#x27;: 10گره  3002یک پنجره ترمینال جدید باز کنید و شناسه آن را روی 3002 تنظیم کنید و یک کیف پول ایجاد کنید. این یک گره ماینر خواهد بود. بلاک چین را راه اندازی کنید:$ cp blockchain_genesis.db blockchain_3002.dbو گره را شروع کنید:$ blockchain_go startnode -miner MINER_WALLETگره  3001ارسال چند سکه:$ blockchain_go send -from WALLET_1 -to WALLET_3 -amount 1$ blockchain_go send -from WALLET_2 -to WALLET_4 -amount 1گره 3002به سرعت! به گره ماینر بروید و آن را در حال استخراج یک بلوک جدید ببینید! همچنین خروجی گره مرکزی را بررسی کنید.گره 3001به گره کیف پول بروید و آن را شروع کنید:$ blockchain_go startnodeبلوک تازه استخراج شده را دانلود می کند!آن را متوقف کنید و تعادل را بررسی کنید:$ blockchain_go getbalance -address WALLET_1Balance of &#x27;WALLET_1&#x27;: 9$ blockchain_go getbalance -address WALLET_2Balance of &#x27;WALLET_2&#x27;: 9$ blockchain_go getbalance -address WALLET_3Balance of &#x27;WALLET_3&#x27;: 1$ blockchain_go getbalance -address WALLET_4Balance of &#x27;WALLET_4&#x27;: 1$ blockchain_go getbalance -address MINER_WALLETBalance of &#x27;MINER_WALLET&#x27;: 10و تمامنتیجه گیریاین قسمت پایانی سریال بود. من می‌توانستم چند پست دیگر در مورد اجرای یک نمونه اولیه واقعی از یک شبکه P2P منتشر کنم، اما برای این کار وقت ندارم. امیدوارم این مقاله به برخی از سؤالات شما در مورد فناوری بیت کوین پاسخ دهد و سؤالات جدیدی را مطرح کند، که می توانید پاسخ آنها را خودتان بیابید. چیزهای جالب تری در فناوری بیت کوین پنهان شده است! موفق باشید!پینوشت : همانطور که در پروتکل شبکه بیت کوین توضیح داده شده است، می توانید با پیاده سازی پیام addr، بهبود شبکه را شروع کنید (لینک در زیر). این یک پیام بسیار مهم است، زیرا به گره ها اجازه می دهد یکدیگر را کشف کنند. من شروع به اجرای آن کردم، اما تمام نشده است!Source codesBitcoin protocol documentationBitcoin networkپایان قسمت هفتمباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Wed, 15 Jun 2022 19:06:50 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت ششم (تراکنش ها-۲)</title>
                <link>https://virgool.io/Solidity/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D8%B4%D8%B4%D9%85-%D8%AA%D8%B1%D8%A7%DA%A9%D9%86%D8%B4-%D9%87%D8%A7-%DB%B2-rfrlgdcrfbv9</link>
                <description>مقدمهدر همان قسمت اول این مجموعه گفتم که بلاکچین یک پایگاه داده توزیع شده است. در آن زمان، تصمیم گرفتیم از بخش «توزیع شده» صرف نظر کنیم و روی بخش «پایگاه داده» تمرکز کنیم. تا به حال، ما تقریباً تمام مواردی را که یک پایگاه داده بلاکچین را می سازد، پیاده سازی کرده ایم. در این پست، مکانیسم‌هایی را که در قسمت‌های قبلی نادیده گرفته شده‌اند، پوشش می‌دهیم و در قسمت بعدی کار بر روی ماهیت توزیع‌شده بلاکچین را آغاز می‌کنیم.این بخش تغییرات قابل توجهی را در کد ایجاد می کند، بنابراین توضیح همه آنها در اینجا بی معنی است. لطفاً برای مشاهده تمام تغییرات از آخرین مقاله به این صفحه مراجعه کنید.پاداش یا Rewardیکی از چیزهای کوچکی که در مقاله قبلی نادیده گرفتیم، پاداش برای استخراج است. و ما در حال حاضر همه چیز برای اجرای آن داریم.این پاداش فقط یک تراکنش coinbase است. هنگامی که یک گره ماینینگ شروع به استخراج یک بلوک جدید می کند، تراکنش ها را از صف می گیرد و یک تراکنش کوین بیس را به آنها اضافه می کند. تنها خروجی تراکنش کوین بیس حاوی هش کلید عمومی ماینر است.پیاده‌سازی پاداش‌ها به آسانی به‌روزرسانی دستور ارسال است:func (cli *CLI) send(from, to string, amount int) {
    ...
    bc := NewBlockchain()
    UTXOSet := UTXOSet{bc}
    defer bc.db.Close()

    tx := NewUTXOTransaction(from, to, amount, &amp;UTXOSet)
    cbTx := NewCoinbaseTX(from, &amp;quot&amp;quot)
    txs := []*Transaction{cbTx, tx}

    newBlock := bc.MineBlock(txs)
    fmt.Println(&amp;quotSuccess!&amp;quot)
}در اجرای ما، کسی که یک تراکنش ایجاد می‌کند، بلوک جدید را استخراج می‌کند و به این ترتیب، یک پاداش دریافت می‌کند.مجموعه  UTXO در قسمت سوم (ماندگاری داده و رابط خط فرمان یا CLI)ما روشی را که بیت کوین Core بلاک ها را در یک پایگاه داده ذخیره می کند مورد مطالعه قرار دادیم. گفته شد که بلوک ها در پایگاه داده بلوک ها و خروجی های تراکنش در پایگاه داده زنجیره ای ذخیره می شوند. اجازه دهید به شما یادآوری کنم که ساختار chainstate چیست:&#x27;c&#x27; + 32-byte transaction hash -&gt; unspent transaction output record for that transactionرکورد خروجی تراکنش خرج نشده برای آن تراکنش&#x27;B&#x27; -&gt; 32-byte block hash: the block hash up to which the database represents the unspent transaction outputsهش بلوک که پایگاه داده خروجی های تراکنش خرج نشده را نشان می دهداز آن مقاله، ما قبلاً تراکنش‌ها را اجرا کرده‌ایم، اما از Chainstate برای ذخیره خروجی‌های آن‌ها استفاده نکرده‌ایم. بنابراین، این همان کاری است که اکنون می خواهیم انجام دهیم.این chainstate تراکنش ها را ذخیره نمی کند. در عوض، آنچه را که مجموعه UTXO یا مجموعه خروجی های تراکنش خرج نشده نامیده می شود، ذخیره می کند. علاوه بر این، «هش بلوک را که پایگاه داده خروجی‌های تراکنش مصرف‌نشده را نشان می‌دهد» را ذخیره می‌کند، که فعلاً آن را حذف می‌کنیم زیرا از ارتفاع بلوک استفاده نمی‌کنیم (اما آنها را در مقالات بعدی پیاده‌سازی خواهیم کرد).بنابراین، چرا می خواهیم مجموعه UTXO را داشته باشیم؟روش Blockchain.FindUnspentTransactions را که قبلاً پیاده سازی کرده بودیم در نظر بگیریدfunc (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
    ...
    bci := bc.Iterator()

    for {
        block := bci.Next()

        for _, tx := range block.Transactions {
            ...
        }

        if len(block.PrevBlockHash) == 0 {
            break
        }
    }
    ...
}این تابع تراکنش هایی را با خروجی های خرج نشده پیدا می کند. از آنجایی که تراکنش‌ها در بلوک‌ها ذخیره می‌شوند، روی هر بلوک در بلاکچین تکرار می‌شود و هر تراکنش موجود در آن را بررسی می‌کند. از 18 سپتامبر 2017، 485,860 بلاک در بیت کوین وجود دارد و کل پایگاه داده 140+ گیگابایت فضای دیسک را اشغال می کند. این به این معنی است که برای تأیید تراکنش ها باید یک گره کامل اجرا شود. علاوه بر این، اعتبارسنجی تراکنش‌ها نیاز به تکرار در بسیاری از بلوک‌ها دارد.راه حل مشکل این است که شاخصی داشته باشیم که فقط خروجی های مصرف نشده را ذخیره کند، و این همان کاری است که مجموعه UTXO انجام می دهد: این یک حافظه پنهان است که از تمام تراکنش های بلاک چین ساخته شده است (بله، با تکرار روی بلوک ها، اما این کار فقط یک بار انجام می شود. ) و بعداً برای محاسبه مانده و اعتبارسنجی معاملات جدید استفاده می شود. مجموعه UTXO تا سپتامبر 2017 حدود 2.7 گیگابایت است.خوب، بیایید فکر کنیم برای اجرای مجموعه UTXO چه چیزی را باید تغییر دهیم. در حال حاضر از روش های زیر برای یافتن تراکنش ها استفاده می شود:۱- این Blockchain.FindUnspentTransactions – تابع اصلی که تراکنش ها را با خروجی های خرج نشده پیدا می کند. این تابعی است که در آن تکرار همه بلوک ها اتفاق می افتد.۲- این Blockchain.FindSpendableOutputs – این تابع زمانی که تراکنش جدیدی ایجاد می شود استفاده می شود. اگر به تعداد کافی خروجی که مقدار مورد نیاز را در خود نگه می دارند، بیابد. از Blockchain.FindUnspentTransactions استفاده می کند.۳- این  Blockchain.FindUTXO - خروجی‌های خرج نشده برای هش کلید عمومی را که برای بدست آوردن تعادل استفاده می‌شود، پیدا می‌کند. از Blockchain.FindUnspentTransactions استفاده می کند.۴- این Blockchain.FindTransaction – یک تراکنش در بلاکچین را با شناسه آن پیدا می کند. روی همه بلوک ها تکرار می شود تا زمانی که آن را پیدا کند.همانطور که می بینید، همه روش ها روی بلوک های پایگاه داده تکرار می شوند. اما فعلا نمی‌توانیم همه آنها را بهبود ببخشیم، زیرا مجموعه UTXO همه تراکنش‌ها را ذخیره نمی‌کند، بلکه فقط آنهایی را که خروجی‌های خرج نشده دارند ذخیره می‌کند. بنابراین، نمی توان از آن در Blockchain.FindTransaction استفاده کرد.بنابراین، ما روش های زیر را می خواهیم:۱- این  Blockchain.FindUTXO - تمام خروجی های خرج نشده را با تکرار روی بلوک ها پیدا می کند.۲- این UTXOSet.Reindex — از FindUTXO برای یافتن خروجی های خرج نشده استفاده می کند و آنها را در پایگاه داده ذخیره می کند. اینجا جایی است که کش اتفاق می افتد.۳- این UTXOSet.FindSpendableOutputs – آنالوگ Blockchain.FindSpendableOutputs، اما از مجموعه UTXO استفاده می کند.۴- این  UTXOSet.FindUTXO - آنالوگ Blockchain.FindUTXO، اما از مجموعه UTXO استفاده می کند.۵- این Blockchain.FindTransaction ثابت باقی می ماند.بنابراین، دو تابع پرکاربرد از هم اکنون از حافظه پنهان (cache) استفاده خواهند کرد! بیایید کدنویسی را شروع کنیم.type UTXOSet struct {
    Blockchain *Blockchain
}ما از یک پایگاه داده استفاده می کنیم، اما مجموعه UTXO را در یک سطل (bucket) دیگر ذخیره می کنیم. بنابراین، UTXOSet با بلاکچین همراه است.func (u UTXOSet) Reindex() {
    db := u.Blockchain.db
    bucketName := []byte(utxoBucket)

    err := db.Update(func(tx *bolt.Tx) error {
        err := tx.DeleteBucket(bucketName)
        _, err = tx.CreateBucket(bucketName)
    })

    UTXO := u.Blockchain.FindUTXO()

    err = db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket(bucketName)

        for txID, outs := range UTXO {
            key, err := hex.DecodeString(txID)
            err = b.Put(key, outs.Serialize())
        }
    })
}این متد در ابتدا مجموعه UTXO را ایجاد می کند. ابتدا سطل (bucket) را در صورت وجود حذف می کند، سپس تمام خروجی های مصرف نشده را از بلاکچین دریافت می کند و در نهایت خروجی ها را در سطل ذخیره می کند.این Blockchain.FindUTXO تقریباً مشابه Blockchain.FindUnspentTransactions است، اما اکنون map ای از جفت TransactionID → TransactionOutputs را برمی گرداند.اکنون می توان از مجموعه UTXO برای ارسال سکه استفاده کرد:func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) {
    unspentOutputs := make(map[string][]int)
    accumulated := 0
    db := u.Blockchain.db

    err := db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()

        for k, v := c.First(); k != nil; k, v = c.Next() {
            txID := hex.EncodeToString(k)
            outs := DeserializeOutputs(v)

            for outIdx, out := range outs.Outputs {
                if out.IsLockedWithKey(pubkeyHash) &amp;&amp; accumulated &lt; amount {
                    accumulated += out.Value
                    unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
                }
            }
        }
    })

    return accumulated, unspentOutputs
}یا بالانس را بررسی کنید:func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput {
    var UTXOs []TXOutput
    db := u.Blockchain.db

    err := db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()

        for k, v := c.First(); k != nil; k, v = c.Next() {
            outs := DeserializeOutputs(v)

            for _, out := range outs.Outputs {
                if out.IsLockedWithKey(pubKeyHash) {
                    UTXOs = append(UTXOs, out)
                }
            }
        }

        return nil
    })

    return UTXOs
}اینها نسخه های کمی تغییر یافته از روش های بلاکچین مربوطه هستند. آن روش های بلاک چین دیگر مورد نیاز نیست.داشتن مجموعه UTXO به این معنی است که داده ها (معاملات) ما اکنون به ذخیره سازی تقسیم می شوند: تراکنش های واقعی در بلاک چین ذخیره می شوند و خروجی های مصرف نشده در مجموعه UTXO ذخیره می شوند. چنین جداسازی به مکانیزم همگام سازی مستحکم نیاز دارد زیرا ما می خواهیم مجموعه UTXO همیشه به روز شود و خروجی های تراکنش های اخیر ذخیره شود. اما ما نمی خواهیم هر بار که یک بلوک جدید استخراج می شود دوباره ایندکس کنیم زیرا این اسکن های مکرر بلاکچین است که می خواهیم از آن اجتناب کنیم. بنابراین، ما به مکانیزمی برای به روز رسانی مجموعه UTXO نیاز داریم:func (u UTXOSet) Update(block *Block) {
    db := u.Blockchain.db

    err := db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoBucket))

        for _, tx := range block.Transactions {
            if tx.IsCoinbase() == false {
                for _, vin := range tx.Vin {
                    updatedOuts := TXOutputs{}
                    outsBytes := b.Get(vin.Txid)
                    outs := DeserializeOutputs(outsBytes)

                    for outIdx, out := range outs.Outputs {
                        if outIdx != vin.Vout {
                            updatedOuts.Outputs = append(updatedOuts.Outputs, out)
                        }
                    }

                    if len(updatedOuts.Outputs) == 0 {
                        err := b.Delete(vin.Txid)
                    } else {
                        err := b.Put(vin.Txid, updatedOuts.Serialize())
                    }

                }
            }

            newOutputs := TXOutputs{}
            for _, out := range tx.Vout {
                newOutputs.Outputs = append(newOutputs.Outputs, out)
            }

            err := b.Put(tx.ID, newOutputs.Serialize())
        }
    })
}این روش بزرگ به نظر می رسد، اما کاری که انجام می دهد کاملاً ساده است. هنگامی که یک بلوک جدید استخراج می شود، مجموعه UTXO باید به روز شود. به روز رسانی به معنای حذف خروجی های صرف شده و افزودن خروجی های خرج نشده از تراکنش های تازه استخراج شده است. اگر تراکنشی که خروجی آن حذف شده است، خروجی دیگری نداشته باشد، آن نیز حذف می شود. خیلی ساده!بیایید اکنون از مجموعه UTXO در جایی که لازم است استفاده کنیم:func (cli *CLI) createBlockchain(address string) {
    ...
    bc := CreateBlockchain(address)
    defer bc.db.Close()

    UTXOSet := UTXOSet{bc}
    UTXOSet.Reindex()
    ...
}ایندکس مجدد درست پس از ایجاد یک بلاکچین جدید انجام می شود. در حال حاضر، این تنها جایی است که از Reindex استفاده می‌شود، حتی اگر در اینجا بیش از حد به نظر می‌رسد، زیرا در ابتدای یک بلاک چین تنها یک بلوک با یک تراکنش وجود دارد و به‌جای آن می‌توان از Update استفاده کرد. اما ممکن است در آینده به مکانیسم نمایه سازی مجدد نیاز داشته باشیم.func (cli *CLI) send(from, to string, amount int) {
    ...
    newBlock := bc.MineBlock(txs)
    UTXOSet.Update(newBlock)
}و مجموعه UTXO پس از استخراج یک بلوک جدید به روز می شود.بیایید بررسی کنیم که کار می کند$ blockchain_go createblockchain -address 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ100000086a725e18ed7e9e06f1051651a4fc46a315a9d298e59e57aeacbe0bf73Done!$ blockchain_go send -from 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 -to 12DkLzLQ4B3gnQt62EPRJGZ38n3zF4Hzt5 -amount 60000001f75cb3a5033aeecbf6a8d378e15b25d026fb0a665c7721a5bb0faa21bSuccess!$ blockchain_go send -from 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 -to 12ncZhA5mFTTnTmHq1aTPYBri4jAK8TacL -amount 4000000cc51e665d53c78af5e65774a72fc7b864140a8224bf4e7709d8e0fa433Success!$ blockchain_go getbalance -address 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1Balance of &#x27;1F4MbuqjcuJGymjcuYQMUVYB37AWKkSLif&#x27;: 20$ blockchain_go getbalance -address 12DkLzLQ4B3gnQt62EPRJGZ38n3zF4Hzt5Balance of &#x27;1XWu6nitBWe6J6v6MXmd5rhdP7dZsExbx&#x27;: 6$ blockchain_go getbalance -address 12ncZhA5mFTTnTmHq1aTPYBri4jAK8TacLBalance of &#x27;13UASQpCR8Nr41PojH8Bz4K6cmTCqweskL&#x27;: 4خب! آدرس 1JnMDSqVoHi4TEFXNw5wJ8skPsPf4LHkQ1 سه بار جایزه دریافت کرد:     ۱- یک بار برای استخراج بلوک های پیدایش.     ۲- یک بار برای استخراج بلوک 0000001f75cb3a5033aeecbf6a8d378e15b25d026fb0a665c7721a5bb0faa21b.     ۳- و یک بار برای ماینینگ بلوک 000000cc51e665d53c78af5e65774a72fc7b864140a8224bf4e7709d8e0fa433درخت مرکل یا Merkle Treeیک مکانیسم بهینه سازی دیگر وجود دارد که می خواهم در این پست در مورد آن صحبت کنم.همانطور که در بالا گفته شد، پایگاه داده کامل بیت کوین (یعنی بلاکچین) بیش از 140 گیگابایت فضای دیسک را اشغال می کند. به دلیل ماهیت غیرمتمرکز بیت کوین، هر گره در شبکه باید مستقل و خودکفا باشد، یعنی هر گره باید یک نسخه کامل از بلاکچین را ذخیره کند. با توجه به اینکه بسیاری از مردم شروع به استفاده از بیت کوین کرده اند، پیروی از این قانون دشوارتر می شود: بعید است که همه یک گره کامل را اجرا کنند. همچنین، از آنجایی که گره‌ها مشارکت‌کنندگان کامل شبکه هستند، مسئولیت‌هایی دارند: باید تراکنش‌ها و بلوک‌ها را تأیید کنند. همچنین، برای تعامل با گره‌های دیگر و دانلود بلوک‌های جدید، ترافیک اینترنتی خاصی مورد نیاز است.در مقاله اولیه بیت کوین منتشر شده توسط ساتوشی ناکاموتو، راه حلی برای این مشکل وجود داشت: تأیید پرداخت ساده یا Simplified Payment Verification به اختصار (SPV). SPV یک گره سبک بیت کوین است که کل بلاکچین را دانلود نمی کند و بلوک ها و تراکنش ها را تأیید نمی کند. در عوض، تراکنش‌ها را در بلوک‌ها (برای تأیید پرداخت‌ها) پیدا می‌کند و به یک گره کامل متصل می‌شود تا فقط داده‌های ضروری را بازیابی کند. این مکانیسم امکان داشتن چندین گره کیف پول سبک را با اجرای تنها یک گره کامل فراهم می کند.برای اینکه SPV امکان پذیر باشد، باید راهی برای بررسی اینکه آیا یک بلوک حاوی تراکنش خاصی است بدون دانلود کل بلوک وجود دارد. و اینجاست که درخت مرکل وارد بازی می شود.درختان مرکل توسط بیت کوین برای به دست آوردن هش تراکنش ها استفاده می شود، که سپس در هدر بلوک ذخیره می شود و توسط سیستم اثبات کار در نظر گرفته می شود. تا به حال، ما فقط هش های هر تراکنش را در یک بلوک به هم متصل می کردیم و SHA-256 را روی آنها اعمال می کردیم. این نیز یک راه خوب برای دریافت یک نمایش منحصر به فرد از تراکنش های بلوکی است، اما مزایای درختان مرکل را ندارد.بیایید به درخت مرکل نگاه کنیم:یک درخت مرکل برای هر بلوک ساخته می‌شود و با برگ‌ها (پایین درخت) شروع می‌شود، جایی که یک برگ یک هش تراکنش است (بیت‌کوین از هش دوگانه SHA256 استفاده می‌کند). تعداد برگ‌ها باید زوج باشد، اما هر بلوک دارای تعداد تراکنش زوج نیست. در صورت وجود تعداد فرد از تراکنش ها، آخرین تراکنش کپی می شود (در درخت مرکل، نه در بلوک!).با حرکت از پایین به بالا، برگ‌ها به صورت جفت گروه‌بندی می‌شوند، هش‌های آن‌ها به هم متصل می‌شوند و یک هش جدید از هش‌های پیوسته به دست می‌آید. هش های جدید گره های درختی جدید را تشکیل می دهند. این فرآیند تا زمانی تکرار می شود که فقط یک گره وجود داشته باشد که ریشه درخت نامیده می شود. سپس هش ریشه به عنوان نمایش منحصر به فرد تراکنش ها استفاده می شود، در هدرهای بلوک ذخیره می شود و در سیستم اثبات کار استفاده می شود.مزیت درختان مرکل این است که یک گره می تواند عضویت تراکنش خاصی را بدون دانلود کل بلوک تأیید کند. فقط یک هش تراکنش، یک هش ریشه درخت Merkle و یک مسیر Merkle برای این مورد نیاز است.در آخر بیایید کد بنویسیم:type MerkleTree struct {
    RootNode *MerkleNode
}

type MerkleNode struct {
    Left  *MerkleNode
    Right *MerkleNode
    Data  []byte
}ما با ساختارها شروع می کنیم. هر MerkleNode داده ها و پیوندهایی را به شاخه های خود نگه می دارد. MerkleTree در واقع گره ریشه است که به گره های بعدی پیوند می خورد که به نوبه خود به گره های بعدی و غیره مرتبط می شوند.بیایید ابتدا یک گره جدید ایجاد کنیم:func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
    mNode := MerkleNode{}

    if left == nil &amp;&amp; right == nil {
        hash := sha256.Sum256(data)
        mNode.Data = hash[:]
    } else {
        prevHashes := append(left.Data, right.Data...)
        hash := sha256.Sum256(prevHashes)
        mNode.Data = hash[:]
    }

    mNode.Left = left
    mNode.Right = right

    return &amp;mNode
}هر گره حاوی مقداری داده است. هنگامی که یک گره یک برگ است، داده ها از خارج منتقل می شوند (در مورد ما یک تراکنش سریالی). هنگامی که یک گره به گره های دیگر پیوند داده می شود، داده های آنها را می گیرد و آنها را به هم متصل می کند و هش می کند.func NewMerkleTree(data [][]byte) *MerkleTree {
    var nodes []MerkleNode

    if len(data)%2 != 0 {
        data = append(data, data[len(data)-1])
    }

    for _, datum := range data {
        node := NewMerkleNode(nil, nil, datum)
        nodes = append(nodes, *node)
    }

    for i := 0; i &lt; len(data)/2; i++ {
        var newLevel []MerkleNode

        for j := 0; j &lt; len(nodes); j += 2 {
            node := NewMerkleNode(&amp;nodes[j], &amp;nodes[j+1], nil)
            newLevel = append(newLevel, *node)
        }

        nodes = newLevel
    }

    mTree := MerkleTree{&amp;nodes[0]}

    return &amp;mTree
}هنگامی که یک درخت جدید ایجاد می شود، اولین چیزی که باید اطمینان حاصل کرد این است که تعداد برگ های زوج وجود داشته باشد. پس از آن، داده ها (که آرایه ای از تراکنش های سریالی است) به برگ درخت تبدیل می شود و درختی از این برگ ها رشد می کند.اکنون، اجازه دهید Block.HashTransactions را تغییر دهیم، که در سیستم اثبات کار برای به دست آوردن هش تراکنش ها استفاده می شود:func (b *Block) HashTransactions() []byte {
    var transactions [][]byte

    for _, tx := range b.Transactions {
        transactions = append(transactions, tx.Serialize())
    }
    mTree := NewMerkleTree(transactions)

    return mTree.RootNode.Data
}ابتدا، تراکنش ها سریالی می شوند (با استفاده از encoding/gob)، و سپس از آنها برای ساخت درخت مرکل استفاده می شود. ریشه درخت به عنوان شناسه منحصر به فرد تراکنش های بلوک عمل می کند.پرداخت به هش کلید عمومی یا Pay to Public Key Hash یا به اختصار P2PKHیک چیز دیگر وجود دارد که می خواهم با جزئیات بیشتر در مورد آن صحبت کنم.همانطور که به یاد دارید، در بیت کوین زبان برنامه نویسی Script وجود دارد که برای قفل کردن خروجی های تراکنش استفاده می شود. و ورودی های تراکنش داده هایی را برای باز کردن قفل خروجی ها فراهم می کنند. زبان ساده است و کد در این زبان فقط دنباله ای از داده ها و عملگرها است. این مثال را در نظر بگیرید:5 2 OP_ADD 7 OP_EQUALمقادیر 5، 2 و 7 داده ها هستند. OP_ADD و OP_EQUAL عملگرها هستند. کد اسکریپت از چپ به راست اجرا می شود: هر قطعه داده در پشته قرار می گیرد و عملگر بعدی به عناصر پشته بالایی اعمال می شود. پشته اسکریپت فقط یک حافظه ساده FILO (اولین ورودی آخرین خروجی) است: اولین عنصر در پشته آخرین عنصری است که باید برداشته شود، با هر عنصر دیگری که روی عنصر قبلی قرار می‌گیرد.بیایید اجرای اسکریپت بالا را به مراحل تقسیم کنیم:Stack: empty. Script: 5 2 OP_ADD 7 OP_EQUAL.Stack: 5. Script: 2 OP_ADD 7 OP_EQUAL.Stack: 5 2. Script: OP_ADD 7 OP_EQUAL.Stack: 7. Script: 7 OP_EQUAL.Stack: 7 7. Script: OP_EQUAL.Stack: true. Script: empty.این OP_ADD دو عنصر را از پشته می گیرد، آنها را خلاصه می کند و مجموع را به پشته پوش می کند. OP_EQUAL دو عنصر را از پشته می گیرد و آنها را با هم مقایسه می کند: اگر برابر باشند، به پشته true پوش می کند. در غیر این صورت false را پوش می کند. نتیجه اجرای یک اسکریپت مقدار عنصر پشته بالایی است: در مورد ما، درست است، به این معنی که اسکریپت با موفقیت به پایان رسید.حال بیایید به اسکریپتی که در بیت کوین برای انجام پرداخت ها استفاده می شود نگاه کنیم:&lt;signature&gt; &lt;pubKey&gt; OP_DUP OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGاین اسکریپت Pay to Public Key Hash (P2PKH) نامیده می شود و این اسکریپت رایج ترین اسکریپت مورد استفاده در بیت کوین است. به معنای واقعی کلمه به یک هش کلید عمومی پرداخت می کند، یعنی سکه ها را با یک کلید عمومی خاص قفل می کند. این قلب پرداخت بیت کوین است: هیچ حسابی وجود ندارد، هیچ وجهی بین آنها انتقال نمی یابد. فقط یک اسکریپت وجود دارد که بررسی می کند امضای ارائه شده و کلید عمومی صحیح است.اسکریپت در واقع در دو قسمت ذخیره می شود:۱- اولین قطعه، &lt;signature&gt; &lt;pubKey&gt;، در فیلد ScriptSig ورودی ذخیره می شود.     ۲- قطعه دوم، OP_DUP OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIG در ScriptPubKey خروجی ذخیره می شود.بنابراین، این خروجی‌ها هستند که منطق باز کردن قفل را تعریف می‌کنند، و این ورودی‌ها هستند که داده‌هایی را برای باز کردن خروجی‌ها فراهم می‌کنند. بیایید اسکریپت را اجرا کنیم:Stack: emptyScript: &lt;signature&gt; &lt;pubKey&gt; OP_DUP OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt;Script: &lt;pubKey&gt; OP_DUP OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt; &lt;pubKey&gt;Script: OP_DUP OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt; &lt;pubKey&gt; &lt;pubKey&gt;Script: OP_HASH160 &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt; &lt;pubKey&gt; &lt;pubKeyHash&gt;Script: &lt;pubKeyHash&gt; OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt; &lt;pubKey&gt; &lt;pubKeyHash&gt; &lt;pubKeyHash&gt;Script: OP_EQUALVERIFY OP_CHECKSIGStack: &lt;signature&gt; &lt;pubKey&gt;Script: OP_CHECKSIGStack: true or false. Script: empty.این OP_DUP عنصر پشته بالایی را کپی می کند. OP_HASH160 عنصر بالای پشته را می گیرد و آن را با RIPEMD160 هش می کند. نتیجه به پشته وارد می شود. OP_EQUALVERIFY دو عنصر پشته بالایی را با هم مقایسه می‌کند و اگر برابر نباشند، اجرای اسکریپت را قطع می‌کند. OP_CHECKSIG امضای یک تراکنش را با هش کردن تراکنش و استفاده از &lt;signature&gt; و &lt;pubKey&gt; تأیید می کند. اپراتور دوم بسیار پیچیده است: یک کپی کوتاه شده از تراکنش ایجاد می کند، آن را هش می کند (زیرا هش تراکنش امضا شده است)، و درستی امضا را با استفاده از &lt;signature&gt; و &lt;pubKey&gt; بررسی می کند.داشتن چنین زبان برنامه نویسی به بیت کوین اجازه می دهد تا یک پلت فرم قرارداد هوشمند نیز باشد: این زبان علاوه بر انتقال به یک کلید، طرح های پرداخت دیگری را نیز ممکن می سازد.نتیجه گیریو بس! ما تقریباً تمام ویژگی های کلیدی یک ارز دیجیتال مبتنی بر بلاکچین را پیاده سازی کرده ایم. ما بلاکچین، آدرس، استخراج و تراکنش داریم. اما یک چیز دیگر وجود دارد که به همه این مکانیسم ها جان می دهد و بیت کوین را به یک سیستم جهانی تبدیل می کند: اجماع. در مقاله بعدی، اجرای بخش «غیرمتمرکز» بلاکچین را آغاز خواهیم کرد. گوش به زنگ باشید!پایان قسمت ششمقسمت هفتمباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Wed, 15 Jun 2022 18:13:47 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت پنجم (آدرس ها)</title>
                <link>https://virgool.io/@hootan09/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D9%BE%D9%86%D8%AC%D9%85-%D8%A2%D8%AF%D8%B1%D8%B3-%D9%87%D8%A7-sggbpwgy6acz</link>
                <description>مقدمهدر مقاله قبل پیاده سازی تراکنش ها را آغاز کردیم. همچنین با ماهیت غیرشخصی تراکنش ها آشنا شدید: هیچ حساب کاربری وجود ندارد، اطلاعات شخصی شما (به عنوان مثال، نام، شماره پاسپورت یا SSN) مورد نیاز نیست و در هیچ کجای بیت کوین ذخیره نمی شود. اما هنوز باید چیزی وجود داشته باشد که شما را به عنوان صاحب خروجی های تراکنش شناسایی کند (یعنی صاحب سکه هایی که روی این خروجی ها قفل شده اند). و این همان چیزی است که آدرس های بیت کوین برای آن مورد نیاز است. تاکنون از رشته های تعریف شده توسط کاربر دلخواه به عنوان آدرس استفاده کرده ایم، و زمان پیاده سازی آدرس های واقعی فرا رسیده است، همانطور که در بیت کوین پیاده سازی می شوند.این بخش تغییرات قابل توجهی را در کد ایجاد می کند، بنابراین توضیح همه آنها در اینجا بی معنی است. لطفاً برای مشاهده تمام تغییرات از آخرین مقاله به این صفحه مراجعه کنید.آدرس های بیت کویندر اینجا نمونه ای از آدرس بیت کوین آورده شده است: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa. این اولین آدرس بیت کوین است که ظاهراً متعلق به ساتوشی ناکاموتو است. آدرس های بیت کوین عمومی هستند. اگر می خواهید برای شخصی سکه بفرستید، باید آدرس او را بدانید. اما آدرس ها (با وجود منحصر به فرد بودن) چیزی نیستند که شما را به عنوان صاحب «کیف پول» معرفی کنند. در واقع، چنین آدرس هایی نمایشی از کلیدهای عمومی قابل خواندن توسط انسان هستند. در بیت کوین، هویت شما یک جفت کلید خصوصی و عمومی است که در رایانه شما ذخیره شده است (یا در مکان دیگری که به آن دسترسی دارید ذخیره می شود). بیت کوین برای ایجاد این کلیدها به ترکیبی از الگوریتم های رمزنگاری متکی است و تضمین می کند که هیچ کس دیگری در جهان نمی تواند بدون دسترسی فیزیکی به کلیدهای شما به سکه های شما دسترسی داشته باشد. بیایید بحث کنیم که این الگوریتم ها چیست.رمزنگاری کلید عمومی (Public-key Cryptography)الگوریتم های رمزنگاری برای تولید کلید عمومی از جفت کلید استفاده می کنند: کلیدهای عمومی و کلیدهای خصوصی. کلیدهای عمومی حساس نیستند و می توانند در اختیار هر کسی قرار گیرند. در مقابل، کلیدهای خصوصی نباید فاش شوند: هیچ کس جز مالک نباید به آنها دسترسی داشته باشد زیرا این کلیدهای خصوصی هستند که به عنوان شناسه مالک عمل می کنند. شما کلیدهای خصوصی خود هستید (البته در دنیای ارزهای دیجیتال).در اصل، کیف پول بیت کوین فقط یک جفت از این کلیدها است. هنگامی که یک برنامه کیف پول نصب می کنید یا از یک مشتری بیت کوین برای ایجاد یک آدرس جدید استفاده می کنید، یک جفت کلید برای شما ایجاد می شود. کسی که کلید خصوصی را کنترل می کند، تمام سکه های ارسال شده به این کلید را در بیت کوین کنترل می کند.کلیدهای خصوصی و عمومی فقط دنباله های تصادفی بایت هستند، بنابراین نمی توانند روی صفحه چاپ شوند و توسط انسان خوانده شوند. به همین دلیل است که بیت کوین از یک الگوریتم برای تبدیل کلیدهای عمومی به یک رشته قابل خواندن توسط انسان استفاده می کند.اگر تا به حال از یک برنامه کیف پول بیت کوین استفاده کرده اید، به احتمال زیاد یک عبارت عبور قابل حفظ کردن برای شما ایجاد شده است. چنین عباراتی به جای کلیدهای خصوصی استفاده می شود و می توان از آنها برای تولید کلید استفاده کرد. این مکانیزم در BIP-039 پیاده سازی شده است.خب، ما اکنون می دانیم که چه چیزی کاربران را در بیت کوین شناسایی می کند. اما بیت کوین چگونه مالکیت خروجی های تراکنش (و سکه های ذخیره شده روی آنها) را بررسی می کند؟امضاهای دیجیتال یا Digital Signaturesدر ریاضیات و رمزنگاری، مفهوم امضای دیجیتال وجود دارد - الگوریتم هایی که تضمین می کنند:     ۱- که داده ها در حین انتقال از فرستنده به گیرنده تغییر نکرده اند.     ۲- که داده ها توسط یک فرستنده خاص ایجاد شده است.     ۳- که فرستنده نمی تواند ارسال داده ها را انکار کند.با اعمال یک الگوریتم امضا کننده برای داده ها (یعنی امضای داده ها)، شخص امضایی دریافت می کند که بعداً می تواند تأیید شود. امضای دیجیتال با استفاده از یک کلید خصوصی اتفاق می‌افتد و تأیید به یک کلید عمومی نیاز دارد.برای امضای داده ها به موارد زیر نیاز داریم:     ۱- داده برای امضا؛     ۲- کلید خصوصیعملیات امضاء یک امضا تولید می کند که در ورودی های تراکنش ذخیره می شود. برای تأیید امضا، موارد زیر مورد نیاز است:     ۱- داده هایی که امضا شد؛     ۲- امضا؛     ۳- کلید عمومی.به زبان ساده، فرآیند تأیید را می توان اینگونه توصیف کرد: بررسی کنید که این امضا از این داده ها با یک کلید خصوصی که برای تولید کلید عمومی استفاده می شود، به دست آمده است.امضای دیجیتال رمزگذاری نیست، شما نمی توانید داده ها را از یک امضا بازسازی کنید. این شبیه هش است: شما داده ها را از طریق یک الگوریتم هش اجرا می کنید و یک نمایش منحصر به فرد از داده ها دریافت می کنید. تفاوت بین امضا و هش جفت کلید است: آنها تأیید امضا را امکان پذیر می کنند.اما از جفت‌های کلید نیز می‌توان برای رمزگذاری داده‌ها استفاده کرد: یک کلید خصوصی برای رمزگذاری و یک کلید عمومی برای رمزگشایی داده‌ها استفاده می‌شود. اگرچه بیت کوین از الگوریتم های رمزگذاری استفاده نمی کند.هر ورودی تراکنش در بیت کوین توسط شخصی که تراکنش را ایجاد کرده است امضا می شود. هر تراکنش در بیت کوین باید قبل از قرار گرفتن در یک بلوک تأیید شود. ابزار تأیید (علاوه بر سایر رویه ها):     ۱- بررسی اینکه ورودی‌ها مجوز استفاده از خروجی‌های تراکنش‌های قبلی را دارند.     ۲- بررسی صحت امضای معاملهاز نظر شماتیک، فرآیند امضای داده ها و تأیید امضا به این صورت است:بیایید اکنون چرخه عمر کامل یک تراکنش را مرور کنیم:    ۱- در ابتدا، بلوک پیدایش (genesis block) وجود دارد که حاوی یک تراکنش کوین بیس است. هیچ ورودی واقعی در معاملات coinbase وجود ندارد، بنابراین امضا لازم نیست. خروجی تراکنش coinbase شامل یک کلید عمومی هش شده است (الگوریتم های RIPEMD16(SHA256(PubKey)) استفاده می شود).    ۲- وقتی کسی سکه می فرستد، یک تراکنش ایجاد می شود. ورودی های تراکنش به خروجی های تراکنش(های) قبلی اشاره خواهد کرد. هر ورودی یک کلید عمومی (نه هش شده) و امضای کل تراکنش را ذخیره می کند.    ۳- گره های دیگر در شبکه بیت کوین که تراکنش را دریافت می کنند، آن را تأیید می کنند. علاوه بر موارد دیگر، آنها بررسی خواهند کرد که: هش کلید عمومی در ورودی با هش خروجی ارجاع شده مطابقت داشته باشد (این امر تضمین می کند که فرستنده فقط سکه های متعلق به آنها را خرج می کند). امضا صحیح است (این تضمین می کند که معامله توسط صاحب واقعی سکه ها ایجاد شده است).    ۴- هنگامی که یک گره ماینر آماده استخراج یک بلوک جدید است، تراکنش را در یک بلوک قرار می دهد و شروع به استخراج آن می کند.    ۵- هنگامی که بلوک استخراج می شود، هر گره دیگری در شبکه پیامی مبنی بر استخراج بلوک دریافت می کند و بلوک را به بلاکچین اضافه می کند.    ۶- پس از اضافه شدن یک بلوک به بلاکچین، تراکنش کامل شد، خروجی های آن می توانند در تراکنش های جدید ارجاع داده شوند.رمزنگاری منحنی بیضوی یا Elliptic Curve Cryptographyهمانطور که در بالا توضیح داده شد، کلیدهای عمومی و خصوصی دنباله ای از بایت های تصادفی هستند. از آنجایی که این کلیدهای خصوصی هستند که برای شناسایی صاحبان سکه ها استفاده می شوند، یک شرط لازم وجود دارد: الگوریتم تصادفی باید بایت های واقعا تصادفی تولید کند. ما نمی خواهیم تصادفاً یک کلید خصوصی که متعلق به شخص دیگری است ایجاد کنیم.بیت کوین از منحنی های بیضوی (elliptic curves) برای تولید کلیدهای خصوصی استفاده می کند. منحنی های بیضوی یک مفهوم پیچیده ریاضی است که ما در اینجا قصد نداریم جزئیات آن را توضیح دهیم (اگر کنجکاو هستید، این مقدمه ملایم برای منحنی های بیضوی را بررسی کنید هشدار: فرمول های ریاضی!). چیزی که ما باید بدانیم این است که از این منحنی ها می توان برای تولید اعداد واقعا بزرگ و تصادفی استفاده کرد. منحنی استفاده شده توسط بیت کوین می تواند به طور تصادفی عددی بین 0 تا ۲ به توان ۲۵۶ (که تقریباً معادل ۱۰ به توان ۷۷ است، این درحالی است که بین ۱۰ به توان ۷۸ تا  ۱۰ به توان ۸۲ اتم در جهان مرئی وجود دارد) انتخاب کند. چنین محدودیت بالایی به این معنی است که تقریباً غیرممکن است که یک کلید خصوصی را دو بار تولید کنید.همچنین، بیت کوین از الگوریتم ECDSA (الگوریتم امضای دیجیتال منحنی بیضوی یا Elliptic Curve Digital Signature Algorithm) برای امضای تراکنش ها استفاده می کند و ما نیز خواهیم کرد.مبنای ۵۸ یا Base58حالا بیایید به آدرس بیت کوین ذکر شده در بالا برگردیم: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa. اکنون می دانیم که این نمایش یک کلید عمومی برای انسان قابل خواندن است. و اگر آن را رمزگشایی کنیم، در اینجا کلید عمومی به نظر می رسد (به عنوان دنباله ای از بایت های نوشته شده در سیستم هگزادسیمال):0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93بیت کوین از الگوریتم Base58 برای تبدیل کلیدهای عمومی به فرمت قابل خواندن توسط انسان استفاده می کند. این الگوریتم بسیار شبیه به Base64 معروف است، اما از الفبای کوتاه تری استفاده می کند: برخی از حروف از الفبا حذف شدند تا از حملاتی که از شباهت حروف استفاده می کنند جلوگیری شود. بنابراین، این نمادها وجود ندارد: 0 (صفر)، O ( o بزرگ)، I ( i بزرگ)، l (حروف کوچک L)، زیرا شبیه به هم هستند. همچنین هیچ علامت + و / وجود ندارد.بیایید روند دریافت آدرس از یک کلید عمومی را به صورت شماتیک تجسم کنیم:بنابراین، کلید عمومی رمزگشایی شده فوق از سه بخش تشکیل شده است:Version  Public key hash                           Checksum00       62E907B15CBF27D5425399EBF6F0FB50EBB88F18  C29B7D93از آنجایی که توابع هش یک راه هستند (یعنی نمی توان آنها را معکوس کرد)، نمی توان کلید عمومی را از هش استخراج کرد. اما می‌توانیم با اجرای آن از طریق توابع هش ذخیره و مقایسه هش‌ها بررسی کنیم که آیا از کلید عمومی برای دریافت هش استفاده شده است یا خیر.خب، حالا که همه قطعات را داریم، بیایید یک کد بنویسیم. برخی از مفاهیم زمانی که به صورت کد نوشته می شوند باید واضح تر باشند.آدرس های پیاده سازیما با ساختار Wallet شروع می کنیم:type Wallet struct {
	PrivateKey ecdsa.PrivateKey
	PublicKey  []byte
}

type Wallets struct {
	Wallets map[string]*Wallet
}

func NewWallet() *Wallet {
	private, public := newKeyPair()
	wallet := Wallet{private, public}

	return &amp;wallet
}

func newKeyPair() (ecdsa.PrivateKey, []byte) {
	curve := elliptic.P256()
	private, err := ecdsa.GenerateKey(curve, rand.Reader)
	pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)

	return *private, pubKey
}کیف پول چیزی جز یک جفت کلید نیست. همچنین برای نگهداری مجموعه ای از کیف پول ها، ذخیره آنها در یک فایل و بارگیری آنها از آن، به نوع Wallets نیاز داریم. در عملکرد ساخت کیف پول یک جفت کلید جدید تولید می شود. تابع newKeyPair ساده است. این ECDSA بر اساس منحنی های بیضوی است، بنابراین ما به یک منحنی نیاز داریم. سپس یک کلید خصوصی با استفاده از منحنی و یک کلید عمومی از کلید خصوصی تولید می شود. یک نکته قابل توجه است در الگوریتم های مبتنی بر منحنی بیضوی، کلیدهای عمومی نقاطی روی یک منحنی هستند. بنابراین، یک کلید عمومی ترکیبی از مختصات X، Y است. در بیت کوین، این مختصات به هم پیوسته و یک کلید عمومی را تشکیل می دهند.حالا بیایید یک آدرس تولید کنیم:func (w Wallet) GetAddress() []byte {
	pubKeyHash := HashPubKey(w.PublicKey)

	versionedPayload := append([]byte{version}, pubKeyHash...)
	checksum := checksum(versionedPayload)

	fullPayload := append(versionedPayload, checksum...)
	address := Base58Encode(fullPayload)

	return address
}

func HashPubKey(pubKey []byte) []byte {
	publicSHA256 := sha256.Sum256(pubKey)

	RIPEMD160Hasher := ripemd160.New()
	_, err := RIPEMD160Hasher.Write(publicSHA256[:])
	publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)

	return publicRIPEMD160
}

func checksum(payload []byte) []byte {
	firstSHA := sha256.Sum256(payload)
	secondSHA := sha256.Sum256(firstSHA[:])

	return secondSHA[:addressChecksumLen]
}در اینجا مراحل تبدیل کلید عمومی به آدرس Base58 آمده است.     ۱- کلید عمومی را بردارید و با الگوریتم های هش RIPEMD160(SHA256(PubKey)) دوبار آن را هش کنید.     ۲- نسخه (version) الگوریتم تولید آدرس را در هش آماده کنید.     ۳- با هش کردن نتیجه مرحله 2 با SHA256(SHA256(Payload)) مبلغ چکسام را محاسبه کنید. چکسام از چهار بایت اول هش به دست آمده است.     ۴- چکسام سوم را به ترکیب version+PubKeyHash اضافه کنید.     5- ترکیب انکد شده version+PubKeyHash+checksum را با Base58 رمزگذاری کنید.در نتیجه، یک آدرس بیت کوین واقعی دریافت خواهید کرد، حتی می توانید موجودی آن را در blockchain.info بررسی کنید. اما می توانم به شما اطمینان دهم که هر چند بار یک آدرس جدید ایجاد کنید و موجودی آن را بررسی کنید، موجودی 0 است. به همین دلیل است که انتخاب الگوریتم رمزنگاری کلید عمومی مناسب بسیار مهم است: با توجه به اینکه کلیدهای خصوصی اعداد تصادفی هستند، شانس تولید همان عدد باید تا حد امکان کم باشد. در حالت ایده آل، باید به اندازه «هرگز» پایین باشد.همچنین توجه داشته باشید که برای دریافت آدرس نیازی به اتصال به گره بیت کوین ندارید. الگوریتم تولید آدرس از ترکیبی از الگوریتم های باز استفاده می کند که در بسیاری از زبان های برنامه نویسی و کتابخانه ها پیاده سازی شده اند.اکنون باید ورودی ها و خروجی ها را تغییر دهیم تا بتوانند از آدرس ها استفاده کنند:type TXInput struct {
	Txid      []byte
	Vout      int
	Signature []byte
	PubKey    []byte
}

func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
	lockingHash := HashPubKey(in.PubKey)

	return bytes.Compare(lockingHash, pubKeyHash) == 0
}

type TXOutput struct {
	Value      int
	PubKeyHash []byte
}

func (out *TXOutput) Lock(address []byte) {
	pubKeyHash := Base58Decode(address)
	pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
	out.PubKeyHash = pubKeyHash
}

func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
	return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}توجه داشته باشید که ما دیگر از فیلدهای ScriptPubKey و ScriptSig استفاده نمی کنیم، زیرا قرار نیست یک زبان برنامه نویسی را پیاده سازی کنیم. در عوض، ScriptSig به فیلدهای Signature و PubKey تقسیم می شود و ScriptPubKey به PubKeyHash تغییر نام می دهد. ما همان منطق قفل/باز کردن قفل خروجی و امضای ورودی‌ها را مانند بیت کوین پیاده‌سازی می‌کنیم، اما در عوض این کار را در متدها انجام خواهیم داد.روش UsesKey بررسی می کند که یک ورودی از یک کلید خاص برای باز کردن قفل خروجی استفاده می کند. توجه داشته باشید که ورودی‌ها کلیدهای عمومی خام را ذخیره می‌کنند (یعنی هش نشده)، اما تابع یک هش می‌گیرد. IsLockedWithKey بررسی می کند که آیا از هش کلید عمومی ارائه شده برای قفل کردن خروجی استفاده شده است. این یک تابع مکمل UsesKey است و هر دو در FindUnspentTransactions برای ایجاد ارتباط بین تراکنش ها استفاده می شوند.این LOCK به سادگی یک خروجی را قفل می کند. وقتی برای شخصی سکه می فرستیم، فقط آدرس او را می دانیم، بنابراین تابع یک آدرس را به عنوان تنها آرگومان می گیرد. سپس آدرس رمزگشایی می شود و هش کلید عمومی از آن استخراج می شود و در قسمت PubKeyHash ذخیره می شود.اکنون، بیایید بررسی کنیم که همه چیز به درستی کار می کند:$ blockchain_go createwalletYour new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt$ blockchain_go createwalletYour new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h$ blockchain_go createwalletYour new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy$ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724dDone!$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXtBalance of &#x27;13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt&#x27;: 10$ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 52017/09/12 13:08:56 ERROR: Not enough funds$ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 600000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162Success!$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXtBalance of &#x27;13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt&#x27;: 4$ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5hBalance of &#x27;15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h&#x27;: 6$ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2syBalance of &#x27;1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy&#x27;: 0خب! حالا بیایید امضاهای تراکنش را پیاده سازی کنیم.پیاده سازی امضاها یا Signaturesتراکنش ها باید امضا شوند زیرا این تنها راهی است که در بیت کوین تضمین می کند که شخص نمی تواند سکه های متعلق به شخص دیگری را خرج کند. اگر امضای نامعتبر باشد، تراکنش نیز نامعتبر تلقی می شود و بنابراین نمی توان آن را به بلاکچین اضافه کرد.ما همه قطعات را برای اجرای امضای تراکنش ها داریم، به جز یک چیز: داده هایی که باید امضا شوند. چه بخش هایی از یک معامله در واقع امضا می شود؟ یا یک معامله به طور کلی امضا شده است؟ انتخاب داده برای امضا بسیار مهم است. موضوع این است که داده هایی که باید امضا شوند باید حاوی اطلاعاتی باشند که داده ها را به روشی منحصر به فرد شناسایی کند. به عنوان مثال، امضا کردن فقط مقادیر خروجی معنی ندارد زیرا چنین امضایی فرستنده و گیرنده را در نظر نمی گیرد.با توجه به اینکه تراکنش‌ها خروجی‌های قبلی را باز می‌کنند، مقادیر آنها را دوباره توزیع می‌کنند و خروجی‌های جدید را قفل می‌کنند، داده‌های زیر باید امضا شوند:۱- هش های کلید عمومی که در خروجی های قفل نشده ذخیره می شوند. این &quot;فرستنده&quot; تراکنش را مشخص می کند.۲- هش های کلید عمومی که در خروجی های جدید قفل شده ذخیره می شوند. این &quot;گیرنده&quot; تراکنش را مشخص می کند.۳- مقدار خروجی های جدیددر بیت کوین، منطق قفل/باز کردن قفل در اسکریپت هایی ذخیره می شود که به ترتیب در فیلدهای ورودی و خروجی ScriptSig و ScriptPubKey ذخیره می شوند. از آنجایی که بیت کوین انواع مختلفی از این اسکریپت ها را مجاز می کند، کل محتوای ScriptPubKey را امضا می کند.همانطور که می بینید، ما نیازی به امضای کلیدهای عمومی ذخیره شده در ورودی ها نداریم. به همین دلیل، در بیت کوین، این یک تراکنش امضا شده نیست، بلکه کپی کوتاه شده آن با ورودی هایی است که ScriptPubKey را از خروجی های ارجاع داده شده ذخیره می کند.روند دقیق دریافت یک کپی تراکنش کوتاه شده در اینجا شرح داده شده است. احتمالاً منسوخ شده است، اما من نتوانستم منبع اطلاعاتی قابل اعتمادتری پیدا کنم.خب، پیچیده به نظر می رسد، بنابراین اجازه دهید شروع به کدنویسی کنیم. ما با روش Sign شروع می کنیم:func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	if tx.IsCoinbase() {
		return
	}

	txCopy := tx.TrimmedCopy()

	for inID, vin := range txCopy.Vin {
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil

		r, s, err := ecdsa.Sign(rand.Reader, &amp;privKey, txCopy.ID)
		signature := append(r.Bytes(), s.Bytes()...)

		tx.Vin[inID].Signature = signature
	}
}این متد یک کلید خصوصی و map ای از تراکنش های قبلی را می گیرد. همانطور که در بالا ذکر شد، برای امضای یک تراکنش، باید به خروجی های اشاره شده در ورودی های تراکنش دسترسی داشته باشیم، بنابراین به تراکنش هایی نیاز داریم که این خروجی ها را ذخیره می کنند.بیایید این روش را مرحله به مرحله مرور کنیم:if tx.IsCoinbase() {
	return
}تراکنش های کوین بیس امضا نمی شوند زیرا هیچ ورودی واقعی در آنها وجود ندارد.txCopy := tx.TrimmedCopy()یک نسخه بریده شده امضا خواهد شد، نه یک تراکنش کامل:func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	for _, vin := range tx.Vin {
		inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
	}

	for _, vout := range tx.Vout {
		outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
	}

	txCopy := Transaction{tx.ID, inputs, outputs}

	return txCopy
}کپی شامل تمام ورودی‌ها و خروجی‌ها می‌شود، اما TXInput.Signature و TXInput.PubKey روی nil تنظیم شده‌اند.بعد، روی هر ورودی در کپی تکرار می کنیم:for inID, vin := range txCopy.Vin {
	prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
	txCopy.Vin[inID].Signature = nil
	txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHashدر هر ورودی، Signature روی nil (فقط یک بررسی مجدد) و PubKey روی PubKeyHash خروجی ارجاع شده تنظیم می شود. در این لحظه، همه تراکنش‌ها به جز تراکنش فعلی «خالی» هستند، یعنی فیلدهای Signature و PubKey آنها روی nil تنظیم شده‌اند. بنابراین، ورودی ها به طور جداگانه امضا می شوند، اگرچه این برای برنامه ما ضروری نیست، اما بیت کوین اجازه می دهد تا تراکنش ها حاوی ورودی هایی باشند که به آدرس های مختلف اشاره می کنند.txCopy.ID = txCopy.Hash()
	txCopy.Vin[inID].PubKey = nilمتد Hash تراکنش را سریالی می کند و آن را با الگوریتم SHA-256 هش می کند. هش حاصل، داده‌ای است که ما امضا می‌کنیم. پس از دریافت هش، باید فیلد PubKey را بازنشانی کنیم تا بر تکرارهای بعدی تأثیری نگذارد.حال، قطعه مرکزی:r, s, err := ecdsa.Sign(rand.Reader, &amp;privKey, txCopy.ID)
	signature := append(r.Bytes(), s.Bytes()...)

	tx.Vin[inID].Signature = signatureما txCopy.ID را با privKey امضا می کنیم. امضای ECDSA یک جفت اعداد است که آنها را به هم متصل کرده و در قسمت امضای ورودی ذخیره می کنیم.حالا تابع تایید:func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	txCopy := tx.TrimmedCopy()
	curve := elliptic.P256()

	for inID, vin := range tx.Vin {
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil

		r := big.Int{}
		s := big.Int{}
		sigLen := len(vin.Signature)
		r.SetBytes(vin.Signature[:(sigLen / 2)])
		s.SetBytes(vin.Signature[(sigLen / 2):])

		x := big.Int{}
		y := big.Int{}
		keyLen := len(vin.PubKey)
		x.SetBytes(vin.PubKey[:(keyLen / 2)])
		y.SetBytes(vin.PubKey[(keyLen / 2):])

		rawPubKey := ecdsa.PublicKey{curve, &amp;x, &amp;y}
		if ecdsa.Verify(&amp;rawPubKey, txCopy.ID, &amp;r, &amp;s) == false {
			return false
		}
	}

	return true
}متد کاملاً ساده است. ابتدا به همان نسخه تراکنش نیاز داریم:txCopy := tx.TrimmedCopy()در مرحله بعد، به همان منحنی که برای تولید جفت کلید استفاده می شود نیاز داریم:curve := elliptic.P256()سپس، امضا را در هر ورودی بررسی می کنیم:for inID, vin := range tx.Vin {
	prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
	txCopy.Vin[inID].Signature = nil
	txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
	txCopy.ID = txCopy.Hash()
	txCopy.Vin[inID].PubKey = nilاین قطعه مشابه موردی است که در روش Sign وجود دارد، زیرا در حین تأیید به همان داده هایی نیاز داریم که امضا شده است.   r := big.Int{}
	s := big.Int{}
	sigLen := len(vin.Signature)
	r.SetBytes(vin.Signature[:(sigLen / 2)])
	s.SetBytes(vin.Signature[(sigLen / 2):])

	x := big.Int{}
	y := big.Int{}
	keyLen := len(vin.PubKey)
	x.SetBytes(vin.PubKey[:(keyLen / 2)])
	y.SetBytes(vin.PubKey[(keyLen / 2):])در اینجا ما مقادیر ذخیره شده در TXInput.Signature و TXInput.PubKey را باز می کنیم، زیرا یک امضا یک جفت عدد و یک کلید عمومی یک جفت مختصات است. ما قبلاً آنها را برای ذخیره سازی به هم متصل کردیم، و اکنون باید آنها را باز کنیم تا در توابعcrypto/ecdsa استفاده کنیم.rawPubKey := ecdsa.PublicKey{curve, &amp;x, &amp;y}
	if ecdsa.Verify(&amp;rawPubKey, txCopy.ID, &amp;r, &amp;s) == false {
		return false
	}
}

return trueاینجاست: ما یک ecdsa.PublicKey را با استفاده از کلید عمومی استخراج شده از ورودی ایجاد می کنیم و ecdsa.Verify را اجرا می کنیم. امضای استخراج شده از ورودی را تأیید می کنیم. اگر همه ورودی‌ها تأیید شوند، true را برگردانید. اگر حداقل یک ورودی تأیید نشد، false را برگردانید.اکنون برای به دست آوردن تراکنش های قبلی به یک تابع نیاز داریم. از آنجایی که این امر مستلزم تعامل با بلاکچین است، آن را به روشی از بلاکچین تبدیل خواهیم کرد:func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
	bci := bc.Iterator()

	for {
		block := bci.Next()

		for _, tx := range block.Transactions {
			if bytes.Compare(tx.ID, ID) == 0 {
				return *tx, nil
			}
		}

		if len(block.PrevBlockHash) == 0 {
			break
		}
	}

	return Transaction{}, errors.New(&amp;quotTransaction is not found&amp;quot)
}

func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
	prevTXs := make(map[string]Transaction)

	for _, vin := range tx.Vin {
		prevTX, err := bc.FindTransaction(vin.Txid)
		prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
	}

	tx.Sign(privKey, prevTXs)
}

func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
	prevTXs := make(map[string]Transaction)

	for _, vin := range tx.Vin {
		prevTX, err := bc.FindTransaction(vin.Txid)
		prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
	}

	return tx.Verify(prevTXs)
}این توابع ساده هستند: FindTransaction یک تراکنش را با شناسه پیدا می کند (این کار مستلزم تکرار بر روی تمام بلوک های زنجیره بلاک است). SignTransaction یک تراکنش را می گیرد، تراکنش هایی را که به آن ارجاع می دهد پیدا می کند و آن را امضا می کند. VerifyTransaction هم همین کار را می کند، اما در عوض تراکنش را تأیید می کند.اکنون، ما باید در واقع معاملات را امضا و تأیید کنیم. امضا در NewUTXOTtransaction انجام می شود:func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
	...

	tx := Transaction{nil, inputs, outputs}
	tx.ID = tx.Hash()
	bc.SignTransaction(&amp;tx, wallet.PrivateKey)

	return &amp;tx
}تأیید قبل از قرار دادن یک تراکنش در یک بلوک انجام می شود:func (bc *Blockchain) MineBlock(transactions []*Transaction) {
	var lastHash []byte

	for _, tx := range transactions {
		if bc.VerifyTransaction(tx) != true {
			log.Panic(&amp;quotERROR: Invalid transaction&amp;quot)
		}
	}
	...
}و بس! بیایید یک بار دیگر همه چیز را بررسی کنیم:$ blockchain_go createwalletYour new address: 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR$ blockchain_go createwalletYour new address: 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab$ blockchain_go createblockchain -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR000000122348da06c19e5c513710340f4c307d884385da948a205655c6a9d008Done!$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 60000000f3dbb0ab6d56c4e4b9f7479afe8d5a5dad4d2a8823345a1a16cf3347bSuccess!$ blockchain_go getbalance -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avRBalance of &#x27;1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR&#x27;: 4$ blockchain_go getbalance -address 1NE86r4Esjf53EL7fR86CsfTZpNN42SfabBalance of &#x27;1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab&#x27;: 6هیچ چیز خراب نیست. عالی!بیایید همچنین در مورد فراخوان bc.SignTransaction (&amp;tx، wallet.PrivateKey) در NewUTXOTransaction نظر بدهیم تا اطمینان حاصل شود که تراکنش‌های بدون امضا قابل استخراج نیستند:func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
   ...
	tx := Transaction{nil, inputs, outputs}
	tx.ID = tx.Hash()
	// bc.SignTransaction(&amp;tx, wallet.PrivateKey)

	return &amp;tx
}$ go install$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 12017/09/12 16:28:15 ERROR: Invalid transactionنتیجه گیریاین واقعاً عالی است که ما تا اینجای کار انجام داده ایم و بسیاری از ویژگی های کلیدی بیت کوین را اجرا کرده ایم! ما تقریباً همه چیز را در خارج از شبکه پیاده سازی کرده ایم، و در قسمت بعدی، تراکنش ها را به پایان خواهیم رساند.پایان قسمت پنجمقسمت ششمباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Wed, 15 Jun 2022 15:59:39 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت چهارم (تراکنش ها-۱)</title>
                <link>https://virgool.io/Solidity/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%DA%86%D9%87%D8%A7%D8%B1%D9%85-%D8%AA%D8%B1%D8%A7%DA%A9%D9%86%D8%B4-%D9%87%D8%A7-%DB%B1-bda9gbgmz7k7</link>
                <description>مقدمهتراکنش ها قلب بیت کوین هستند و تنها هدف بلاکچین ذخیره تراکنش ها به روشی ایمن و قابل اعتماد است، بنابراین هیچ کس نمی تواند آنها را پس از ایجاد تغییر دهد. امروز ما اجرای تراکنش ها را آغاز می کنیم. اما از آنجایی که این یک موضوع کاملاً بزرگ است، آن را به دو بخش تقسیم می‌کنم: در این بخش، مکانیسم کلی تراکنش‌ها را پیاده‌سازی می‌کنیم و در قسمت دوم، جزئیات را بررسی می‌کنیم.همچنین، از آنجایی که تغییرات کد بسیار زیاد است، توصیف همه آنها در اینجا بی معنی است. شما می توانید تمام تغییرات را اینجا ببینید.قاشقی وجود ندارداگر تا به حال یک برنامه وب ایجاد کرده اید، به منظور اجرای پرداخت ها، احتمالاً این جداول را در یک DB ایجاد می کنید: حساب ها (accounts) و تراکنش ها (transactions). یک حساب اطلاعات مربوط به یک کاربر، از جمله اطلاعات شخصی و موجودی او را ذخیره می کند، و یک تراکنش اطلاعات مربوط به انتقال پول از یک حساب به حساب دیگر را ذخیره می کند. در بیت کوین، پرداخت ها به روشی کاملا متفاوت انجام می شود. آنها موارد زیر هستند :۱- بدون حساب.۲- بدون بالانس یا موجودی۳- بدون آدرس۴- بدون سکه۵- بدون فرستنده و گیرندهاز آنجایی که بلاکچین یک پایگاه داده عمومی و باز است، ما نمی‌خواهیم اطلاعات حساس در مورد صاحبان کیف پول را ذخیره کنیم. سکه ها در حساب ها جمع آوری نمی شوند. تراکنش ها پولی را از یک آدرس به آدرس دیگر منتقل نمی کنند. هیچ فیلد یا مشخصه ای وجود ندارد که موجودی حساب را نگه دارد. فقط معاملات وجود دارد. اما درون یک تراکنش چیست؟تراکنش بیت کوینتراکنش ترکیبی از ورودی و خروجی است:type Transaction struct {
	ID   []byte
	Vin  []TXInput
	Vout []TXOutput
}ورودی‌های یک تراکنش جدید، خروجی‌های یک تراکنش قبلی است (البته یک استثنا وجود دارد که بعداً درباره آن صحبت خواهیم کرد). خروجی ها جایی هستند که سکه ها در واقع ذخیره می شوند. نمودار زیر ارتباط متقابل تراکنش ها را نشان می دهد:توجه کنید که:۱- خروجی هایی هستند که به ورودی ها مرتبط نیستند.۲- در یک تراکنش، ورودی ها می توانند به خروجی های چند تراکنش اشاره کنند.۳- یک ورودی باید به یک خروجی اشاره کند.در طول این مقاله، ما از کلماتی مانند &quot;پول&quot; (money)، &quot;سکه&quot;(coins)، &quot;خرج کردن&quot;(spend)، &quot;ارسال&quot;(send)، &quot;حساب&quot;(account) و غیره استفاده خواهیم کرد. اما چنین مفاهیمی در بیت کوین وجود ندارد. تراکنش‌ها فقط مقادیر را با یک اسکریپت قفل می‌کنند، که فقط توسط کسی که آنها را قفل کرده است می‌تواند باز شود.خروجی های تراکنش یا Transaction Outputsابتدا با خروجی ها شروع می کنیم:type TXOutput struct {
	Value        int
	ScriptPubKey string
}در واقع، این خروجی‌ها هستند که «سکه‌ها» را ذخیره می‌کنند (به قسمت Value در بالا توجه کنید) و ذخیره به معنای قفل کردن آنها با یک پازل است که در ScriptPubKey ذخیره می شود. در داخل، بیت کوین از یک زبان برنامه نویسی به نام Script استفاده می کند که برای تعریف منطق قفل و باز کردن خروجی ها استفاده می شود. این زبان کاملاً ابتدایی است (این به طور عمدی ساخته شده است تا از هک ها و سوء استفاده های احتمالی جلوگیری شود)، اما ما در مورد آن با جزئیات صحبت نمی کنیم. در اینجا می توانید توضیح دقیقی در مورد آن پیدا کنید.در بیت کوین، فیلد Value تعداد ساتوشی ها را ذخیره می کند، نه تعداد بیت کوین. ساتوشی صد میلیونیم بیت کوین (0.00000001 BTC) است، بنابراین این کوچکترین واحد ارز در بیت کوین (مانند یک سنت) است.از آنجایی که ما آدرس‌ها را پیاده‌سازی نکرده ایم، فعلاً از کل منطق مربوط به اسکریپت‌نویسی اجتناب می‌کنیم. ScriptPubKey یک رشته دلخواه (آدرس کیف پول تعریف شده توسط کاربر) را ذخیره می کند.به هر حال، داشتن چنین زبان برنامه نویسی به این معنی است که بیت کوین می تواند به عنوان یک پلتفرم قرارداد هوشمند (smart-contract) نیز مورد استفاده قرار گیرد.یک چیز مهم در مورد خروجی ها این است که آنها تقسیم ناپذیر (indivisible) هستند، به این معنی که شما نمی توانید بخشی از مقدار آن را ارجاع دهید. هنگامی که یک خروجی در یک تراکنش جدید ارجاع داده می شود، به عنوان یک کل مصرف می شود. و اگر مقدار آن بیشتر از مقدار مورد نیاز باشد، تغییر ایجاد شده و به فرستنده بازگردانده می شود. این شبیه شرایط دنیای واقعی است که شما مثلاً یک اسکناس 5 دلاری را برای چیزی که 1 دلار قیمت دارد پرداخت می کنید و 4 دلار برگردانده می شود.ورودی های تراکنش یا Transaction Inputsو این ورودی است:type TXInput struct {
	Txid      []byte
	Vout      int
	ScriptSig string
}همانطور که قبلا ذکر شد، یک ورودی به خروجی قبلی اشاره می کند: Txid شناسه چنین تراکنش را ذخیره می کند و Vout اندیس یک خروجی را در تراکنش ذخیره می کند. ScriptSig یک اسکریپت است که داده هایی را برای استفاده در ScriptPubKey یک خروجی فراهم می کند. اگر داده ها درست باشند، خروجی را می توان باز کرد و از مقدار آن برای تولید خروجی های جدید استفاده کرد. اگر درست نباشد، خروجی را نمی توان در ورودی ارجاع داد. این مکانیزمی است که تضمین می کند کاربران نمی توانند سکه های متعلق به افراد دیگر را خرج کنند.باز هم، از آنجایی که ما هنوز آدرسی را پیاده سازی نکرده ایم، ScriptSig فقط یک آدرس کیف پول دلخواه تعریف شده توسط کاربر را ذخیره می کند. در مقاله بعدی بررسی کلیدهای عمومی و امضاها را اجرا خواهیم کرد.بیایید آن را خلاصه کنیم. خروجی ها جایی هستند که &quot;سکه ها&quot; ذخیره می شوند. هر خروجی دارای یک اسکریپت باز کردن قفل است که منطق باز کردن قفل خروجی را تعیین می کند. هر تراکنش جدید باید حداقل یک ورودی و خروجی داشته باشد. یک ورودی به خروجی یک تراکنش قبلی اشاره می کند و داده هایی (فیلد ScriptSig) را ارائه می دهد که در اسکریپت باز کردن قفل خروجی برای باز کردن قفل آن و استفاده از مقدار آن برای ایجاد خروجی های جدید استفاده می شود.اما چه چیزی اول شد: ورودی یا خروجی؟اول تخم مرغ بوده. (اشاره به اول مرغ بوده یا تخم مرغ)در بیت کوین، این تخم مرغ است که قبل از مرغ آمده است. منطق ورودی-ارجاع-خروجی ها وضعیت کلاسیک &quot;مرغ یا تخم مرغ&quot; است. ورودی ها خروجی ها را تولید می کنند و خروجی ها ورودی ها را ممکن می کنند. و در بیت کوین، خروجی ها قبل از ورودی ها قرار می گیرند.هنگامی که یک ماینر شروع به استخراج یک بلوک می کند، یک تراکنش کوین بیس یا coinbase transaction به آن اضافه می کند. تراکنش کوین بیس نوع خاصی از تراکنش است که به خروجی های قبلی نیاز ندارد. خروجی‌ها (یعنی «سکه‌ها») را از هیچ‌جا ایجاد می‌کند. تخم مرغ بدون مرغ این پاداشی است که ماینرها برای استخراج بلوک های جدید دریافت می کنند.همانطور که می دانید، بلوک پیدایش (genesis block) در ابتدای زنجیره بلوکی وجود دارد. این بلوک است که اولین خروجی را در بلاک چین تولید می کند. و هیچ خروجی قبلی لازم نیست زیرا هیچ تراکنش قبلی و چنین خروجی وجود ندارد.بیایید یک تراکنش کوین بیس ایجاد کنیم:func NewCoinbaseTX(to, data string) *Transaction {
	if data == &amp;quot&amp;quot {
		data = fmt.Sprintf(&amp;quotReward to &#039;%s&#039;&amp;quot, to)
	}

	txin := TXInput{[]byte{}, -1, data}
	txout := TXOutput{subsidy, to}
	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
	tx.SetID()

	return &amp;tx
}یک تراکنش کوین بیس تنها یک ورودی دارد. در اجرای ما Txid آن خالی است و Vout برابر با 1- است. همچنین، یک تراکنش coinbase یک اسکریپت را در ScriptSig ذخیره نمی کند. در عوض، داده های دلخواه در آنجا ذخیره می شود.در بیت کوین، اولین تراکنش کوین بیس حاوی پیام زیر است: &quot;The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”&quot;. خودت میتونی ببینیشاین subsidy مقدار پاداش است. در بیت کوین، این عدد در هیچ کجا ذخیره نمی شود و فقط بر اساس تعداد کل بلاک ها محاسبه می شود: تعداد بلاک ها بر 210000 تقسیم می شود. استخراج بلوک پیدایش 50 BTC تولید می کند و هر 210000 بلوک مقدار پاداش نصف می شود. در اجرای خود، پاداش را به عنوان یک ثابت ذخیره می کنیم (حداقل در حال حاضر ?).ذخیره تراکنش ها در بلاک چیناز این پس، هر بلوک باید حداقل یک تراکنش را ذخیره کند و دیگر امکان استخراج بلوک بدون تراکنش وجود ندارد. این بدان معنی است که ما باید فیلد Data را از Block حذف کنیم و به جای آن تراکنش ها را ذخیره کنیم:type Block struct {
	Timestamp     int64
	Transactions  []*Transaction
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}دو تابع NewBlock و NewGenesisBlock نیز باید بر این اساس تغییر کنند:func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
	block := &amp;Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
	...
}

func NewGenesisBlock(coinbase *Transaction) *Block {
	return NewBlock([]*Transaction{coinbase}, []byte{})
}چیزی که باید تغییر کند ایجاد یک بلاکچین جدید است:func CreateBlockchain(address string) *Blockchain {
	...
	err = db.Update(func(tx *bolt.Tx) error {
		cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
		genesis := NewGenesisBlock(cbtx)

		b, err := tx.CreateBucket([]byte(blocksBucket))
		err = b.Put(genesis.Hash, genesis.Serialize())
		...
	})
	...
}اکنون، تابع آدرسی می گیرد که پاداش استخراج بلوک پیدایش را دریافت می کند.اصلاح  الگوریتم Proof-of-Workالگوریتم Proof-of-Work باید تراکنش های ذخیره شده در یک بلوک را در نظر بگیرد تا ثبات و قابلیت اطمینان بلاکچین را به عنوان ذخیره تراکنش تضمین کند. بنابراین اکنون باید روش ProofOfWork.prepareData را اصلاح کنیم:func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.HashTransactions(), // This line was changed
			IntToHex(pow.block.Timestamp),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)

	return data
}به جای pow.block.Data، اکنون از تابع  pow.block.HashTransactions استفاده می کنیم که عبارت است از:func (b *Block) HashTransactions() []byte {
	var txHashes [][]byte
	var txHash [32]byte

	for _, tx := range b.Transactions {
		txHashes = append(txHashes, tx.ID)
	}
	txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))

	return txHash[:]
}باز هم، ما از هش به عنوان مکانیزمی برای ارائه نمایش منحصر به فرد داده ها استفاده می کنیم. ما می‌خواهیم همه تراکنش‌های یک بلوک به‌طور منحصربه‌فرد توسط یک هش شناسایی شوند. برای رسیدن به این هدف، هش های هر تراکنش را دریافت می کنیم، آنها را به هم متصل می کنیم و یک هش از ترکیب الحاقی را دریافت می کنیم.بیت کوین از تکنیک پیچیده تری استفاده می کند: تمام تراکنش های موجود در یک بلوک را به عنوان درخت مرکل یا Merkle tree نشان می دهد و از ریشه هش درخت در سیستم اثبات کار استفاده می کند. این رویکرد به شما امکان می دهد تا به سرعت بررسی کنید که آیا یک بلوک حاوی تراکنش خاصی است، فقط با داشتن هش ریشه و بدون دانلود همه تراکنش ها.بیایید بررسی کنیم که همه چیز تا اینجا درست است:$ blockchain_go createblockchain -address Ivan00000093450837f8b52b78c25f8163bb6137caf43ff4d9a01d1b731fa8ddcc8aDone!خب! ما اولین جایزه استخراج را دریافت کردیم. اما چگونه موجودی یک کاربر یا بالانس را بررسی کنیم؟خروجی های تراکنش خرج نشده یا Unspent Transaction Outputsما باید تمام خروجی های تراکنش مصرف نشده (UTXO) را پیدا کنیم. مصرف نشده به این معنی است که این خروجی ها در هیچ ورودی ارجاع نشده اند. در نمودار بالا، این موارد عبارتند از:tx0, output 1;tx1, output 0;tx3, output 0;tx4, output 0.البته، وقتی بالانس یا موجودی را بررسی می‌کنیم، به همه آن‌ها نیاز نداریم، بلکه فقط به آن‌هایی نیاز داریم که می‌توان با کلیدی که در اختیار ماست، قفل آن را باز کرد (در حال حاضر کلیدها پیاده‌سازی نشده اند و به جای آن از آدرس‌های تعریف‌شده کاربر استفاده می‌کنیم). ابتدا، بیایید روش های قفل-باز کردن قفل (locking-unlocking) را در ورودی ها و خروجی ها تعریف کنیم:در اینجا ما فقط فیلدهای اسکریپت را با unlockingData مقایسه می کنیم. پس از پیاده سازی آدرس ها بر اساس کلیدهای خصوصی، این قطعات در مقاله آینده بهبود خواهند یافت.func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
	return in.ScriptSig == unlockingData
}

func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
	return out.ScriptPubKey == unlockingData
}مرحله بعدی - یافتن تراکنش های حاوی خروجی های خرج نشده - این مورد بسیار دشوار است:func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
  var unspentTXs []Transaction
  spentTXOs := make(map[string][]int)
  bci := bc.Iterator()

  for {
    block := bci.Next()

    for _, tx := range block.Transactions {
      txID := hex.EncodeToString(tx.ID)

    Outputs:
      for outIdx, out := range tx.Vout {
        // Was the output spent?
        if spentTXOs[txID] != nil {
          for _, spentOut := range spentTXOs[txID] {
            if spentOut == outIdx {
              continue Outputs
            }
          }
        }

        if out.CanBeUnlockedWith(address) {
          unspentTXs = append(unspentTXs, *tx)
        }
      }

      if tx.IsCoinbase() == false {
        for _, in := range tx.Vin {
          if in.CanUnlockOutputWith(address) {
            inTxID := hex.EncodeToString(in.Txid)
            spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
          }
        }
      }
    }

    if len(block.PrevBlockHash) == 0 {
      break
    }
  }

  return unspentTXs
}از آنجایی که تراکنش ها در بلوک ها ذخیره می شوند، ما باید هر بلوک را در یک زنجیره بلوکی بررسی کنیم. با خروجی ها شروع می کنیم:if out.CanBeUnlockedWith(address) {
	unspentTXs = append(unspentTXs, tx)
}اگر خروجی با همان آدرسی که ما در حال جستجوی خروجی های تراکنش خرج نشده آن هستیم قفل شده باشد، این همان خروجی است که می خواهیم. اما قبل از گرفتن آن، باید بررسی کنیم که آیا یک خروجی قبلاً در یک ورودی ارجاع شده است یا خیر:if spentTXOs[txID] != nil {
	for _, spentOut := range spentTXOs[txID] {
		if spentOut == outIdx {
			continue Outputs
		}
	}
}ما از مواردی که در ورودی ها به آنها اشاره شده بود صرفنظر می کنیم (مقادیر آنها به خروجی های دیگر منتقل شده است، بنابراین نمی توانیم آنها را بشماریم). پس از بررسی خروجی‌ها، همه ورودی‌هایی را جمع‌آوری می‌کنیم که می‌توانند قفل خروجی‌ها را با آدرس ارائه‌شده باز کنند (این برای تراکنش‌های coinbase صدق نمی‌کند، زیرا آنها قفل خروجی‌ها را باز نمی‌کنند):if tx.IsCoinbase() == false {
    for _, in := range tx.Vin {
        if in.CanUnlockOutputWith(address) {
            inTxID := hex.EncodeToString(in.Txid)
            spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
        }
    }
}این تابع فهرستی از تراکنش های حاوی خروجی های خرج نشده را برمی گرداند. برای محاسبه بالانس یا موجودی به یک تابع دیگر نیاز داریم که تراکنش ها را می گیرد و فقط خروجی ها را برمی گرداند:func (bc *Blockchain) FindUTXO(address string) []TXOutput {
       var UTXOs []TXOutput
       unspentTransactions := bc.FindUnspentTransactions(address)

       for _, tx := range unspentTransactions {
               for _, out := range tx.Vout {
                       if out.CanBeUnlockedWith(address) {
                               UTXOs = append(UTXOs, out)
                       }
               }
       }

       return UTXOs
}خودشه! اکنون می توانیم دستور getbalance را پیاده سازی کنیم:func (cli *CLI) getBalance(address string) {
	bc := NewBlockchain(address)
	defer bc.db.Close()

	balance := 0
	UTXOs := bc.FindUTXO(address)

	for _, out := range UTXOs {
		balance += out.Value
	}

	fmt.Printf(&amp;quotBalance of &#039;%s&#039;: %d\n&amp;quot, address, balance)
}مانده حساب مجموع مقادیر تمام خروجی های تراکنش خرج نشده است که توسط آدرس حساب قفل شده اند.بیایید بالانس خود را پس از استخراج بلوک پیدایش بررسی کنیم:$ blockchain_go getbalance -address IvanBalance of &#x27;Ivan&#x27;: 10این اولین پول ماست!ارسال سکه Sending Coinsاکنون می خواهیم چند سکه برای شخص دیگری ارسال کنیم. برای این کار، باید یک تراکنش جدید ایجاد کنیم، آن را در یک بلوک قرار دهیم و بلوک را استخراج کنیم. تا به حال فقط تراکنش coinbase (که نوع خاصی از تراکنش ها است) را پیاده سازی کرده بودیم، اکنون به یک تراکنش عمومی نیاز داریم:func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	acc, validOutputs := bc.FindSpendableOutputs(from, amount)

	if acc &lt; amount {
		log.Panic(&amp;quotERROR: Not enough funds&amp;quot)
	}

	// Build a list of inputs
	for txid, outs := range validOutputs {
		txID, err := hex.DecodeString(txid)

		for _, out := range outs {
			input := TXInput{txID, out, from}
			inputs = append(inputs, input)
		}
	}

	// Build a list of outputs
	outputs = append(outputs, TXOutput{amount, to})
	if acc &gt; amount {
		outputs = append(outputs, TXOutput{acc - amount, from}) // a change
	}

	tx := Transaction{nil, inputs, outputs}
	tx.SetID()

	return &amp;tx
}قبل از ایجاد خروجی های جدید، ابتدا باید تمام خروجی های مصرف نشده را پیدا کرده و مطمئن شویم که ارزش کافی را ذخیره می کنند. این کاری است که روش FindSpendableOutputs انجام می دهد. پس از آن، برای هر خروجی یافت شده یک مرجع ورودی ایجاد می شود. سپس دو خروجی ایجاد می کنیم:۱- یکی که با آدرس گیرنده قفل شده است. این انتقال واقعی سکه ها به آدرس دیگری است.۲- یکی که با آدرس فرستنده قفل شده است. این یک تغییر است. فقط زمانی ایجاد می شود که خروجی های خرج نشده ارزش بیشتری نسبت به تراکنش جدید داشته باشند. به یاد داشته باشید: خروجی ها تقسیم ناپذیر یا indivisible هستند.متد FindSpendableOutputs مبتنی بر روش FindUnspentTransactions است که قبلا تعریف کردیم:func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
	unspentOutputs := make(map[string][]int)
	unspentTXs := bc.FindUnspentTransactions(address)
	accumulated := 0

Work:
	for _, tx := range unspentTXs {
		txID := hex.EncodeToString(tx.ID)

		for outIdx, out := range tx.Vout {
			if out.CanBeUnlockedWith(address) &amp;&amp; accumulated &lt; amount {
				accumulated += out.Value
				unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

				if accumulated &gt;= amount {
					break Work
				}
			}
		}
	}

	return accumulated, unspentOutputs
}این روش بر روی تمام تراکنش های خرج نشده تکرار می شود و مقادیر آنها را جمع می کند. هنگامی که مقدار انباشته بیشتر یا برابر با مقداری است که می‌خواهیم انتقال دهیم، متوقف می‌شود و مقدار انباشته و شاخص‌های خروجی گروه‌بندی شده بر اساس شناسه‌های تراکنش را برمی‌گرداند. ما نمی‌خواهیم بیشتر از چیزی که قرار است خرج کنیم، بگیریم.اکنون می توانیم روش Blockchain.MineBlock را اصلاح کنیم:func (bc *Blockchain) MineBlock(transactions []*Transaction) {
	...
	newBlock := NewBlock(transactions, lastHash)
	...
}در نهایت، اجازه دهید دستور ارسال یا  send را پیاده سازی کنیم:func (cli *CLI) send(from, to string, amount int) {
	bc := NewBlockchain(from)
	defer bc.db.Close()

	tx := NewUTXOTransaction(from, to, amount, bc)
	bc.MineBlock([]*Transaction{tx})
	fmt.Println(&amp;quotSuccess!&amp;quot)
}ارسال سکه به معنای ایجاد یک تراکنش و افزودن آن به بلاک چین از طریق استخراج یک بلوک است. اما بیت کوین فورا این کار را انجام نمی دهد (مانند ما). در عوض، تمام تراکنش‌های جدید را در مموری و حافظه (یا mempool) قرار می‌دهد و زمانی که ماینر آماده استخراج یک بلوک است، تمام تراکنش‌ها را از mempool می‌گیرد و یک بلوک کاندید ایجاد می‌کند. تراکنش‌ها تنها زمانی تایید می‌شوند که بلوکی حاوی آن‌ها استخراج شده و به بلاک چین اضافه شود.بیایید بررسی کنیم که ارسال سکه کار می کند:$ blockchain_go send -from Ivan -to Pedro -amount 600000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37Success!$ blockchain_go getbalance -address IvanBalance of &#x27;Ivan&#x27;: 4$ blockchain_go getbalance -address PedroBalance of &#x27;Pedro&#x27;: 6بسیار عالی! اکنون، بیایید تراکنش‌های بیشتری ایجاد کنیم و اطمینان حاصل کنیم که ارسال از چندین خروجی به خوبی انجام می‌شود:$ blockchain_go send -from Pedro -to Helen -amount 200000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdfSuccess!$ blockchain_go send -from Ivan -to Helen -amount 2000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aaSuccess!اکنون سکه های هلن در دو خروجی قفل شده اند: یکی از پدرو و دیگری از ایوان. بیایید آنها را برای شخص دیگری بفرستیم:$ blockchain_go send -from Helen -to Rachel -amount 3000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0Success!$ blockchain_go getbalance -address IvanBalance of &#x27;Ivan&#x27;: 2$ blockchain_go getbalance -address PedroBalance of &#x27;Pedro&#x27;: 4$ blockchain_go getbalance -address HelenBalance of &#x27;Helen&#x27;: 1$ blockchain_go getbalance -address RachelBalance of &#x27;Rachel&#x27;: 3عالی به نظر می رسد! حالا بیایید یک شکست را آزمایش کنیم:$ blockchain_go send -from Pedro -to Ivan -amount 5panic: ERROR: Not enough funds$ blockchain_go getbalance -address PedroBalance of &#x27;Pedro&#x27;: 4$ blockchain_go getbalance -address IvanBalance of &#x27;Ivan&#x27;: 2نتیجه گیریاوه! آسان نبود، اما ما اکنون معاملات داریم! اگرچه، برخی از ویژگی های کلیدی یک ارز دیجیتال مشابه بیت کوین وجود ندارد:۱- آدرس ها. ما هنوز آدرس‌های مبتنی بر کلید خصوصی واقعی نداریم.۲- پاداش. استخراج بلوک مطلقاً سودآور نیست!۳- مجموعه UTXO. به دست آوردن بالانس نیاز به اسکن کل زنجیره بلوکی دارد، که در صورت وجود بلوک های بسیار و زیاد، ممکن است زمان بسیار زیادی طول بکشد. همچنین، اگر بخواهیم تراکنش‌های بعدی را تأیید کنیم، ممکن است زمان زیادی طول بکشد. مجموعه UTXO برای حل این مشکلات و انجام سریع عملیات با تراکنش ها در نظر گرفته شده است.۴- ممپول (Mempool). این جایی است که تراکنش ها قبل از بسته بندی در یک بلوک ذخیره می شوند. در اجرای فعلی ما، یک بلوک فقط شامل یک تراکنش است و این کاملاً ناکارآمد است.پایان قسمت چهارمقسمت پنجملینک منبعباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Wed, 15 Jun 2022 13:43:36 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت سوم (ماندگاری داده و رابط خط فرمان یا CLI)</title>
                <link>https://virgool.io/Solidity/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D8%B3%D9%88%D9%85-%D9%85%D8%A7%D9%86%D8%AF%DA%AF%D8%A7%D8%B1%DB%8C-%D8%AF%D8%A7%D8%AF%D9%87-%D9%88-%D8%B1%D8%A7%D8%A8%D8%B7-%D8%AE%D8%B7-%D9%81%D8%B1%D9%85%D8%A7%D9%86-%DB%8C%D8%A7-cli-j337n3l2xwqi</link>
                <description>مقدمهتا کنون، ما یک بلاکچین با سیستم اثبات کار (proof-of-work) ساخته‌ایم که استخراج را ممکن می‌کند. پیاده‌سازی ما در حال نزدیک‌تر شدن به یک بلاکچین کاملاً کاربردی است، اما همچنان فاقد برخی ویژگی‌های مهم است. امروز شروع به ذخیره یک بلاکچین در یک پایگاه داده می کنیم و پس از آن یک رابط خط فرمان ساده برای انجام عملیات با بلاکچین ایجاد می کنیم. در اصل، بلاکچین یک پایگاه داده توزیع شده است. فعلاً قسمت «توزیع‌شده» را حذف می‌کنیم و روی قسمت «پایگاه داده» تمرکز می‌کنیم.انتخاب پایگاه دادهدر حال حاضر، هیچ پایگاه داده ای در پیاده سازی ما وجود ندارد. در عوض، هر بار که برنامه را اجرا می کنیم بلوک هایی ایجاد می کنیم و آنها را در حافظه مموری ذخیره می کنیم. ما نمی‌توانیم از یک بلاکچین دوباره استفاده کنیم، نمی‌توانیم آن را با دیگران به اشتراک بگذاریم، بنابراین باید آن را روی دیسک ذخیره کنیم.به کدام پایگاه داده نیاز داریم؟ در واقع، هر پایگاه داده ای می تواند باشد. در مقاله اصلی بیت کوین، چیزی در مورد استفاده از یک پایگاه داده خاص گفته نشده است، بنابراین این به توسعه دهنده بستگی دارد که از چه DB استفاده کند. Bitcoin Core که در ابتدا توسط ساتوشی ناکاموتو منتشر شد و در حال حاضر یک پیاده سازی مرجع بیت کوین است، از LevelDB استفاده می کند (اگرچه تنها در سال 2012 به مشتری معرفی شد). و ما استفاده خواهیم کرد از …پایگاه داده BoltDBزیرا۱- ساده و مینیمالیست.۲-  در Go پیاده سازی شده است.۳-  نیازی به اجرای سرور ندارد.۴- این اجازه می دهد تا ساختار داده ای را که می خواهیم بسازیم.میتوانید نگاهی به README BoltDB در Github بیاندازید.این Bolt یک پایگاه داده به صورت key/value hsj که تماما با زبان Go پیاده سازی شده است که از پروژه LMDB هاوارد چو الهام گرفته شده است. هدف این پروژه ارائه یک پایگاه داده ساده، سریع و قابل اعتماد برای پروژه هایی است که به سرور پایگاه داده کامل مانند Postgres یا MySQL نیاز ندارند.از آنجایی که Bolt قرار است به عنوان یک عملکرد سطح پایین مورد استفاده قرار گیرد، سادگی امری کلیدی است.   تعداد و پیاده سازی API ها کوچک خواهد بود و فقط روی دریافت مقادیر و تنظیم مقادیر تمرکز دارد. همین.برای نیازهای ما عالی به نظر می رسد! بیایید یک دقیقه آن را مرور کنیم.این BoltDB یک ذخیره‌سازی key/value است، به این معنی که هیچ جدولی مانند SQL RDBMS (MySQL، PostgreSQL، و غیره)، هیچ ردیف و ستونی وجود ندارد. در عوض، داده ها به صورت جفت کلید-مقدار ذخیره می شوند (مانند map در Golang). جفت‌های کلید-مقدار در قالب سطل‌هایی (buckets) ذخیره می‌شوند که برای گروه‌بندی جفت‌های مشابه در نظر گرفته شده‌اند (این شبیه به جداول در RDBMS است). بنابراین، برای به دست آوردن یک مقدار، باید یک سطل (bucket) و یک کلید را بشناسید.ساختار پایگاه دادهقبل از شروع اجرای منطق ماندگاری داده، ابتدا باید تصمیم بگیریم که چگونه داده ها را در DB ذخیره کنیم و برای این، به روشی که Bitcoin Core این کار را انجام می دهد اشاره خواهیم کرد.به عبارت ساده، Bitcoin Core از دو سطل (buckets) برای ذخیره داده ها استفاده می کند:1- بلوک ها (Blocks) که متادیتا را ذخیره می کند که تمام بلوک های یک زنجیره را توصیف کند.2-  وضعیت یک زنجیره (ChainState) را ذخیره می‌کند، که تمام خروجی‌های تراکنش مصرف‌نشده در حال حاضر و برخی متادیتاها هستند.همچنین بلوک ها به صورت فایل های جداگانه روی دیسک ذخیره می شوند. این برای یک هدف عملکردی انجام می شود. خواندن یک بلوک واحد نیازی به بارگیری همه (یا برخی) از آنها در حافظه ندارد. ما این را اجرا نخواهیم کرد.در بلوک ها، جفت های کلید -&gt; مقدار عبارتند از:&#x27;b&#x27; + 32-byte block hash -&gt; block index record&#x27;f&#x27; + 4-byte file number -&gt; file information record&#x27;l&#x27; -&gt; 4-byte file number: the last block file number used&#x27;R&#x27; -&gt; 1-byte boolean: whether we&#x27;re in the process of reindexing&#x27;F&#x27; + 1-byte flag name length + flag name string -&gt; 1 byte boolean: various flags that can be on or off&#x27;t&#x27; + 32-byte transaction hash -&gt; transaction index recordدر  ChainState ها ، جفت های کلید -&gt; مقدار عبارتند از:&#x27;c&#x27; + 32-byte transaction hash -&gt; unspent transaction output record for that transaction&#x27;B&#x27; -&gt; 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs(توضیحات مفصل را می توانید در اینجا بیابید)از آنجایی که ما هنوز تراکنش نداریم، فقط سطل بلوک یا Blocks خواهیم داشت. همچنین، همانطور که در بالا گفته شد، کل DB را به صورت یک فایل واحد ذخیره می کنیم، بدون اینکه بلوک ها را در فایل های جداگانه ذخیره کنیم. بنابراین ما به هیچ چیز مرتبط با شماره فایل نیاز نخواهیم داشت. بنابراین اینها جفت های کلیدی -&gt; ارزشی هستند که از آنها استفاده خواهیم کرد:32-byte block-hash -&gt; Block structure (serialized)&#x27;l&#x27; -&gt; the hash of the last block in a chainاین تمام چیزی است که برای شروع اجرای مکانیسم پایداری باید بدانیم.سریال سازی یا Serializationهمانطور که قبلا گفته شد، مقادیر در BoltDB فقط می توانند از نوع []byte باشند، و ما می خواهیم ساختارهای Block را در DB ذخیره کنیم. ما از encoding/gob برای سریال سازی ساختارها استفاده خواهیم کرد.بیایید روش Serialize از Block را پیاده سازی کنیم (پردازش خطاها برای اختصار حذف شده است):func (b *Block) Serialize() []byte {
	var result bytes.Buffer
	encoder := gob.NewEncoder(&amp;result)

	err := encoder.Encode(b)

	return result.Bytes()
}این قطعه کد ساده است. در ابتدا، بافری را اعلام می کنیم که داده های سریالی را ذخیره می کند. سپس یک انکدر gob را مقداردهی اولیه می کنیم و بلوک را رمزگذاری می کنیم. نتیجه به عنوان یک آرایه بایت برگردانده می شود.در مرحله بعد، ما به یک تابع deserializing جهت خارج کردن از حالت سریال سازی نیاز داریم که یک آرایه بایت را به عنوان ورودی دریافت کند و یک Block را برگرداند. این یک متد نیست بلکه یک تابع مستقل خواهد بود.func DeserializeBlock(d []byte) *Block {
	var block Block

	decoder := gob.NewDecoder(bytes.NewReader(d))
	err := decoder.Decode(&amp;block)

	return &amp;block
}و همین  برای سریال سازی کافی است!ماندگاری یا Persistenceبیایید با عملکرد NewBlockchain شروع کنیم. در حال حاضر، یک نمونه جدید از بلاکچین ایجاد می کند و بلوک پیدایش (genesis block) را به آن اضافه می کند. کاری که ما می خواهیم انجام دهد این است که:۱- یک فایل DB را باز کنید.۲- بررسی کنید که آیا بلاکچین در آن ذخیره شده است یا خیر.۳- اگر بلاک چین وجود دارد:        ۳-۱ - یک نمونه بلاکچین جدید ایجاد کنید.        ۳-۲ - نوک (tip) نمونه Blockchain را روی آخرین هش بلاک ذخیره شده در DB قرار دهید.۴- اگر بلاکچین موجود وجود نداشته باشد:         ۴-۱ - بلوک پیدایش را ایجاد کنید.         ۴-۲ - در DB ذخیره کنید.         ۴-۳ - هش بلوک پیدایش (genesis block’s) را به عنوان آخرین هش بلوک ذخیره کنید.         ۴-۴ - یک نمونه Blockchain جدید با نوک آن به سمت بلوک پیدایش ایجاد کنید.نمونه کد، به شکل زیر استfunc NewBlockchain() *Blockchain {
	var tip []byte
	db, err := bolt.Open(dbFile, 0600, nil)

	err = db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))

		if b == nil {
			genesis := NewGenesisBlock()
			b, err := tx.CreateBucket([]byte(blocksBucket))
			err = b.Put(genesis.Hash, genesis.Serialize())
			err = b.Put([]byte(&amp;quotl&amp;quot), genesis.Hash)
			tip = genesis.Hash
		} else {
			tip = b.Get([]byte(&amp;quotl&amp;quot))
		}

		return nil
	})

	bc := Blockchain{tip, db}

	return &amp;bc
}بیایید این قطعه کد را مرور کنیم.db, err := bolt.Open(dbFile, 0600, nil)این یک روش استاندارد برای باز کردن یک فایل BoltDB است. توجه داشته باشید که اگر چنین فایلی وجود نداشته باشد، خطایی را بر نمی گرداند.err = db.Update(func(tx *bolt.Tx) error {
...
})در BoltDB، عملیات با پایگاه داده در یک تراکنش اجرا می شود. و دو نوع تراکنش وجود دارد: فقط خواندنی و خواندنی-نوشتنی. در اینجا، ما یک تراکنش خواندن-نوشتن را باز می کنیم زیرا انتظار داریم بلوک پیدایش را در DB قرار دهیم.b := tx.Bucket([]byte(blocksBucket))

if b == nil {
	genesis := NewGenesisBlock()
	b, err := tx.CreateBucket([]byte(blocksBucket))
	err = b.Put(genesis.Hash, genesis.Serialize())
	err = b.Put([]byte(&amp;quotl&amp;quot), genesis.Hash)
	tip = genesis.Hash
} else {
	tip = b.Get([]byte(&amp;quotl&amp;quot))
}این هسته فانکشن است. در اینجا، سطلی (bucket) را به دست می آوریم که بلوک های ما را ذخیره می کند. اگر وجود داشته باشد، کلید l (L کوچک) را از آن می خوانیم. اگر وجود نداشته باشد، بلوک پیدایش را تولید می‌کنیم، سطل را ایجاد می‌کنیم، بلوک را در آن ذخیره می‌کنیم و کلید l (L کوچک) را به‌روزرسانی می‌کنیم که آخرین هش بلاک زنجیره را ذخیره می‌کند.همچنین به روش جدید ایجاد یک بلاکچین توجه کنید:bc := Blockchain{tip, db}ما دیگر همه بلوک ها را در آن ذخیره نمی کنیم، در عوض فقط نوک زنجیره ذخیره می شود. همچنین، ما یک اتصال DB را ذخیره می کنیم، زیرا می خواهیم یک بار آن را باز کنیم و در حین اجرای برنامه باز نگه داریم. بنابراین، ساختار بلاکچین اکنون به شکل زیر است:type Blockchain struct {
	tip []byte
	db  *bolt.DB
}مورد بعدی که می‌خواهیم به‌روزرسانی کنیم، روش AddBlock است.  اکنون اضافه کردن بلوک‌ها به یک زنجیره به آسانی افزودن یک عنصر به یک آرایه نیست. از این به بعد بلوک ها را در DB ذخیره می کنیم:func (bc *Blockchain) AddBlock(data string) {
	var lastHash []byte

	err := bc.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		lastHash = b.Get([]byte(&amp;quotl&amp;quot))

		return nil
	})

	newBlock := NewBlock(data, lastHash)

	err = bc.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		err := b.Put(newBlock.Hash, newBlock.Serialize())
		err = b.Put([]byte(&amp;quotl&amp;quot), newBlock.Hash)
		bc.tip = newBlock.Hash

		return nil
	})
}بیایید این قطعه کد را مرور کنیم.err := bc.db.View(func(tx *bolt.Tx) error {
	b := tx.Bucket([]byte(blocksBucket))
	lastHash = b.Get([]byte(&amp;quotl&amp;quot))

	return nil
})این نوع دیگر (فقط خواندنی) تراکنش های BoltDB است. در اینجا آخرین هش بلاک را از DB دریافت می کنیم تا از آن برای استخراج هش بلاک جدید استفاده کنیم.newBlock := NewBlock(data, lastHash)
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
err = b.Put([]byte(&amp;quotl&amp;quot), newBlock.Hash)
bc.tip = newBlock.Hashپس از استخراج یک بلوک جدید، نسخه سریال شده آن را در DB ذخیره می کنیم و کلید l را به روز می کنیم، که اکنون هش بلوک جدید را ذخیره می کند.انجام شد! سخت نبود، نه؟بررسی بلاک چینهمه بلوک‌های جدید اکنون در یک پایگاه داده ذخیره می‌شوند، بنابراین می‌توانیم یک بلاکچین را دوباره باز کنیم و یک بلوک جدید به آن اضافه کنیم. اما پس از اجرای این، یک ویژگی خوب را از دست دادیم. دیگر نمی‌توانیم بلوک‌های بلاکچین را چاپ کنیم زیرا دیگر بلوک‌ها را در یک آرایه ذخیره نمی‌کنیم. بیایید این نقص را برطرف کنیم!این BoltDB  اجازه می دهد تا روی همه کلیدها در یک سطل (bucket) تکرار شود، اما کلیدها به ترتیب بایتی ذخیره می شوند و ما می خواهیم بلوک ها به ترتیبی که در یک زنجیره بلوکی می گیرند چاپ شوند. همچنین، چون نمی‌خواهیم همه بلوک‌ها را در حافظه بارگذاری کنیم (DB بلاکچین ما می‌تواند بزرگ باشد!... یا بیایید وانمود کنیم که می‌تواند)، آنها را یکی یکی می‌خوانیم. برای این منظور، به یک تکرارکننده بلاکچین نیاز داریم:type BlockchainIterator struct {
	currentHash []byte
	db          *bolt.DB
}هر بار که بخواهیم روی بلوک‌های بلاکچین لوپ بزنیم، یک تکرار کننده ایجاد می‌شود و هش بلاک ازچرخه فعلی و یک اتصال DB را ذخیره می‌کند. به دلیل که بعدا خواهیم دید، یک تکرار کننده به طور منطقی به یک بلاکچین متصل می شود (این یک نمونه (instance) از بلاکچین است که یک اتصال DB را ذخیره می کند) و بنابراین این کار یک متد برای ایجاد بلاکچین است.func (bc *Blockchain) Iterator() *BlockchainIterator {
	bci := &amp;BlockchainIterator{bc.tip, bc.db}

	return bci
}توجه داشته باشید که یک تکرار کننده در ابتدا به نوک یک بلاک چین اشاره می کند، بنابراین بلوک ها از بالا به پایین، از جدیدترین به قدیمی ترین به دست می آیند. در واقع، انتخاب یک نوک به معنای «رای دادن» به یک بلاکچین است. یک بلاکچین می‌تواند چندین انشعاب داشته باشد و طولانی‌ترین آن‌ به عنوان  انشعاب اصلی در نظر گرفته می‌شود. پس از گرفتن نوک (این می تواند هر بلوکی در بلاکچین باشد) می توانیم کل بلاکچین را بازسازی کنیم و طول آن و کار مورد نیاز برای ساخت آن را پیدا کنیم. این واقعیت همچنین به این معنی است که یک نوک نوعی شناسه یک بلاکچین است.این BlockchainIterator تنها یک کار را انجام می دهد بلوک بعدی را از یک بلاکچین برمی گرداند.func (i *BlockchainIterator) Next() *Block {
	var block *Block

	err := i.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		encodedBlock := b.Get(i.currentHash)
		block = DeserializeBlock(encodedBlock)

		return nil
	})

	i.currentHash = block.PrevBlockHash

	return block
}همین. بخش DB تمام است!رابط خط فرمان یا CLIتا کنون پیاده سازی ما هیچ رابطی برای تعامل با برنامه ارائه نکرده است. ما به سادگی NewBlockchain، bc.AddBlock را در تابع main اجرا کرده ایم. زمان بهبود این امر است! ما می خواهیم این دستورات را داشته باشیم:blockchain_go addblock &quot;Pay 0.031337 for a coffee&quot;blockchain_go printchainتمام عملیات مربوط به خط فرمان توسط ساختار CLI پردازش می شود:type CLI struct {
	bc *Blockchain
}&quot;نقطه ورودی&quot; آن تابع Run است:func (cli *CLI) Run() {
	cli.validateArgs()

	addBlockCmd := flag.NewFlagSet(&amp;quotaddblock&amp;quot, flag.ExitOnError)
	printChainCmd := flag.NewFlagSet(&amp;quotprintchain&amp;quot, flag.ExitOnError)

	addBlockData := addBlockCmd.String(&amp;quotdata&amp;quot, &amp;quot&amp;quot, &amp;quotBlock data&amp;quot)

	switch os.Args[1] {
	case &amp;quotaddblock&amp;quot:
		err := addBlockCmd.Parse(os.Args[2:])
	case &amp;quotprintchain&amp;quot:
		err := printChainCmd.Parse(os.Args[2:])
	default:
		cli.printUsage()
		os.Exit(1)
	}

	if addBlockCmd.Parsed() {
		if *addBlockData == &amp;quot&amp;quot {
			addBlockCmd.Usage()
			os.Exit(1)
		}
		cli.addBlock(*addBlockData)
	}

	if printChainCmd.Parsed() {
		cli.printChain()
	}
}ما از پکیج  استاندارد flag در زبان Go برای تجزیه آرگومان های خط فرمان استفاده می کنیم.addBlockCmd := flag.NewFlagSet(&amp;quotaddblock&amp;quot, flag.ExitOnError)
printChainCmd := flag.NewFlagSet(&amp;quotprintchain&amp;quot, flag.ExitOnError)
addBlockData := addBlockCmd.String(&amp;quotdata&amp;quot, &amp;quot&amp;quot, &amp;quotBlock data&amp;quot)ابتدا دو دستور فرعی addblock و printchain ایجاد می کنیم و سپس پرچم یا فلگ   data- را به اولی اضافه می کنیم. printchain هیچ پرچمی نخواهد داشت.switch os.Args[1] {
case &amp;quotaddblock&amp;quot:
	err := addBlockCmd.Parse(os.Args[2:])
case &amp;quotprintchain&amp;quot:
	err := printChainCmd.Parse(os.Args[2:])
default:
	cli.printUsage()
	os.Exit(1)
}سپس دستور ارائه شده توسط کاربر را بررسی می کنیم و زیرفرمان (subcommand) پرچم مرتبط را تجزیه می کنیم.if addBlockCmd.Parsed() {
	if *addBlockData == &amp;quot&amp;quot {
		addBlockCmd.Usage()
		os.Exit(1)
	}
	cli.addBlock(*addBlockData)
}

if printChainCmd.Parsed() {
	cli.printChain()
}سپس بررسی می کنیم که کدام یک از دستورات فرعی تجزیه شده و توابع مرتبط را اجرا می کنیم.func (cli *CLI) addBlock(data string) {
	cli.bc.AddBlock(data)
	fmt.Println(&amp;quotSuccess!&amp;quot)
}

func (cli *CLI) printChain() {
	bci := cli.bc.Iterator()

	for {
		block := bci.Next()

		fmt.Printf(&amp;quotPrev. hash: %x\n&amp;quot, block.PrevBlockHash)
		fmt.Printf(&amp;quotData: %s\n&amp;quot, block.Data)
		fmt.Printf(&amp;quotHash: %x\n&amp;quot, block.Hash)
		pow := NewProofOfWork(block)
		fmt.Printf(&amp;quotPoW: %s\n&amp;quot, strconv.FormatBool(pow.Validate()))
		fmt.Println()

		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
}این قطعه بسیار شبیه به قطعه ای است که قبلا داشتیم. تنها تفاوت این است که ما اکنون از BlockchainIterator برای تکرار بر روی بلوک های یک بلاکچین استفاده می کنیم.همچنین فراموش نکنید که تابع main را بر این اساس تغییر دهید:func main() {
	bc := NewBlockchain()
	defer bc.db.Close()

	cli := CLI{bc}
	cli.Run()
}توجه داشته باشید که یک بلاکچین جدید بدون توجه به اینکه چه آرگومان های خط فرمان ارائه می شود ایجاد می شود.و تمام! بیایید بررسی کنیم که همه چیز همانطور که انتظار می رود کار می کند:$ blockchain_go printchainNo existing blockchain found. Creating a new one...Mining the block containing &quot;Genesis Block&quot;000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109bPrev. hash:Data: Genesis BlockHash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109bPoW: true$ blockchain_go addblock -data &quot;Send 1 BTC to Ivan&quot;Mining the block containing &quot;Send 1 BTC to Ivan&quot;000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13Success!$ blockchain_go addblock -data &quot;Pay 0.31337 BTC for a coffee&quot;Mining the block containing &quot;Pay 0.31337 BTC for a coffee&quot;000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148Success!$ blockchain_go printchainPrev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13Data: Pay 0.31337 BTC for a coffeeHash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148PoW: truePrev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109bData: Send 1 BTC to IvanHash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13PoW: truePrev. hash:Data: Genesis BlockHash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109bPoW: true(صدای باز شدن قوطی آبجو &quot;بدول الکل&quot;)نتیجه گیریدفعه بعد آدرس ها، کیف پول ها و (احتمالا) تراکنش ها را اجرا خواهیم کرد. پس با ما همراه باشید!پایان قسمت سوم.قسمت چهارملینک منبعباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Tue, 14 Jun 2022 20:48:47 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت دوم (الگوریتم اثبات کار یا Proof-of-Work)</title>
                <link>https://virgool.io/@hootan09/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D8%AF%D9%88%D9%85-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%DA%A9%D8%A7%D8%B1-%DB%8C%D8%A7-proof-of-work-ywmu6m5av5bg</link>
                <description>مقدمه در مقاله قبلی ما یک ساختار داده بسیار ساده ساختیم که ماهیت پایگاه داده بلاکچین است و ما امکان افزودن بلوک‌ها را به آن با رابطه زنجیره‌ای بین آنها فراهم کردیم. هر بلوک به بلوک قبلی مرتبط است. افسوس که پیاده‌سازی بلاکچین ما یک نقص مهم دارد و آن این است که افزودن بلاک‌ها به زنجیره آسان و ارزان است. یکی از اصل های بلاکچین و بیت‌کوین این است که افزودن بلاک‌های جدید کار سختی باشد. امروز قصد داریم این نقص را برطرف کنیم.الگوریتم اثبات کار یا Proof-of-Workیکی از ایده های کلیدی بلاکچین این است که برای قرار دادن داده ها در آن باید کار سختی انجام داد. همین کار سخت است که بلاکچین را ایمن و سازگار می کند. همچنین برای این کار سخت پاداش نیز پرداخت می شود (اینگونه است که مردم برای استخراج ، سکه دریافت می کنند).این مکانیسم بسیار شبیه سازوکار زندگی واقعی است. فرد باید سخت کار کند تا پاداش بگیرد و زندگی خود را حفظ کند. در بلاکچین، برخی از شرکت‌کنندگان (ماینرها) شبکه برای حفظ شبکه، اضافه کردن بلوک‌های جدید به آن و دریافت پاداش برای کار خود تلاش می‌کنند. در نتیجه کار آنها، یک بلوک به روشی ایمن در بلاکچین گنجانده می شود که ثبات کل پایگاه داده بلاکچین را حفظ می کند. شایان ذکر است که کسی که کار را تمام کرده است باید این را ثابت کند.کل این مکانیسم &quot;کار سخت و اثبات&quot; را اثبات کار یا proof-of-work می نامند. سخت است زیرا به قدرت محاسباتی زیادی نیاز دارد. حتی رایانه هایی با کارایی بالا نیز نمی توانند آن را به سرعت انجام دهند. علاوه بر این، سختی این کار هر از گاهی افزایش می یابد تا نرخ بلوک های جدید در حدود 6 بلوک در ساعت حفظ شود. در بیت کوین، هدف چنین کاری یافتن هش برای یک بلوک است که برخی از الزامات را برآورده می کند. و این هش است که به عنوان یک مدرک عمل می کند. بنابراین، همین یافتن دلیل و اثبات کار، یک کار واقعی است.در آخرین نکته قابل توجه الگوریتم‌های اثبات کار ، باید یک الزام را برآورده کنند و آن اینکه  انجام کار سخت است، اما تأیید اثبات آسان است. یک مدرک معمولاً به شخص دیگری تحویل داده می شود، بنابراین برای آنها، تأیید آن نباید زمان زیادی را صرف کند.هش کردن یا Hashingدر این پاراگراف، هش کردن را مورد بحث قرار خواهیم داد. اگر با مفهوم آن آشنا هستید، می توانید از این قسمت صرف نظر کنید.هش کردن فرآیند به دست آوردن هش برای داده های مشخص است. هش یک نمایش منحصر به فرد از داده هایی است که روی آنها محاسباتی شده است. تابع هش تابعی است که داده هایی با اندازه دلخواه را می گیرد و یک هش با اندازه ثابت تولید می کند. در اینجا برخی از ویژگی های کلیدی هش وجود دارد:داده های اصلی را نمی توان از هش بازیابی کرد. بنابراین، هش رمزگذاری نیست.داده ها می توانند فقط یک هش داشته باشند و هش منحصر به فرد است. تغییر حتی یک بایت در داده های ورودی منجر به هش کاملاً متفاوتی می شود.توابع هش به طور گسترده ای برای بررسی سازگاری داده ها استفاده می شود. برخی از ارائه دهندگان نرم افزار علاوه بر بسته نرم افزاری، چک سام ها (checksum) را نیز منتشر می کنند. پس از دانلود یک فایل، می توانید آن را به یک تابع درهم سازی وارد کنید و هش تولید شده را با هش ارائه شده توسط توسعه دهنده نرم افزار مقایسه کنید.در بلاکچین، هش برای تضمین ثبات یک بلاک استفاده می شود. داده های ورودی برای یک الگوریتم هش شامل هش بلوک قبلی است، بنابراین تغییر یک بلوک در زنجیره را غیرممکن (یا حداقل بسیار دشوار) می کند و فرد باید هش آن و هش همه بلوک های بعد از آن را دوباره محاسبه کند.هش کش یا Hashcashبیت کوین از Hashcash استفاده می کند، یک الگوریتم اثبات کار (Proof-of-Work) که در ابتدا برای جلوگیری از هرزنامه ایمیل ایجاد شد. می توان آن را به مراحل زیر تقسیم کرد:۱- برخی از داده‌ های شناخته شده عمومی به عنوان مثال ( در مورد ایمیل، آدرس ایمیل گیرنده است، در مورد بیت‌کوین، هدرهای بلوکی است) را می گیرد.۲- یک شمارنده به آن اضافه می کند. شمارنده از 0 شروع می شود.۳-  یک هش از ترکیب  داده + شمارنده  دریافت می کنید. ۴- بررسی میکند  که هش الزامات خاصی را برآورده کند.         ۴-۱ - اگر این کار را کرد، کار شما تمام شده است.         ۴-۲ - اگر نشد، شمارنده را افزایش دهید و مراحل 3 و 4 را تکرار کنید.بنابراین، این یک الگوریتم brute force است. شما شمارنده را تغییر می‌دهید، یک هش جدید را محاسبه می‌کنید، آن را بررسی می‌کنید، شمارنده را افزایش می‌دهید، یک هش را محاسبه می‌کنید، و الی آخر. به همین دلیل است که از نظر محاسباتی گران است.اکنون بیایید به الزاماتی که یک هش باید برآورده کند، دقیق تر نگاه کنیم. در اجرای اولیه Hashcash، این الزام به نظر می رسد &quot;20 بیت اول هش باید صفر باشد&quot;. در بیت کوین، این نیاز هر از چند گاهی تنظیم می شود، زیرا، با وجود طراحی، هر 10 دقیقه یک بلاک باید تولید شود، علیرغم اینکه قدرت محاسباتی با زمان افزایش می یابد و ماینرهای بیشتری به شبکه می پیوندند.برای نشان دادن این الگوریتم، داده‌های مثال قبلی (من دونات را دوست دارم &quot;I like donuts&quot;) گرفتم و یک هش پیدا کردم که با 3 بایت صفر شروع می‌شود (هر بایت دو کلمه است):()این ca07ca مقدار هگزادسیمال شمارنده است که در سیستم اعشاری معادل 13240266 است.پیاده سازیخب، ما تئوری قضیه را تمام کردیم، بیایید کد بنویسیم! ابتدا، اجازه دهید دشواری استخراج را تعریف کنیم:const targetBits = 24در بیت‌کوین، «بیت‌های هدف target bits » هدر بلوک است که دشواری استخراج بلوک را ذخیره می‌کند. ما در حال حاضر یک الگوریتم تنظیم هدف را پیاده سازی نمی کنیم، بنابراین می توانیم دشواری را به عنوان یک ثابت جهانی تعریف کنیم.مقدار 24 یک عدد دلخواه است، هدف ما این است  هدف یا  target  ای داشته باشیم که کمتر از 256 بیت در حافظه مصرف کند. و ما می خواهیم تفاوت به اندازه کافی قابل توجه باشد، اما نه خیلی بزرگ، زیرا هر چه تفاوت بزرگتر باشد، یافتن هش مناسب دشوارتر است.type ProofOfWork struct {
	block  *Block
	target *big.Int
}

func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))

	pow := &amp;ProofOfWork{b, target}

	return pow
}در اینجا ساختار ProofOfWork ایجاد می کنیم که یک اشاره گر به یک block و یک اشاره گر به یک target را نگه می دارد. &quot;target&quot; نام دیگری برای نیاز توضیح داده شده در پاراگراف قبلی است. ما از یک عدد صحیح بزرگ به دلیل نحوه مقایسه هش با target استفاده می کنیم: یک هش را به یک عدد صحیح بزرگ تبدیل می کنیم و بررسی می کنیم که آیا کمتر از target است یا خیر.در تابع NewProofOfWork، یک big.Int را با مقدار 1 مقداردهی اولیه می کنیم و آن را با 256 - targetBitsبه سمت چپ شیفت می دهیم. 256 طول هش SHA-256 بر حسب بیت است و الگوریتم هش SHA-256 است که ما از آن استفاده خواهیم کرد. نمایش هگزادسیمال target به صورت زیر است:0x10000000000000000000000000000000000000000000000000000000000و 29 بایت در حافظه اشغال می کند. و در اینجا مقایسه بصری آن با هش های نمونه های قبلی است:0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e300000100000000000000000000000000000000000000000000000000000000000000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76caهش اول (محاسبه شده بر روی &quot;من دونات را دوست دارم&quot;) بزرگتر از هدف است، بنابراین اثبات معتبری برای کار نیست. هش دوم (محاسبه شده روی &quot;من donutsca07ca را دوست دارم&quot;) کوچکتر از هدف است، بنابراین یک اثبات معتبر است.شما می توانید یک target را به عنوان مرز بالایی یک محدوده در نظر بگیرید: اگر یک عدد (هش) کمتر از مرز باشد، معتبر است و بالعکس. کاهش مرز منجر به اعداد معتبر کمتری خواهد شد و بنابراین، کار دشوارتری برای یافتن یک عدد معتبر مورد نیاز است.اکنون، ما به داده ها برای هش نیاز داریم. بیایید آن را آماده کنیم:func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.Data,
			IntToHex(pow.block.Timestamp),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)

	return data
}این قطعه ساده است. ما فقط فیلدهای block را با target و nonce ادغام می کنیم. این nonceدر اینجا در نقش شمارنده از توضیحات Hashcash بالا است، این یک اصطلاح رمزنگاری است.بسیار خوب، تمام آماده سازی ها انجام شده است، اجازه دهید هسته الگوریتم PoW را پیاده سازی کنیم:func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce := 0

	fmt.Printf(&amp;quotMining the block containing \&amp;quot%s\&amp;quot\n&amp;quot, pow.block.Data)
	for nonce &lt; maxNonce {
		data := pow.prepareData(nonce)
		hash = sha256.Sum256(data)
		fmt.Printf(&amp;quot\r%x&amp;quot, hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Print(&amp;quot\n\n&amp;quot)

	return nonce, hash[:]
}ابتدا متغیرها را مقداردهی اولیه می کنیم. hashInt نمایش عدد صحیح هش است. nonce شمارنده است. در مرحله بعد، یک حلقه &quot;بی نهایت&quot; را اجرا می کنیم: این حلقه توسط maxNonce محدود شده است که برابر با math.MaxInt64 است. این کار برای جلوگیری از سرریز احتمالی nonce انجام می شود. اگرچه دشواری اجرای PoW ما برای سرریز شدن شمارنده بسیار کم است، اما بهتر است این بررسی را انجام دهید، فقط در صورت امکان.در حلقه ما: ۱-    داده ها را آماده کنید.۲-   آن را با SHA-256 هش کنید.۳-     هش را به یک عدد صحیح بزرگ تبدیل کنید.۴-     عدد صحیح را با هدف مقایسه کنید.به همین راحتی که قبلا توضیح داده شد. اکنون می‌توانیم متد SetHash  را از Block حذف کرده و تابع NewBlock را تغییر دهیم:func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &amp;Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
	pow := NewProofOfWork(block)
	nonce, hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce

	return block
}در اینجا می توانید ببینید که nonce به عنوان یک ویژگی Block ذخیره می شود. این امر ضروری است، زیرا برای تأیید یک مدرک نیاز به nonce است. ساختار Block اکنون به نظر می شود:type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}بسیار خوب! بیایید برنامه را اجرا کنیم تا ببینیم آیا همه چیز خوب کار می کند یا خیر:Mining the block containing &quot;Genesis Block&quot;00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Mining the block containing &quot;Send 1 BTC to Ivan&quot;00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Mining the block containing &quot;Send 2 more BTC to Ivan&quot;000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbePrev. hash:Data: Genesis BlockHash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1Data: Send 1 BTC to IvanHash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804Data: Send 2 more BTC to IvanHash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbeآری می بینید که هر هش اکنون با سه بایت صفر شروع می شود و مدتی طول می کشد تا این هش ها را دریافت کنید.یک کار دیگر برای انجام باقی مانده است: اجازه دهید تأیید اعتبار (Proof of Work) آثار را ممکن کنیم.func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int

	data := pow.prepareData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])

	isValid := hashInt.Cmp(pow.target) == -1

	return isValid
}و اینجاست که ما به nonce ذخیره شده نیاز داریم.بیایید یک بار دیگر بررسی کنیم که همه چیز خوب است:func main() {
	...

	for _, block := range bc.blocks {
		...
		pow := NewProofOfWork(block)
		fmt.Printf(&amp;quotPoW: %s\n&amp;quot, strconv.FormatBool(pow.Validate()))
		fmt.Println()
	}
}خروجی:...Prev. hash:Data: Genesis BlockHash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038PoW: truePrev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038Data: Send 1 BTC to IvanHash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062bPoW: truePrev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062bData: Send 2 more BTC to IvanHash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57aPoW: trueنتیجه گیریبلاکچین ما یک قدم به معماری واقعی خود نزدیک‌تر است. افزودن بلاک‌ها اکنون به کار سخت نیاز دارد، بنابراین استخراج امکان‌پذیر است. اما هنوز برخی از ویژگی های حیاتی را ندارد. پایگاه داده بلاکچین پایدار نیست، کیف پول، آدرس، تراکنش وجود ندارد و مکانیسم توافقی (consensus) وجود ندارد. همه این موارد را در مقالات آینده پیاده سازی خواهیم کرد، و در حال حاضر، ماینینگ تان مبارک!پایان قسمت دوم.قسمت سوملینک منبعباقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Tue, 14 Jun 2022 16:28:09 +0430</pubDate>
            </item>
                    <item>
                <title>ساخت بلاکچین با نگاهی به ساختار بین کوین - قسمت اول (نمونه اولیه)</title>
                <link>https://virgool.io/Solidity/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A8%D9%84%D8%A7%DA%A9%DA%86%DB%8C%D9%86-%D8%A8%D8%A7-%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1-%D8%A8%DB%8C%D9%86-%DA%A9%D9%88%DB%8C%D9%86-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-%D9%86%D9%85%D9%88%D9%86%D9%87-%D8%A7%D9%88%D9%84%DB%8C%D9%87-ht6jd1xrcfoj</link>
                <description>سخن مترجم:درسال های اخیر این بهترین مقاله ای هست که در رابطه با بلاکچین خواندم.نسخه اصلی آن در سال ۲۰۱۷ در قالب ۷ قسمت توسط آقای Ivan Kuznetsov نوشته شده است.برگردان این مجموعه به زبان های مختلفی از جمله چینی نیز ترجمه شده است.نکته: تمامی نمونه کد ها با زبان Go (Golang) پیاده سازی شده است.مقدمهبلاکچین یکی از انقلابی‌ترین فناوری‌های قرن بیست و یکم است که هنوز در حال بلوغ است و پتانسیل آن هنوز به‌طور کامل شناخته نشده است. در اصل، بلاکچین فقط یک پایگاه داده توزیع شده از سوابق است. اما چیزی که آن را منحصر به فرد می کند این است که یک پایگاه داده خصوصی نیست، بلکه عمومی است، یعنی هرکسی که از آن استفاده می کند یک نسخه کامل یا جزئی از آن را دارد. و یک رکورد جدید فقط با رضایت سایر نگهبانان پایگاه داده می تواند اضافه شود. همچنین، این بلاکچین است که ارزهای دیجیتال و قراردادهای هوشمند را امکان پذیر کرد.در این سری از مقالات، یک ارز دیجیتال ساده‌سازی شده را می‌سازیم که مبتنی بر پیاده‌سازی ساده بلاکچین است.بلوک (Block):بیایید با بخش «بلاک» «بلاکچین» شروع کنیم. در بلاکچین، بلوک هایی هستند که اطلاعات ارزشمند را ذخیره می کنند. به عنوان مثال، بلوک های بلاکچین در بیت کوین از تراکنش های این رمز ارز نگهداری میکند. علاوه بر این، یک بلوک حاوی برخی اطلاعات فنی مانند نسخه آن، زمان (timestamp) و هش بلوک قبلی است. در این مقاله قصد نداریم بلاک را همانطور که در مشخصات بلاکچین یا بیت کوین توضیح داده شده پیاده سازی کنیم، در عوض از یک نسخه ساده شده آن استفاده خواهیم کرد که فقط حاوی اطلاعات قابل توجهی است. کد زیر نمونه چیزی است که از یک بلوک میخواهیم توسعه دهیم.type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
}این Timestamp زمان فعلی است (زمانی که بلوک ایجاد می‌شود)، Data اطلاعات ارزشمند واقعی موجود در بلوک است، PrevBlockHash هش بلوک قبلی را ذخیره می‌کند و Hash هش بلوک فعلی است. در مشخصات بیت کوین Timestamp، PrevBlockHash و Hash هدرهای بلوکی هستند که یک ساختار داده جداگانه را تشکیل می دهند و تراکنش ها (در مورد ما داده ها یا Data) یک ساختار داده جداگانه است. بنابراین ما آنها را در اینجا برای سادگی با هم مخلوط می کنیم.خُب چگونه هش ها را محاسبه کنیم؟ نحوه محاسبه هش ها ویژگی بسیار مهم بلاکچین است و این ویژگی است که بلاکچین را ایمن می کند. مسئله این است که محاسبه هش از نظر محاسباتی یک عملیات دشوار است، حتی در رایانه‌های سریع هم زمان می‌برد (به همین دلیل است که مردم برای استخراج بیت‌کوین پردازنده‌های گرافیکی قدرتمندی می‌خرند). این یک طراحی معماری عمدی است که افزودن بلوک‌های جدید را دشوار می‌کند، بنابراین از تغییر آن‌ها پس از اضافه شدن جلوگیری می‌کند. ما در مقاله آینده درباره پیاده سازی این مکانیسم بحث خواهیم کرد.در حال حاضر، ما فقط فیلدهای بلوک را می گیریم، آنها را به هم متصل می کنیم و یک هش SHA-256 را روی این داده های متصل شده محاسبه می کنیم. بیایید این کار را در متد SetHash انجام دهیم:func (b *Block) SetHash() {
	timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
	headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
	hash := sha256.Sum256(headers)

	b.Hash = hash[:]
}در مرحله بعد، از طریق کد زیر یک تابعی را اجرا می کنیم که ایجاد یک بلوک را ساده می کند:func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &amp;Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
	block.SetHash()
	return block
}و برای ساخت بلوک همین کافی است.بلاکچین (Blockchain) :حالا بیایید یک بلاکچین را پیاده سازی کنیم. در اصل بلاکچین فقط یک پایگاه داده با ساختار مشخص است: این یک لیست ترتیب گذاری شده  و لینک شده با عناصر قبلی موجود در خود  است. این بدان معناست که بلوک ها به ترتیب درج و ذخیره می شوند و هر بلوک به بلوک قبلی پیوند داده می شود. این ساختار امکان دریافت سریع آخرین بلوک در یک زنجیره و دریافت (به طور کارآمد) بلوک توسط هش آن را فراهم می کند.در Golang می‌توان این ساختار را با استفاده از یک آرایه و یا یک map پیاده‌سازی کرد: آرایه هش‌های مرتب را نگه می‌دارد (آرایه‌ها در Go مرتب می‌شوند)، و نقشه جفت‌های hash → block را نگه می‌دارد (map ها در گولنگ نامرتب هستند). اما برای نمونه اولیه بلاکچین ما فقط از یک آرایه استفاده می کنیم، زیرا در حال حاضر نیازی به دریافت بلوک ها توسط هش آنها نداریم.type Blockchain struct {
	blocks []*Block
}این اولین بلاکچین ماست! هیچوقت فکر نمیکردم به این راحتی باشه?حالا بیایید امکان اضافه کردن بلوک به آن را فراهم کنیم:func (bc *Blockchain) AddBlock(data string) {
	prevBlock := bc.blocks[len(bc.blocks)-1]
	newBlock := NewBlock(data, prevBlock.Hash)
	bc.blocks = append(bc.blocks, newBlock)
}ساده بود یا نه؟..برای افزودن یک بلوک جدید نیاز به یک بلوک داریم که از اول موجود باشد، اما بلوکی در زنجیره بلوکی ما وجود ندارد! بنابراین، در هر بلاکچین، حداقل باید یک بلوک وجود داشته باشد، و چنین بلوکی، اولین بلوک در زنجیره، بلوک پیدایش یا genesis block نامیده می شود. بیایید روشی را پیاده سازی کنیم که چنین بلوکی ایجاد می کند:func NewGenesisBlock() *Block {
	return NewBlock(&amp;quotGenesis Block&amp;quot, []byte{})
}اکنون، می‌توانیم تابعی را پیاده‌سازی کنیم که با بلاک پیدایش، یک زنجیره بلوکی ایجاد می‌کند:func NewBlockchain() *Blockchain {
	return &amp;Blockchain{[]*Block{NewGenesisBlock()}}
}بیایید بررسی کنیم که بلاکچین به درستی کار می کند:func main() {
	bc := NewBlockchain()

	bc.AddBlock(&amp;quotSend 1 BTC to Ivan&amp;quot)
	bc.AddBlock(&amp;quotSend 2 more BTC to Ivan&amp;quot)

	for _, block := range bc.blocks {
		fmt.Printf(&amp;quotPrev. hash: %x\n&amp;quot, block.PrevBlockHash)
		fmt.Printf(&amp;quotData: %s\n&amp;quot, block.Data)
		fmt.Printf(&amp;quotHash: %x\n&amp;quot, block.Hash)
		fmt.Println()
	}
}خروجی:Prev. hash:Data: Genesis BlockHash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168Prev. hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168Data: Send 1 BTC to IvanHash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1Prev. hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1Data: Send 2 more BTC to IvanHash: 561237522bb7fcfbccbc6fe0e98bbbde7427ffe01c6fb223f7562288ca2295d1و تمام.نتیجه گیری:ما یک نمونه اولیه بلاکچین بسیار ساده ساختیم. این فقط مجموعه‌ای از بلوک‌ها است که هر بلوک به بلوک قبلی متصل است. با این حال، بلاکچین واقعی بسیار پیچیده تر است. در بلاکچین ما، افزودن بلوک‌های جدید آسان و سریع است، اما در بلاکچین واقعی، افزودن بلوک‌های جدید به مقداری کار نیاز دارد: قبل از دریافت مجوز برای افزودن بلوک، باید محاسبات سنگینی انجام داد (این مکانیسم اثبات کار یا Proof-of-Work نامیده می‌شود). همچنین، بلاکچین یک پایگاه داده توزیع شده است که هیچ تصمیم گیرنده واحدی ندارد. بنابراین، یک بلوک جدید باید توسط سایر شرکت کنندگان شبکه قبول و تأیید شود (این مکانیسم اجماع یا consensus نامیده می شود). و هنوز هیچ تراکنشی در بلاکچین ما انجام نشده است!در مقالات آینده به هر یک از این ویژگی ها خواهیم پرداخت.پایان قسمت اولقسمت دوملینک منبع باقی قسمت ها نسخه اصلیBuilding Blockchain in Go. Part 1: Basic PrototypeBuilding Blockchain in Go. Part 2: Proof-of-WorkBuilding Blockchain in Go. Part 3: Persistence and CLIBuilding Blockchain in Go. Part 4: Transactions 1Building Blockchain in Go. Part 5: AddressesBuilding Blockchain in Go. Part 6: Transactions 2Building Blockchain in Go. Part 7: Network</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Mon, 13 Jun 2022 17:48:22 +0430</pubDate>
            </item>
                    <item>
                <title>چگونه می توان یک شبیه ساز (ایمولاتور) برای مفسر CHIP-8 نوشت</title>
                <link>https://virgool.io/Solidity/%DA%86%DA%AF%D9%88%D9%86%D9%87-%D9%85%DB%8C-%D8%AA%D9%88%D8%A7%D9%86-%DB%8C%DA%A9-%D8%B4%D8%A8%DB%8C%D9%87-%D8%B3%D8%A7%D8%B2-%D8%A7%DB%8C%D9%85%D9%88%D9%84%D8%A7%D8%AA%D9%88%D8%B1-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%85%D9%81%D8%B3%D8%B1-chip-8-%D9%86%D9%88%D8%B4%D8%AA-am2fuahtpklo</link>
                <description>این راهنما قصد دارد به معرفی مختصری از دنیای شبیه‌سازی بپردازد و همچنین به شما یاد می‌دهد که چگونه خودتان یکی را از ابتدا بنویسید.پیشنهاد می کنم اگه انگلیسی تون خوب هست اصل مقاله رو از اینجا بخوانید.من شخصاً از اواخر دهه 90 در مورد شبیه سازها هیجان زده بودم.از آنجایی که آن روزها کنسولی نداشتم (فقط یک C64 داشتم)، وقتی فهمیدم می‌توانید از یک شبیه‌ساز برای اجرای بازی‌های کنسول روی رایانه شخصی استفاده کنید، شگفت‌زده شدم. هنوز به یاد دارم که بازی Super Mario 3 را روی رایانه شخصی با استفاده از شبیه ساز Snes9x  برای کنسول  SNES/Super Famicom انجام دادم و چند سال بعد با استفاده از Bleem،  بازی Metal Gear Solid را تکمیل کردم! (شبیه ساز PSX).اما این روزها بیشتر بر روی ارائه پشتیبانی از پروژه های شبیه ساز کنسول های اخیر مانند: شبیه ساز PCSX2 (Sony PlayStation 2) و Dolphin-emu (Nintendo GameCube and Wii) و nullDC (Sega Dreamcast) کار میکنم.در حالی که این راهنما از شما انتظار دارد که دانش پایه ای در مورد سیستم های کامپیوتری داشته باشید و فرض می کند که یک زبان برنامه نویسی را می دانید، برای افرادی که به طور کلی به شبیه سازی علاقه مند هستند نیز باید خواندنی جالب باشد.تعریف ایمولاتور (شبیه ساز) و تفاوت آن با سیمولاتورمن فکر می کنم مهم است که ابتدا بفهمیم شبیه ساز چیست و چه چیزی نیست.شبیه ساز یک برنامه کامپیوتری است که طراحی داخلی و عملکرد یک سیستم کامپیوتری (سیستم A) را تقلید می کند. این به کاربران اجازه می دهد تا نرم افزار طراحی شده برای این سیستم خاص (سیستم A) را بر روی یک سیستم کامپیوتری یا معماری کاملاً متفاوت (سیستم B) اجرا کنند.اغلب مردم یک ایمولاتور را با یک سیمولاتور اشتباه می گیرند و بالعکس. فقط به یاد داشته باشید که این کلمات مترادف نیستند.(نکته: معمولا در زبان فارسی هر دو کلمه ایمولاتور و سیمولاتور  ، &quot;شبیه ساز&quot; ترجمه می شوند )بیایید به مثال زیر نگاهی بیندازیم:بازی Pong یک بازی تنیس دو بعدی است که توسط Atari ساخته شده و بر روی سخت افزار خود اجرا می شود. با این حال، این بازی نه تنها در سیستم‌های آتاری، بلکه بر روی پلتفرم‌های رقیب مانند Amstrad، Amiga و C64 نیز در دسترس بود.از آنجایی که آتاری مجوز اجرای هر بازی پُنگ را بر روی این پلتفرم ها نداشت، به این معنی بود که همه بازی های مشابه ، کدهای آتاری را اجرا نمی کردند. اساساً اتفاقی که افتاد این است که مردم پیاده سازی (کلون) خود را از بازی Pong ایجاد کردند. در این مورد آنها ظاهر و رفتار بازی Pong را simulate شبیه سازی کردند.در صورت وجود ایمولاتور ، ما تصمیم می گیریم که بازی Pong را برای سیستم بومی خود مجدداً بازنویسی نکنیم. در عوض، ما محیط را با یک برنامه کامپیوتری دوباره ایجاد می کنیم که به ما امکان می دهد کد اصلی ماشین Pong را اجرا کنیم. یکی از مزایای این کار این است که نه تنها به ما اجازه می دهد تا Pong را اجرا کنیم، بلکه به هر برنامه دیگری که برای آن پلتفرم توسعه یافته است نیز اجازه اجرای آن را می دهد.این CHIP-8 چیست؟درحقیقت Chip-8 یک زبان برنامه نویسی ساده، تفسیر شده است که برای اولین بار در اواخر دهه 1970 و اوایل دهه 1980 بر روی برخی از سیستم های کامپیوتری ، بخوانید &quot;خودتان انجام دهید (do-it-yourself)&quot; طراحی شد. کامپیوترهای COSMAC VIP، DREAM 6800 و ETI 660 چند نمونه هستند. این رایانه ها معمولاً برای استفاده از تلویزیون به عنوان نمایشگر طراحی شده بودند، دارای حافظه رم (RAM) بین 1 تا 4K (کیلوبایت)بودند و از صفحه کلید هگزادسیمال با 16 کلید برای ورودی استفاده می کردند. مفسر فقط 512 بایت حافظه اشغال می کرد و برنامه هایی که به صورت هگزادسیمال وارد کامپیوتر می شدند، حتی کوچکتر بودند.در حقیقت CHIP-8 هرگز یک سیستم واقعی نبود، بلکه بیشتر شبیه یک ماشین مجازی (VM) بود که در دهه 70 توسط جوزف ویزبکر(Joseph Weisbecker) توسعه یافت. بازی هایی که به زبان CHIP-8 نوشته شده اند، می توانند به راحتی روی سیستم هایی اجرا شوند که دارای مفسر CHIP-8 هستند.در اوایل دهه 1990، زبان Chip-8 توسط فردی به نام آندریاس گوستافسون احیا شد. او یک مترجم Chip-8 برای ماشین حساب نموداری HP48 به نام Chip-48 ایجاد کرد. HP48 در آن زمان راهی برای ساخت آسان بازی‌های سریع نداشت و Chip-8 پاسخ آن بود. Chip-48 بعداً Super Chip-48 را ایجاد کرد، اصلاحی در Chip-48 که امکان گرافیک با وضوح بالاتر و همچنین سایر پیشرفت های گرافیکی را فراهم می کرد ، Chip-48 الهام بخش یک محصول کاملاً جدید از مترجمان Chip-8 برای پلتفرم های مختلف، از جمله MS-DOS، Windows 3.1، Amiga، HP48، MSX، ​​Adam و ColecoVision است.چرا باید با یک ایمولاتور CHIP-8 شروع کنیم؟نوشتن شبیه ساز CHIP-8 احتمالاً ساده ترین پروژه شبیه سازی(ایمولاتور) است که می توانید انجام دهید. با توجه به تعداد کم کدهای عملیاتی (opcodes) (در مجموع 35 کد برای CHIP-8) و این واقعیت که دستورالعمل های زیادی در CPU های پیشرفته تر استفاده می شود، پروژه ای مانند این آموزشی است (درکی بهتر از نحوه کار CPU و نحوه اجرای کد ماشین ). همچنین قابل مدیریت (تعداد کم کدهای عملیاتی برای پیاده سازی) و زمان بر نبودن پیاده سازی آن (پروژه در چند روز به پایان می رسد) بهترین گزینه برای مطالعه آن است.نکاتی برای قبل از شروع ...یک زبان برنامه نویسی را انتخاب کنید که با آن آشنا هستید (C/C++ یا Java رایج هستند)مثال‌های زیر از C/C++ استفاده می‌کننداز این پروژه به عنوان راهی برای یادگیری برنامه نویسی استفاده نکنید. (اگر عملیات بیتی شما را گیج می کند، ابتدا آنها را مطالعه کنید)احتمالاً برای مدیریت خروجی صدا/تصویر و ورودی کاربر (GLUT / SDL / DirectX) نیاز به استفاده از کتابخانه های شخص ثالث دارید.حالا بریم سراغش!مشخصات CPUهنگامی که شروع به نوشتن یک شبیه ساز می کنید، مهم است که اطلاعات بیشتری در مورد سیستمی که می خواهید شبیه سازی کنید پیدا کنید. سعی کنید دریابید که چه مقدار حافظه و رجیسترها در سیستم استفاده می شود، از چه معماری استفاده می کند و ببینید آیا می توانید اسناد فنی را که مجموعه دستورالعمل را توصیف می کند، در دست داشته باشید.در مورد CHIP-8 ، توصیه می کنم به توضیحات CHIP-8 در ویکی پدیا نگاهی بیندازید.من یک نمای کلی از سیستم CHIP-8 و نکاتی در مورد نحوه اجرای قطعات ضروری به شما ارائه خواهم کرد:این CHIP-8 دارای 35 opcodes است که همگی دو بایتی هستند. برای ذخیره opcode فعلی، به یک نوع داده نیاز داریم که به ما امکان ذخیره دو بایت را بدهد. یک short بدون علامت (unsigned short) دو بایت طول دارد و بنابراین با نیازهای ما مطابقت دارد:چیپ 8 در مجموع دارای حافظه 4K (چهار کیلوبایت) است که می توانیم آن را به صورت زیر شبیه سازی کنیم:رجیسترهای CPU: چیپ 8 دارای 15 رجیستر 8 بیتی همه منظوره با نام های V1، V0 تا VE است. رجیستر شانزدهم به عنوان &quot;پرچم یا flag برای رقم نَقلی&quot; (carry flag) استفاده می شود. هر هشت بیت یک بایت است بنابراین می توانیم از یک char بدون علامت برای این منظور استفاده کنیم:یک رجیستر اندیس گذاری با نام (I) و یک شمارنده برنامه (pc) وجود دارد که می تواند مقداری از 0x000 تا 0xFFF داشته باشد.نقشه حافظه سیستم:سیستم گرافیکی: تراشه 8 دارای یک کد عملیاتی است که اسپرایت(sprite) را در صفحه نمایش می کِشد. این ترسیم در حالت XOR انجام می شود و اگر یک پیکسل در نتیجه ترسیم خاموش شود، رجیستر VF تنظیم می شود. این برای تشخیص برخورد استفاده می شود.گرافیک چیپ 8 سیاه و سفید است و صفحه نمایش آن در مجموع 2048 پیکسل (64 در 32) دارد. این را می توان به راحتی با استفاده از آرایه ای که حالت پیکسل (1 یا 0) را نگه می دارد پیاده سازی کرد:وقفه ها و ثبات سخت افزار. چیپ 8 هیچ کدام را ندارد، اما دو تایمر ثبت کننده وجود دارد که روی 60 هرتز شمارش می کنند. وقتی با مقداری بالای صفر تنظیم شوند انها شمارش معکوس تا مقدار صفر خواهند کرد.هر زمان که تایمر صدا به صفر برسد، زنگ سیستم به صدا در می آید.مهم است که بدانید مجموعه دستورالعمل تراشه 8 دارای کدهای عملیاتی است که به برنامه اجازه می دهد به یک آدرس خاص بپرد یا یک برنامه فرعی فراخوانی کند. در حالی که در مشخصات CHIP-8 ، یک پشته ذکر نشده است، شما باید خودتان یکی را به عنوان بخشی از مفسر پیاده سازی کنید. پشته برای به خاطر سپردن مکان فعلی قبل از انجام پرش استفاده می شود. بنابراین هر زمان که یک پرش انجام دادید یا یک برنامه فرعی فراخوانی کردید، پیش از ادامه، شمارنده برنامه را در پشته ذخیره کنید. این سیستم دارای 16 سطح پشته است و برای اینکه به خاطر بسپارید از کدام سطح پشته استفاده می شود، باید یک نشانگر پشته (sp) یا Stack Pointer را پیاده سازی کنید.در نهایت، تراشه 8 دارای یک صفحه کلید مبتنی بر HEX (0x0-0xF) از 0 تا F است، می توانید از یک آرایه برای ذخیره وضعیت فعلی کلید استفاده کنید.چرخه اجرای بازی یا Game Loopبرای اینکه به شما ایده بدهم که چگونه شبیه ساز خود را طراحی کنید، یک نمونه کوچک از یک طرح را ایجاد کردم. این به شما یاد نمی دهد که چگونه از GLUT یا SDL برای مدیریت گرافیک و ورودی استفاده کنید، بلکه فقط به شما نشان می دهد که جریان شبیه ساز شما چگونه باید باشد.خط 3-5: در این مثال فرض می کنیم که یک کلاس جداگانه برای مدیریت کدهای عملیاتی (opcodes) ایجاد می کند.خط 10-11: تنظیمات گرافیکی (اندازه پنجره، حالت نمایش، و غیره) و سیستم ورودی به صورت توابع صدا می کندخط 14: حافظه، رجیسترها و صفحه نمایش را پاک می کندخط 15: برنامه را در حافظه کپی می کندخط 21: یک چرخه از سیستم را شبیه سازی می کندخط 24: از آنجایی که سیستم در هر چرخه صفحه نمایش را ترسیم نمی کند، زمانی که باید صفحه نمایش خود را به روز کنیم باید یک پرچم یا flag ترسیم تنظیم کنیم. فقط دو کد عملیاتی باید این پرچم را تنظیم کنند:0x00E0 - صفحه را پاک می کند0xDXYN - یک اسپرایت روی صفحه می‌کشدخط 28: اگر کلیدی را فشار دهیم یا رها کنیم، باید این حالت را در قسمتی که صفحه کلید را شبیه سازی می کند، ذخیره کنیم.چرخه شبیه سازی یا Emulation cycleدر ادامه به چرخه شبیه سازی خواهیم پرداخت.در هر چرخه ، متد emulateCycle صدا زده می شود که یک چرخه از CPU تراشه 8 را شبیه سازی می کند. در طول این چرخه، شبیه ساز یک opcode را واکشی، دیکد و اجرا می کند.واکشی کد عملیاتی یا Fetch opcodeدر طی این مرحله، سیستم یک opcode را از حافظه در مکانی که توسط شمارنده برنامه (pc) مشخص شده است واکشی می کند. در شبیه ساز Chip-8 ما داده ها در آرایه ای ذخیره می شوند که در آن هر آدرس حاوی یک بایت است. از آنجایی که یک opcode برابر با 2 بایت است، باید دو بایت متوالی را واکشی کنیم و آنها را با هم ادغام کنیم تا opcode واقعی را بدست آوریم.برای نشان دادن اینکه چگونه این کار می کند، از کد اپکد 0xA2F0 استفاده خواهیم کرد.برای ادغام هر دو بایت و ذخیره آنها در یک short بدون علامت (نوع داده 2 بایتی) از عملیات بیتی OR استفاده می کنیم:پس واقعا چه اتفاقی افتاد؟ابتدا 0xA2 را 8 بیت به سمت چپ منتقل کردیم که 8 صفر اضافه می کند.در مرحله بعد از عملیات بیتی OR برای ادغام آنها استفاده می کنیم:دیکد opcodeهمانطور که ما opcode فعلی خود را ذخیره کرده ایم، باید opcode را دیکد کنیم و جدول opcode را بررسی کنیم تا ببینیم به چه معناست. با همان opcode ادامه می دهیم:اگر به جدول opcode نگاهی بیندازیم، موارد زیر را به ما می گوید:مثلا ANNN رجیستر I را روی آدرس NNN تنظیم می کندما باید مقدار رجیستر اندیس I را برابر آدرس NNN یعنی (0x2F0) تنظیم کنیم.اجرای opcodeاکنون که می دانیم با opcode چه کار کنیم، می توانیم opcode را در شبیه ساز خود اجرا کنیم. برای دستورالعمل مثال ما 0xA2F0 به این معنی است که ما باید مقدار 0x2F0 را در ثبات اندیس I ذخیره کنیم. از آنجایی که فقط 12 بیت حاوی مقداری است که باید ذخیره کنیم، برای خلاص شدن از شر چهار بیت اول از عملگر AND (&amp;) استفاده می کنیم. (به چهار بیت nibble گفته می شود):کد آن:از آنجایی که هر دستورالعمل 2 بایت است، پس از هر کد عملیاتی باید شمارنده برنامه را دو بایت افزایش دهیم. این درست است مگر اینکه به آدرس خاصی در حافظه بروید یا یک برنامه فرعی را فراخوانی کنید (در این صورت باید شمارنده برنامه را در پشته ذخیره کنید). اگر کد عملیات بعدی باید نادیده گرفته شود، شمارنده برنامه را به چهار افزایش دهید.تایمرها یا Timersعلاوه بر اجرای کدهای عملیاتی، تراشه 8 دارای دو تایمر است که باید پیاده سازی کنید. همانطور که در بالا ذکر شد، هر دو تایمر (تایمر تاخیر و تایمر صدا) اگر روی مقداری بزرگتر از صفر تنظیم شده باشند، تا صفر شمارش معکوس می کنند. از آنجایی که این تایمرها روی 60 هرتز شمارش معکوس می کنند، ممکن است بخواهید چیزی را پیاده سازی کنید که چرخه شبیه سازی شما را کُند کند (60 کد عملیاتی را در یک ثانیه اجرا کنید).دست به کار شویداکنون که اصول اولیه شبیه‌سازی و نحوه عملکرد سیستم را می‌دانید، زمان آن است که همه قطعات را کنار هم قرار دهید و شروع به کدنویسی شبیه‌ساز(ایمولاتور) کنید.مقدار دهی اولیه سیستم یا Initialize systemقبل از اجرای اولین چرخه شبیه سازی، باید وضعیت سیستم خود را آماده کنید. شروع به پاکسازی حافظه و صفر کردن رجیسترها کنید. در حالی که تراشه 8 واقعاً دارای بایوس (BIOS) یا سیستم عامل نیست، یک مجموعه فونت اساسی در حافظه دارد. این فونت باید در محل حافظه 0x50 == 80 و به بعد بارگذاری شود. جزئیات بیشتر در مورد نحوه عملکرد فونت‌ست را می‌توانید در انتهای این راهنما بیابید.نکته مهم دیگری که باید به خاطر بسپارید این است که سیستم انتظار دارد برنامه در محل حافظه 0x200 بارگذاری شود. این بدان معناست که شمارنده برنامه (pc) شما نیز باید روی این مکان تنظیم شود.بارگذاری برنامه یا بازی نوشته شده برای CHIP-8 در حافظهپس از اینکه شبیه ساز را مقداردهی اولیه کردید، برنامه را در حافظه بارگذاری کنید (از fopen در حالت باینری استفاده کنید) و شروع به پر کردن حافظه در مکان 0x200 == 512 کنید.شبیه سازی را شروع کنیدسیستم ما اکنون آماده اجرای اولین کد عملیاتی خود است. همانطور که در بالا ذکر شد، ما باید opcode را واکشی، دیکد و اجرا کنیم. در این مثال ما با خواندن 4 بیت اول کد فعلی شروع می کنیم تا بفهمیم که کد opcode چیست و شبیه ساز باید چه کاری انجام دهد:در برخی موارد ما نمی‌توانیم تنها به چهار بیت اول تکیه کنیم تا ببینیم کد opcode چیست. به عنوان مثال، 0x00E0 و 0x00EE هر دو با 0x0 شروع می شوند. در این مورد یک سوئیچ اضافی اضافه می کنیم و چهار بیت آخر را با هم مقایسه می کنیم:نمونه های opcodeاجازه دهید نگاهی به کدهای عملیاتی دیگری بیندازیم که ممکن است در ابتدا دلهره آور به نظر برسند.مثال اول opcode 0x2NNNاین opcode زیر روال (subroutine) را در آدرس NNN فراخوانی می کند. از آنجایی که برای آدرس NNN نیاز به پرش موقت داریم، به این معنی است که باید آدرس فعلی شمارنده برنامه را در پشته ذخیره کنیم. پس از ذخیره مقدار شمارنده برنامه در پشته، نشانگر پشته را افزایش دهید تا از بازنویسی پشته فعلی جلوگیری شود. اکنون که شمارنده برنامه را ذخیره کرده ایم، می توانیم آن را به آدرس NNN تنظیم کنیم. به یاد داشته باشید، چون ما یک زیربرنامه را در یک آدرس خاص فراخوانی می کنیم، نباید شمارنده برنامه را دو برابر کنید.مثال دوم  opcode 0x8XY4این اپکد مقدار VY را به VX اضافه می کند. ثبات VF در صورت وجود رقم نَقلی بر روی 1 و در صورت عدم وجود روی 0 تنظیم می شود. از آنجا که ثبات فقط می تواند مقادیر 0 تا 255 (مقدار 8 بیتی) را ذخیره کند، به این معنی است که اگر مجموع VX و VY بزرگتر از 255 باشد، نمی توان آن را در ثبات ذخیره کرد (یا در واقع دوباره از 0 شروع به شمارش می کند. ). اگر مجموع VX و VY بزرگتر از 255 باشد، از flag یا پرچم رقم نَقلی استفاده می کنیم تا به سیستم بفهمانیم که مجموع هر دو مقدار واقعاً بزرگتر از 255 بوده است. فراموش نکنید که شمارنده برنامه را پس از اجرای کد عملیاتی دو برابر افزایش دهید.مثال سوم opcode x0FX33نمایش ده دهی با کد باینری VX را در آدرس های I ذخیره می کند، I به اضافه 1، و I به علاوه 2باید اعتراف کنم که نمی‌توانستم نحوه پیاده‌سازی این opcode را بفهمم، بنابراین از راه‌حل TJA استفاده کردم.مدیریت گرافیک و ورودیترسیم پیکسل هاآن opcode ای که مسئول رسم به صفحه نمایش ما هست برابر 0xDXYN است. توضیحات ویکی پدیا موارد زیر را به ما می گوید:یک sprites در مختصات (VX, VY) می‌کشد که عرض آن 8 پیکسل و ارتفاع N پیکسل است. هر ردیف 8 پیکسلی با شروع از مقداری که در محل حافظه ثبات I به صورت بیت کد خوانده می شود. ارزش ثبات I بعد از اجرای این دستورالعمل تغییر نمی کند. همانطور که در بالا توضیح داده شد، اگر پیکسل های صفحه نمایش از حالت تنظیم به حالت تنظیم نشده در هنگام ترسیم اسپرایت برگردند، VF روی 1 تنظیم می شود و اگر این اتفاق نیفتد، روی 0 تنظیم می شود.همانطور که شرح کد عملیاتی به ما می گوید، تراشه 8 در واقع با کشیدن sprites روی صفحه نمایش می کشد. به ما مکان جایی که sprite باید رسم شود (opcode به ما می گوید که کدام ثبات V را باید بررسی کنیم تا مختصات X و Y را واکشی کنیم) و تعداد ردیف ها (N) را به ما می دهد. عرض هر اسپرایت ثابت است (8 بیت / 1 بایت). وضعیت هر پیکسل با استفاده از عملیات XOR بیتی تنظیم می شود. این بدان معنی است که وضعیت پیکسل فعلی را با مقدار فعلی در حافظه مقایسه می کند. اگر مقدار فعلی با مقدار موجود در حافظه متفاوت باشد، مقدار بیت 1 خواهد بود. اگر هر دو مقدار مطابقت داشته باشند، مقدار بیت 0 خواهد بود.بیایید فرض کنیم که اپکد 0xD003 بود. این بدان معنی است که می خواهد یک اسپرایت در مکان 0.0 بکشد که 3 ردیف ارتفاع دارد. در محل حافظه I، مقادیر زیر تنظیم شد:این 3 بایت چگونه یک sprite را نشان می دهد؟ به مقادیر باینری هر بایت نگاهی بیندازید:شما باید از نمایش باینری برای پر کردن آرایه خود ([ ]gfx) استفاده کنید .قبل از تنظیم مقدار در [ ]gfx با استفاده از عملگر XOR، همچنین باید بررسی کنید که آیا هر یک از پیکسل ها از 1 به 0 تغییر کرده است یا خیر. (این آزمایشی برای تشخیص برخورد خواهد بود).نمونه ای از پیاده سازی Opcode 0xDXYNخط 3-4: موقعیت و ارتفاع اسپرایت را مشخص  میکندخط 5: مقدار پیکسلخط 8: تنظیم مجدد VF ثباتخط 9: روی هر سطر حلقه می زندخط 11: مقدار پیکسل را از حافظه که از محل I شروع می شود واکشی می کندخط 12: روی 8 بیت از یک سطرحلقه می زندخط 14: بررسی می کندکه آیا پیکسل ارزیابی شده فعلی روی 1 تنظیم شده است (توجه داشته باشید که 0x80 &gt;&gt; xline اسکن از طریق بایت، یک بیت در آن زمان است)خط 16-17: بررسی می کند که آیا پیکسل روی نمایشگر روی 1 تنظیم شده است یا خیر. اگر تنظیم شده است، باید با تنظیم رجیستر VF برخورد را ثبت کنیم.خط 18: مقدار پیکسل را با استفاده از XOR تنظیم می کندخط 23: ما آرایه [ ]gfx خود را تغییر دادیم و بنابراین باید صفحه را به روز کنیم.خط 24: شمارنده برنامه را به روز کنید تا به کد عملیاتی بعدی برویدورودی کاربرسیستم Chip 8 از یک صفحه کلید HEX ساده استفاده می کند که به کاربران اجازه می دهد با سیستم تعامل داشته باشند. برای شبیه ساز ما این بدان معناست که ما باید روشی را پیاده سازی کنیم که وضعیت هر کلید را در متغیری که حالت های کلید را کنترل می کند، تنظیم کند. در هر چرخه باید وضعیت ورودی کلید را بررسی کنید و آن را در [ ]key ذخیره می کند.در واقع مهم نیست که چه مقداری را ذخیره می‌کنید، زیرا کد opcode 0xEX9E و 0xEXA1 فقط بررسی می‌کنند که آیا کلید خاصی فشار داده شده یا فشرده نشده است. Opcode 0xFX0A فقط برای فشار دادن کلید منتظر می ماند و زمانی که یک کلید دریافت کرد، نام کلید را در ثبات ذخیره می کند و نه حالت کلید را.در زیر نمونه ای از طرح اصلی صفحه کلید را خواهید دید. واقعاً مهم نیست که چگونه نگاشت کلید را اجرا می کنید، اما من چیزی را در سمت راست پیشنهاد می کنم.مجموعه فونت CHIP-8این مجموعه فونت Chip 8 است. عرض هر عدد یا کاراکتر 4 پیکسل و ارتفاع آن 5 پیکسل است.ممکن است درست شبیه آرایه ای از اعداد تصادفی به نظر برسد، اما به موارد زیر دقت کنید:به مثال سمت چپ نگاه کنید که ما داریم عدد 0 را ترسیم می کنیم. همانطور که می بینید می بینید که از 5 مقدار تشکیل شده است. از هر مقدار، ما از نمایش باینری برای ترسیم استفاده می کنیم. توجه داشته باشید که فقط چهار بیت اول (nibble) برای ترسیم یک عدد یا کاراکتر استفاده می شود.جمع بندیامیدواریم این راهنما اطلاعات کافی برای شروع پروژه شبیه ساز خود در اختیار شما قرار دهد. حداقل اکنون باید درک اولیه ای از نحوه کار شبیه سازی و شاید درک بهتری از نحوه اجرای کدهای عملیاتی توسط یک CPU داشته باشید.من پیاده سازی خود را از یک مفسر چیپ 8 قرار داده ام که در زیر می توانید از آن به عنوان مرجع استفاده کنید. فایل فشرده zip حاوی یک باینری برای ویندوز است اما همچنین شامل سورس کد کامل شبیه ساز(ایمولاتور) است. از آنجایی که سورس کد کامل ارائه شده است، توصیه می‌کنم فقط به فایل chip8.cpp به عنوان آخرین راه‌حل نگاه کنید تا ببینید چگونه یک کد عملیاتی خاص را پیاده‌سازی کرده‌ام. فایل chip8.h و main.cpp باید بدون لو دادن (Spoil) بیش از حد قابل مطالعه باشد. در واقع، main.cpp عمدتا حاوی کد GLUT است که می توانید در پروژه های دیگر (غیر مرتبط با شبیه ساز) نیز دوباره از آن استفاده کنید.نسخه myChip8 (31489 دانلود) – آخرین نسخه (نسخه ویندوز + سورس کد)نسخه اولیه mychip8 برای سال 2003 (شامل ابزار دیباگ خوب)نسخه پورت شده اندرویدی برای سال 2008اگر این راهنما برای شما مفید بود به من اطلاع دهید! اگر سوالی دارید یا فکر می‌کنید که بخش‌های اساسی از دست رفته است، لطفاً از بخش نظرات این سایت استفاده کنید ? ! </description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Tue, 08 Mar 2022 16:25:26 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی پایپ لاین CI/CD برای برنامه های Node.js با Jenkins</title>
                <link>https://virgool.io/@hootan09/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%BE%D8%A7%DB%8C%D9%BE-%D9%84%D8%A7%DB%8C%D9%86-cicd-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%87%D8%A7%DB%8C-nodejs-%D8%A8%D8%A7-jenkins-ezyydlfpbddy</link>
                <description>تعریف پایپ لاین در مهندسی نرم افزار: به توالی خطی ای از ماژول های مشخص می گویند که در جهتی مشخص در حال حرکت هستند- منبعتعریف CI/CD:CI: Continuous IntegrationCD: Continuous Deployment/Deliveryادغام مستمر (Continuous Integration) و پیاده سازی مستمر یا تحویل مستمر (Continuous Deployment) دو روش مدرن توسعه نرم افزار هستند.ادغام مستمر (CI) فرآیندی است که هر بار که یکی از اعضای تیم تغییراتی را در کنترل نسخه ای کد (Version Control) انجام می دهد، سپس فرآیند ساخت (Build) و آزمایش (Test) کد به طور خودکار انجام می شود.تحویل مستمر (CD) را می توان به عنوان توسعه یکپارچه سازی مداوم در نظر گرفت و فرآیندی است که به طور خودکار یک برنامه کاربردی را پس از موفقیت آمیز بودن CI به کار می گیرد.هدف مینیمم کردن مدیریت زمان است. بازه زمانی ای که بین توسعه و نوشتن یک خط کد جدید و استفاده از این کد توسط کاربران فعال در نرم افزار تولید شده سپری میشود(فرآیند بروز رسانی نرم افزار تولید شده).چرا CI/CD:مزایای زیادی برای شیوه های CI/CD وجود دارد.من قصد ندارم در مورد هر مزیتی با جزئیات صحبت کنم، اما می‌خواهم چند مورد از مهمترین آنها را در اینجا بیان کنم:مزایای ادغام مستمر (CI):باگ کمترتغییرات زمینه ای (context switching) کمتر ، به عنوان توسعه دهندگان به محض شکسته شدن روند ساخت (Build) ، هشدار داده می شودهزینه تست نرم افزار و کد کاهش میابدتیم تضمین کیفیت QA یا (Quality Assurance) شما زمان کمتری را صرف آزمایش و امتحان می کند مزایای تحویل مستمر (CD):انتشار نسخه ها ریسک کمتری دارندفرآیند انتشار ساده تر میشودمشتریان یک جریان مداوم از پیشرفت ها را مشاهده می کنندتوسعه را تسریع می کند زیرا نیازی به توقف توسعه برای انتشار نیستدر این آموزش ما چه چیزی خواهیم ساخت:در این آموزش ما یک برنامه ساده Node.js ای خواهیم ساخت که روی سرورهای دیجیتال اوشن میزبانی می شود.علاوه بر این ما قصد داریم یک سرور اتوماتیک سازی به نام جنکینز (Jenkins) را پیکربندی کنیم و در یک سرور جداگانه از دیجیتال اوشن میزبانی کنیم.این Jenkins به ما کمک می کند که فرآیندهای CI/CD اتوماتیک شود.در هر بار تغییر کدهای موجود در گیت ریپازیتوری برای برنامه Node.js ای ما ، Jenkins مطلع می شود و تغییرات را در سرور Jenkins (مرحله 1) دریافت می کند ، وابستگی ها را نصب می کند (مرحله 2) و تستِ ادغام (مرحله 3) را اجرا می کند.اگر همه تست ها درست انجام شود، جنکینز برنامه را در سرور Node.js ای ما مستقر می کند (مرحله 4). در صورت عدم موفقیت، به توسعه دهنده اطلاع داده می شود.شمای کلی روش انجام (شکل 1)ساخت یک برنامه Node.js:قبل از نوشتن پایپ لاین های CI/CD ، ما اول نیاز به یک برنامه داریم تا قابل تست کردن و پیاده سازی باشد.ما قصد داریم یک برنامه ساده تحت وب Node.js ای بسازیم که متن &quot;hello world&quot; را به عنوال ریسپانس برگرداند.اول بیایید تا باهم مراحل ساخت یک ریپازیتوری گیت هابی را برای این پروژه انجام دهیم.ساخت ریپازیتوری گیت هاب:ابتدا یک ریپازیتوری جدید در حساب کاربری GitHub خود ایجاد کنید و نام آن را node-app بگذارید.شما می توانید نوع ریپوی خود را Public یا Private انتخاب کنیددر قسمت چک باکس تیک گزینه Initialize this repository with a README را بزنیددر منوی کشویی در قسمت Add .gitignore گزینه Node را انتخاب کنیدبر روی دکمه Create repository کلیک کنیداکنون برنامه node-app مارا از ریپوی زیر به کامپیوتر خود کُلن کنید و به پوشه آن بروید.git clone git@github.com:&lt;github username&gt;/node-app.git
cd node-appبرنامه Node.js موجود:اولین گام هنگام ساختن یک برنامه Node.js ای ، ایجاد فایل package.json است. در این فایل وابستگی های اپلیکیشن را لیست می کنیم. یک فایل جدید در ریشه پروژه خود به نام package.json ایجاد کنید و محتوای زیر را در آن کپی کنید:{
     “name”: “node-app”,
     “description”: “hello jenkins test app”,
     “version”: “0.0.1”,
     “private”: true,
     “dependencies”: {
           “express”: “3.12.0”
      },
      “devDependencies”: {
             “mocha”: “1.20.1”,
             “supertest”: “0.13.0”
      }
}نیازمندی های موجود در این برنامه عبارتند از:فریم ورک Expressفریم ورک Mocha  جهت تست برنامه های نود (شما می توانید هر کدام از فریم ورک های تست دیگر نظیر Jasmin , Jest , Tape و... را استفاده کنید)ماژول Supertest که یک انتزاع سطح بالا برای تست برنامه های مبتنی بر HTTP ارائه می دهدپس از اینکه وابستگی های خود را در فایل package.json تعریف کردیم، آماده نصب آنها هستیم:$ npm installبسیار عالی! برای نوشتن کد آماده هستید؟ یک فایل جدید در ریشه پروژه به نام index.js ایجاد کنید و کد زیر را کپی کنید://importing node framework
const express = require(‘express’);
 
const app = express();

//Respond with &amp;quothello world&amp;quot for requests that hit our root &amp;quot/&amp;quot
app.get(‘/’, function (req, res) {
     res.send(‘hello world’);
    });

//listen to port 3000 by default
app.listen(process.env.PORT || 3000);
 
module.exports = app;هنگامی که درخواست‌هایی که به URL ریشه ما (“/“) در مرورگر وارد می شود، برنامه ما با &quot;hello world&quot; پاسخ می‌دهد.و این شد برنامه ما.در زیر ساختار پوشه آن را می بینید.اکنون ما آماده ایم تا برنامه خود را اجرا کنیم.$ node index.jsشما می توانید برنامه خود را در مرورگر مشاهده کنید و به آدرس زیر بروید:http://localhost:3000نوشتن تست برای برنامه:ما آماده ایم تا اولین تست خود را بنویسیم.تست ما به آدرس ریشه سایت (&quot;/&quot;) می رود و تأیید می کند که صفحه با متن &quot;hello world&quot; پاسخ می دهد یا خیر.یک پوشه جدید به اسم test ایجاد کنید و سپس در داخل آن فایل test.js را ایجاد کنید.کدهای زیر را در این فایل کپی کنید.const request = require(‘supertest’);
const app = require(‘../index.js’);

describe(‘GET /’, function() {
     it(‘respond with hello world’, function(done) { 

     //navigate to root and check the the response is &amp;quothello world&amp;quot
      request(app).get(‘/’).expect(‘hello world’, done);
      });
});ما قصد داریم که از Mocha برای اجرای تست های خود استفاده کنیم.ما Mocha را به عنوان بخشی از devDependencies خود در فایل package.json نصب کردیم.برای اجرای تست باید فایل test/test.js/ خود را به عنوان آرگومان Mocha ارسال کنیم.$ ./node_modules/.bin/mocha ./test/test.jsاگر این دستور را از ریشه پروژه خود اجرا کنید، اجرای تست و قبولی را خواهید دید.با نگاهی به شکل 1 مرحله شماره 3، ما می خواهیم Jenkins تست ادغام ما را پس از ساخت (Build) آن اجرا کند. برای رسیدن به آن، باید یک شل اسکریپت  (shell script) در پروژه خود ایجاد کنیم که آزمایش ما را آغاز کند.یک پوشه جدید با نام script ایجاد کنید و در آن یک فایل با نام test (بدون فرمت و پسوند) ایجاد کرده و سپس کدهای زیر را در آن کپی کنید.#!/bin/sh./node_modules/.bin/mocha ./test/test.jsجهت اعطای مجوزهای اجرایی به این فایل دستور زیر را اجرا کنید:$ chmod +x script/testو آن را با اجرای این شل اسکریپت از ریشه پروژه تست کنید:$ ./script/testبوم! تست ادغام آماده است و اکنون ما آماده هستیم تا کد خود را به GitHub پوش (push) کنیم:$ git add .
$ git commit -m ‘simple node app with test’
$ git push origin masterپیاده سازی سرور نود در دیجیتال اوشن:ما قصد داریم برنامه نود خود را روی یک سرور میزبانی کنیم تا تمام جهان بتوانند شاهکار ما را ببینند. ما از DigitalOcean به عنوان ارائه دهنده میزبانی خود استفاده خواهیم کرد. DigitalOcean یک راه آسان برای پیکربندی سرورها و اجرای نمونه های جدید ارائه می دهد.ساخت یک دراپلت (Droplet) نود :ابتدا ثبت نام کنید و به حساب DigitalOcean خود وارد شوید.روی دکمه Create new droplet کلیک کنید.انتخاب یک ایمیج: روی تب one click app کلیک کنید و node JS را از لیست انتخاب کنیداندازه مقدار رم را انتخاب کنید: 1 گیگابایت (ارزان ترین)انتخاب منطقه دیتاسنتر: نزدیکترین منطقه به خود را انتخاب کنید. من منطقه 3 نیویورک را انتخاب می کنمکلیدهای SSH خود را اضافه کنید: کلید SSH دستگاه محلی خود را اضافه کنید. اگر کلید SSH ندارید این را دنبال کنید تا یکی بسازید. این دستور کلید عمومی SSH شما را کپی کرده و در قسمت متن قرار می دهد$ pbcopy &lt; ~/.ssh/id_rsa.pubنام میزبان را انتخاب کنید: نام آن را &quot;nodejs-app&quot; بگذاریدروی دکمه ایجاد کلیک کنیدتنضیمات سرور Nodejs-app:بیایید کلاه DevOps را روی سر بگذاریم و سرور نود خود را راه اندازی کنیمترمینال را در کامپیوتر خود خود باز کنید و به عنوان کاربر اصلی (root) وارد سرور nodejs-app خود شوید:$ ssh root@NODE.SERVER.IPاکنون شما به عنوان کاربر root که یک کاربر فوق العاده قدرتمند است، وارد شده اید. و &quot;با قدرت بزرگ، مسئولیت های بزرگی به وجود می آید&quot;.از آنجایی که ما مسئولیت‌ها را دوست نداریم، بیایید یک کاربر جدید برای انجام تنظیمات سرور ایجاد کنیم و آن را با نام خانوادگی (last name) شما نامگذاری کنیم:$ adduser &lt;lastname&gt;رمز عبور کاربر را وارد کنید و دستورات را دنبال کنید. قبل از اینکه به اکانت جدید خود سوییچ کنیم، باید به او امتیازات sudo بدهیم:$ usermod -a -G sudo &lt;username&gt;اکنون می توانید به اکانت جدید خود سوییچ کنید:$ su — usernameپیاده سازی برنامه نود در سرور:سرور DigitalOcean ما با Node ارائه می شود اما Git روی آن نصب نیست. اجازه می دهیم git را با استفاده از app-get نصب کنیم:$ sudo apt-get install gitبرنامه نود خود را از ریپو به سرور کُلن کنید:$ git clone https://github.com/&lt;username&gt;/node-app.gitبه پوشه آن بروید و نیازمندی ها را با فرمان زیر نصب کنید:$ cd node-app
$ npm install — productionقبل از اینکه بتوانیم به برنامه خود در مرورگر دسترسی پیدا کنیم، باید یک مرحله اضافی را تکمیل کنیم. همانطور که به یاد دارید ما برنامه خود را به طور پیش فرض روی پورت 3000 اجرا می کنیم. فایروال DigitalOcean دسترسی مشتریان به هر پورت به جز 80 پورت را مسدود می کند. خوشبختانه اوبونتو دارای ابزار پیکربندی فایروال UFW است که با دستور زیر  قوانین (rule) فایروال را برای رفع انسداد پورت های خاص اضافه می کند.$ sudo ufw allow 3000
$ node index.jsاکنون می توانید با اضافه کردن PORT به آدرس IP سرور به برنامه نود خود دسترسی پیدا کنید:http://NODE.SERVER.IP:3000اجرای همیشگی برنامه نود: شروع برنامه نود با دستور بالا برای اهداف توسعه خوب است اما برای پروداکشن خیر. در صورت خرابی نمونه نود ما به فرآیند و برنامه ای نیاز داریم که راه اندازی مجدد خودکار را انجام دهد. ما قصد داریم از ماژول PM2 برای کمک به این کار استفاده کنیم. PM2 یک مدیر فرآیند (Proccess manager) برای اهداف کلی و یک Runtime برای برنامه‌های Node.js با قابلیت Load Balancer داخلی است. بیایید PM2 را نصب کنیم و نمونه نود خود را راه اندازی کنیم:$ sudo npm install pm2@latest -g
$ pm2 start index.jsاکنون برنامه نود ما تنظیم و اجرا شده است.تنظیم سرور Jenkins :ساخت یک دراپلت (Droplet) Jenkins:بیایید با ایجاد دومین دراپلت DigitalOcean شروع کنیم که برنامه جنکینز ما را میزبانی می کند. دستورالعمل های زیر را در بخش ساخت یک دراپلت (Droplet) نود در بالا دنبال کنید و &quot;jenkins-app&quot; را به عنوان نام میزبان خود انتخاب کنید. در نهایت با 2 دراپلت مواجه خواهید شد:ساخت کاربر جدید:به عنوان کاربر روت به دراپلت جدید SSH بزنید.یک کاربر جدید بسازید و به آن امتیازات sudo بدهید و به کاربر تازه اضافه شده بروید:$ ssh root@JENKINS.SERVER.IP
$ adduser &lt;username&gt;
$ usermod -a -G sudo &lt;username&gt;
$ su — &lt;username&gt;جنکینز باید بتواند تغییرات را از ریپوی برنامه نود دریافت کند، بنابراین، باید git را روی این سرور نیز نصب کنیم:$ sudo apt-get install gitنصب  Jenkins:دریافت Jenkins://add the repository key to the system
$ wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt-key add -//append the Debian package repository address to the server&#039;s echo $ deb https://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list//update
$ sudo apt-get updateنصب Jenkins:$ sudo apt-get install jenkinsاجرای Jenkins:$ sudo systemctl start jenkinsجنکینز روی پورت 8080 اجرا می شود. فایروال را به خاطر دارید؟ بیایید پورت را باز کنیم:$ sudo ufw allow 8080و اکنون می توانیم از طریق وب و مرورگر به Jenkins برویم:http://JENKINS.SERVER.IP:8080تنظیمات Jenkins:هنگامی که به صفحه اصلی جنکینز می روید، احتمالاً متوجه مرحله دیگری شده اید که باید انجام دهید. شما باید قفل جنکینز را باز کنیدبا استفاده از مسیر زیر رمز عبور جنکینز اجرا شده در سرور جنکینز خود را کپی کنید$ sudo vim /var/lib/jenkins/secrets/initialAdminPasswordرمز عبور را در قسمت باکس متن در صفحه وب قرار دهید. شما آماده راه اندازی جنکینز هستید. ابتدا می خواهیم افزونه GitHub را اضافه کنیم. از منوی سمت چپ manage Jenkins را انتخاب کنید و به manage plugins بروید. در صفحه پلاگین ها، تب available را انتخاب کنید و به دنبال GitHub plugin بگردید، چک باکس آن را انتخاب کنید و روی دکمه Download now and install after restart کلیک کنید.پس از اتمام نصب، صفحه را به پایین اسکرول کنید و گزینه Restart Jenkins when installation is complete را انتخاب کنید. با این کار جنکینز مجددا راه اندازی می شود و نصب پلاگین کامل می شود.تغییر پسورد ادمین Jenkins:من در این مرحله پیشنهاد می کنم رمز عبور کاربر ادمین جنکینز خود را تغییر دهید. از منوی سمت چپ گزینه Manage Jenkins را انتخاب کرده و روی Manage Users کلیک کنید. کاربر ادمین را انتخاب کنید و یک رمز عبور جدید انتخاب کنید. در آینده هنگام ورود به Jenkins از رمز عبور جدید استفاده خواهید کرد.ساخت Jenkins Job:ما می‌خواهیم جاب Jenkins خود را ایجاد کنیم که مسئول برداشتن تغییرات کد از ریپوی گیت node-app ، نصب وابستگی‌ها، اجرای تست و استقرار برنامه ای است که هر بار که یک توسعه‌دهنده به شاخه اصلی ریپو (Main Branch) node-app تغییراتی در کد می‌دهد، جاب را اجرا کنیم.روی دکمه New Item کلیک کنید، نام مورد را node-app بگذارید و گزینه Build a free-style software project را انتخاب کنید و دکمه OK را بزنید.تنظیمات Jenkins Job:در قسمت Source Code Management دکمه رادیویی git را انتخاب کنید و لینک github https را به قسمت Repository URL وارد کنید:https://github.com/&lt;username&gt;/node-app.gitدر قسمت Build Triggers گزینه GitHub hook trigger for GITScm polling را انتخاب کنید.این کار جاب جنکینز مارا با هر بار پوش کردن تغییرات در برنچ Main گیت هاب اجرا می کند.در قسمت Build  روی دکمه Add Build Step کلیک کنید و گزینه Execute Shell را انتخاب کنید.دستورات زیر را درقسمت مربوط به Command وارد کنیدnpm install
./script/testدر این مرحله از بیلد، ما قصد داریم وابستگی ها را نصب کنیم و سپس شل اسکریپت  تست خود را اجرا کنیم.اضافه کردن Git Webhook:ما قصد داریم Git Webhook را اضافه کنیم تا هر بار که توسعه‌دهنده کد جدیدی را به برنچ Main در گیت هاب ارسال می‌کند، به Jenkins اطلاع دهیم.به ریپوی خود Node-app در گیت هاب بروید، روی تب Settings کلیک کنید، Webhooks را از منوی سمت چپ انتخاب کنید و روی دکمه Add Webhooks کلیک کنید. آدرس webhook Jenkins خود را در Payload URL وارد کنید:http://JENKINS.SERVER.IP:8080/github-webhook/و گزینه Just the Push Event را انتخاب کنید در نهایت دکمه Add webhook را کلیک کنید.بیایید آنچه را که تا کنون انجام داده ایم ،امتحان کنیم. به پروژه node-app در کامپیوتر خود بروید و نسخه موجود در package.json را به 0.0.2 تغییر دهید. این تغییر را کامیت و به GitHub پوش کنید. پس از پوش کردن ، به جاب (Job) جنکینز خود در مرورگر بروید و مشاهده کنید که جاب جنکینز با موفقیت شروع و تکمیل می شود.پیاده سازی و Deployment:این قسمت ، آخرین قطعه از پازل پیاده سازی برنامه نود ما در سرور node-app است، زمانی که تست ما اجرا و قبول خواهد شد.احراز هویت SSH:برای انجام این کار، سرور جنکینز باید بتواند به سرور node-app وارد شود، تغییرات جدید در مخزن را پول (pull) کند، وابستگی ها را نصب کند و سرور را مجددا راه اندازی کند. اجازه دهید ابتدا دسترسی ssh به جنکینز را تنظیم کنیم.وقتی جنکینز را نصب می کنیم، به طور خودکار یوزر جنکینز را ایجاد می شود. با SSH به سرور جنکینز به عنوان کاربر روت وارد میشویم و مراحل زیر را انجام می دهیم:$ ssh root@JENKINS.SERVER.IPسوییچ به کاربر جنکینز:$ su — jenkinsایجاد کلید SSH:$ ssh-keygen -t rsaسپس کلید تولید شده را در مسیر var/lib/jenkins/.ssh/id_rsa/ ذخیره میکنیمcat ~/.ssh/id_rsa.pubو خروجی را در کلیپ بورد خود کپی کنید. اکنون آماده هستیم تا کلید عمومی را روی سرور nodejs-app قرار دهیم تا احراز هویت بین سرور Jenkins و سرور nodejs-app تکمیل شود.از طریق SSH به عنوان روت وارد سرور nodejs-app شده و به کاربر خود سوئیچ کنید:$ ssh root@NODE.SERVER.IP
$ su - &lt;username&gt;فایلی را که در آن کلیدهای مجاز ذخیره شده است باز کنید:$ vim ~/.ssh/authorized_keysو کلید عمومی (public key) جنکینز را که ایجاد کردیم در آن فایل کپی کنید. با فشار دادن دکمه esc روی صفحه کلید خود آن را ذخیره کنید، x: را تایپ کرده و enter را فشار دهید.مجوز (permission) صحیح را در پوشه ssh. تنظیم کنید:chmod 700 ~/.ssh
chmod 600 ~/.ssh/*قبل از اینکه ادامه دهیم ، اجازه می‌دهیم راه‌اندازی SSH خود را امتحان کنیم. اگر تنظیم درست باشد، باید بتوانیم از سرور جنکینز (JENKINS.SERVER.IP) به عنوان کاربر jenkins به username&gt;@NODE.SERVER.IP&gt; بدون وارد کردن رمز عبور SSH کنیم.$ ssh root@JENKINS.SERVER.IP
$  su - jenkins
$ ssh &lt;username&gt;@NODE.SERVER.IPاتوماتیک سازی Deployment:ما قصد داریم شل اسکریپت دیگری ایجاد کنیم که مسئول استقرار و پیاده سازی است. یک فایل دیگر در پوشه script به نام deploy ایجاد کنید و اسکریپت زیر را به آن اضافه کنید:#!/bin/sh
ssh ezderman@NODE.SERVER.IP &lt;&lt;EOF
   cd ~/node-app
   git pull
   npm install — production
   pm2 restart all
   exit
EOFاین اسکریپت به سرور نود SSH می زند، تغییرات را از GitHub پول (pull) می کند، وابستگی‌ها را نصب می‌کند و سرویس را دوباره راه‌اندازی می‌کند.فایل اسکریپت جدید خود را با دستور زیر قابل اجرا کنید:$ chmod +x script/deployقبل از اینکه تغییرات خود را کامیت (commit) کنیم، اجازه دهید مرحله Deployment را به Jenkins Job خود اضافه کنیم:و آن را ذخیره کنید.بررسی درستی روال انجام کارها:ما آماده ایم هر چیزی را که از ابتدا ساخته ایم آزمایش کنیم. به پروژه node-app خود بروید و فایل index.js را ویرایش کنید تا سرور نود ما پاسخ &quot;Hey world&quot; برگرداند. فراموش نکنید test/test.js خود را تغییر دهید تا این رشته جدید را نیز تست کند.در نهایت تغییرات در کد ها را کامیت و پوش کنید$ git add .
$ git commit -m ‘add deployment script’
$ git push origin masterبعد از پوش کردن خواهید دید که جاب جنکینز شروع خواهد شد و وقتی تمام بشود تغییرات را در آدرس http://NODE.SERVER.IP:3000 خواهید دید.و پایانمنبع</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Fri, 03 Dec 2021 21:31:49 +0330</pubDate>
            </item>
                    <item>
                <title>ساخت یک شبکه عصبی درهم پیچیده برای طبقه بندی تصویر با کمک تنسورفلو</title>
                <link>https://virgool.io/@hootan09/%D8%B3%D8%A7%D8%AE%D8%AA-%DB%8C%DA%A9-%D8%B4%D8%A8%DA%A9%D9%87-%D8%B9%D8%B5%D8%A8%DB%8C-%D8%AF%D8%B1%D9%87%D9%85-%D9%BE%DB%8C%DA%86%DB%8C%D8%AF%D9%87-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%B7%D8%A8%D9%82%D9%87-%D8%A8%D9%86%D8%AF%DB%8C-%D8%AA%D8%B5%D9%88%DB%8C%D8%B1-%D8%A8%D8%A7-%DA%A9%D9%85%DA%A9-%D8%AA%D9%86%D8%B3%D9%88%D8%B1%D9%81%D9%84%D9%88-mudcorcnqvhi</link>
                <description>شبکه عصبی درهم پیچیده یا کانولوشن (Convolutional Neural Network) یا به اختصار (CNN) یک نوع خاص از شبکه عصبی عمیق یا همان یادگیری عمیق (Deep Neural Network) می باشد که در مشکلات بینایی کامپیوتری (Computer Vision) عملکرد چشم گیری دارد . به طور مثال در طبقه بندی تصاویر ، تشخیص اشیاء و … استفاده میشود.در این مقاله ما قصد داریم که یک دسته بندی کننده تصاویر (Image Classification) با کمک تنسورفلو بسازیم و این کار را با استفاده از پیاده سازی CNN انجام میدهیم تا تصاویر سگ ها و گربه ها را دسته بندی کنیم.در برنامه نویسی سنتی این ممکن نیست که یک راه حل مقیاس پذیر برای مسائلی مانند بینایی کامپیوتر ساخت که با یک الگوریتم برای شناسایی ماهیت موجود در تصاویر به اندازه کافی خوب باشد.با کمک یادگیری ماشین ما میتوانیم به وسیله آموزش دادن مدلی برای مثال‌های داده شده ، به پیش‌بینی تقریبی داده‌های دیده نشده بپردازیم که برای استفاده موضوعی کافی باشد.شبکه عصبی درهم پیچیده یا کانولوشن (CNN) چطور کار میکند:این شبکه عصبی (CNN) از چندین لایه ی پیچشی (convolution layers) و لایه های ادغام کننده (pooling layers) و لایه های متراکم (dense layers) ساخته شده است.ایده اصلی این لایه های پیچشی یا کانولوشن (convolution layers) این است که تصاویر ورودی را طوری تغییر دهد و تبدیلاتی انجام دهد که بتوان از این تغییرات مشخصه هایی (نظیر گوش و بینی و پاها) از سگ ها و گربه ها را استخراج کند و انها را به صورت متمایز برای هر گونه (سگ یا گربه) درست انتخاب کند.این کار با درگیر کردن داده های تصاویر با یک کرنل (kernel یا هسته) انجام میشود.یک کرنل فیلتر خاصی است که جهت استخراج ویژگی های مشخصی از تصاویر استفاده میشود.همچنین استفاده از چندیدن فیلتر کرنل (چندیدن کرنل) جهت استخراج چندین مشخصه از یک تصویر نیز امکان پذیر است .معمولا یک تابع فعال سازی (activation function) به طور مثال با اسم های (tanh یا relu) به مقادیر پیچیده شده ی حاصل از ترکیب کرنل و داده های تصویر اعمال میشود تا غیر خطی بودن را بتوان افزایش داد.کار اصلی لایه های ادغام کننده (pooling layers) این است که اندازه و ابعاد تصویر را کاهش دهد.به واسطه این کار فقط مشخصه های مهم از تصویر باقی می ماند و باقی قسمت ها و نواحی تصویر حذف میگردد.استفاده از این لایه ها هزینه پردازشی را نیز کاهش میدهد و باعث بهبود سرعت محاسبه می گردد.یکی از مهم ترین استراتژی و روش های ادغام ، تجمع حداکثری (max-pooling) و همچنین جمع آوری متوسط (average-pooling) میباشد.در حقیقت اندازه ماتریس ادغام (فیلتر ادغام) ، میزان کاهش تصویر را مشخص میکند.به طور مثال یک ماتریس 2x2 اندازه تصویر را 50% درصد کاهش خواهد داد.این مجموعه از لایه های پیچشی یا کانولوشن (convolution layers) و لایه های ادغام کننده (pooling layers) به ما کمک خواهد کرد تا مشخصه های مهم در تصویر را پیدا کنیم و بعدا از لایه های متراکم (dense layers) برای یادگیری و پیشبینی استفاده خواهد شد.ساخت یک طبقه بندی کننده تصویر:شبکه عصبی درهم پیچیده یا کانولوشن (CNN) یک نوع از شبکه عصبی عمیق (Deep Neural Network) می باشد که جهت آموزش و یادگیری ماشین نیاز به قدرت محاسباتی و پردازشی زیادی دارد.علاوه بر این جهت بدست آوردن دقت کافی در ساخت مدل (Model) باید دیتاست یا مجموعه داده ما نسبتا بزرگ باشد.از این رو کدهای برنامه نویسی را در Google Colab اجرا میکنیم که یک پلتفرم با هدف توسعه و تحقیقاتی می باشد.این Colab از سخت افزار GPU نیز پشتیبانی میکند که سرعت فرایند یادگیری و آموزش (training) را تقویت می کند.دانلود و بارگذاری دیتاست:این دیتاست شامل 2000 تصاویری از سگ ها و گربه ها میباشد که فرمت آن JPG است.اول نیاز داریم که این دیتاست را دانلود و فایل ZIP آن را استخراج (extract) کنیم.(در اینجا دیتاست در پوشه tmp/ بارگذاری میشود )دانلود دیتاستاستخراج و بازکردن فایل ZIP دیتاستپوشه باز شده شامل 2 زیرپوشه با نام های train و validation می باشد.این دو پوشه جهت آموزش داده و تست کردن ، مورد استفاده قرار خواهند گرفت.داخل هر دو پوشه همچنین 2 زیر پوشه دیگر نیز وجود دارد که شامل عکس های مجزا از سگ ها و گربه ها میشود و ما به راحتی میتوانیم از این پوشه ها جهت تست و آموزش در داخل تنسورفلو استفاده کنیم.در حقیقت ما دو نوع کلاس داده داریم (سگ ها و گربه ها) که به وسیله ی مولدهای داده (Data Generator) در تنسورفلو مورد استفاده قرار میگیرد.مشخص کردن مسیر تصاویر تست و آموزش در برنامهبارگذاری داده ها توسط مولد داده تصاویر در تسورفلودر اینجا ما دوتا مولد داده (Data Generator) برای تست و آموزش داریم.هنگام بارگذاری داده ها، مقیاس مجدد  (1/255) برای نرمال سازی (normalize) مقادیر پیکسل جهت همگرایی سریعتر مدل اعمال می شود.علاوه بر این هنگام بارگذاری داده ها ما از دسته های 20 تایی تصاویر استفاده میکنیم و همه آنها به اندازه 150X150 تغییر اندازه داده شده اند.اگر تصاویری اندازه متفاوتی داشته باشند این کار باعث یکسان سازی اندازه ها میشود.ساخت مدل:قبلا داده ها در برنامه بارگذاری و آماده شده است اکنون ما میتوانیم مدل را بسازیم.در اینجا من از 3 لایه کانولوشن یا پیچشی  (convolutional layers) و سپس 3 لایه تجمع حداکثری (max-pooling) استفاده خواهم نمود.سپس یک لایه Flatten و در نهایت از 2 لایه متراکم (Dense layers) استفاده خواهم نمود.ساخت مدل CNNدر سه لایه کانولوشن اول من از 16 کرنل استفاده کردم که اندازه هر کرنل یک ماتریس 3x3 می باشد.زمانی که فیلتر های کرنل با تصویر درهم امیخته شد به تابع فعال سازی (activation function) با نام relu فرستاده میشود تا مقداری غیر خطی بدست آید.شکل ورودی به این لایه باید 150x150 باشد که قبلا این تغییر اندازه را اعمال کرده ایم.همچنین تمام تصاویر رنگی هستند و شامل سه کانال رنگی RGB (قرمز-سبز-آبی) می باشند.در لایه max-pooling من یک کرنل 2x2 اضافه کردم که بزرگترین مقادیر را انتخاب خواهد کرد و باعث خواهد شد تصویر ما 50% درصد کاهش اندازه پیدا کند.این دو دسته ی (convolution و max-pooling) سه لایه ای ها جهت استخراج مشخصه هایی از تصویر بکار گرفته شده اند.اگر پیچیدگی و سختی مشخصه ها زیادتر از انتظار باشد ، باید لایه های بیشتری اضافه کنیم تا مدل عمیق تری بسازیم.لایه Flatten خروجی را از آخرین لایه max-pooling میگیرد و آن را تبدیل به یک آرایه یک بُعدی میکند.این لایه یک بُعدی میتواند خوراک لایه های Dense ما بشود.لایه متراکم Dense یک لایه منظم از نورون ها در یک شبکه عصبی می باشد.اینجاست که فرایند حقیقی یادگیری اتفاق می افتد و وزن ها (weights) تنظیم میگردند.در اینجا ما 2 لایه متراکم  Dense داریم و از آنجایی که این یک طبقه بندی باینری (دودویی) است، تنها 1 نورون در لایه خروجی وجود دارد.تعداد نورون ها در لایه دیگر را می توان به عنوان یک فراپارامتر تنظیم کرد تا بهترین دقت را به دست آورد.آموزش (Train) مدل:قبلا ما یک مدل را ساختیم و اکنون میتوانیم آن را کامپایل و گردآوری کنیم.کامپایل مدلدر اینجا باید نحوه محاسبه ضرر (loss) یا خطا (error) را تعریف کنیم.از آنجایی که ما از یک طبقه بندی باینری استفاده می کنیم، می توانیم از binary_crossentropy استفاده کنیم.با پارامتر بهینه کننده، نحوه تنظیم وزن ها در شبکه را به گونه ای ارسال می کنیم که ضرر یا loss کاهش یابد.گزینه های زیادی وجود دارد که می توان از آنها استفاده کرد و در اینجا من از روش RMSprop استفاده می کنم.در نهایت، از پارامتر متریک (قابل سنجش) برای تخمین اینکه مدل ما چقدر خوب است استفاده می شود و در اینجا از دقت یا accuracy استفاده می کنیم.حالا ما میتوانیم آموزش دادن مدل را شروع کنیم.آموزش دادن مدلدر اینجا ما داده هایی که برای train و validation توسط مولد بارگذاری کرده بودیم را به عنوان ورودی میدهیم.آنجایی که مولد داده ما دارای اندازه ی 20 دسته ای برای ورودی است، باید 100 step_per_epoch (گام در هر دوره) داشته باشیم تا همه 2000 تصویر آموزشی و 50 مورد برای تصاویر اعتبارسنجی را پوشش دهیم.پارامتر epochs تعداد تکرارهایی را که برای آموزش انجام می دهیم را تعیین می کند.باقی پارامتر ها پیشرفت در هر تکرار را در حین آموزش نشان می دهد.نتیجه 15 گام (epochs) پس از 15 دوره، مدل 98.9% دقت را در مجموعه آموزشی و 71.5% دقت را در مجموعه اعتبار سنجی کسب کرده است.این نشانه واضحی است که مدل ما بیش از حد برازش (overfit) شده است و این یعنی مدل ما در داده های مجموعه آموزشی (train data) بسیار خوب عمل خواهد کرد و برای داده های دیده نشده عملکرد ضعیفی خواهد داشت.برای حل مشکل اضافه برازش (overfitting)، هم می‌توانیم منظم‌سازی (regularization) اضافه کنیم تا از پیچیده‌شدن بیش از حد (over-complexing) مدل جلوگیری کنیم، هم می‌توانیم داده‌های بیشتری را به مجموعه آموزشی اضافه کنیم تا مدل برای داده‌های دیده نشده بهینه تر شود.از آنجایی که ما یک مجموعه داده بسیار کوچک (2000 تصویر) برای آموزش داریم، اضافه کردن داده های بیشتر باید مشکل را برطرف کند.جمع‌آوری داده‌های بیشتر برای آموزش یک مدل در یادگیری ماشین بسیار دشوار است، زیرا برای این کار پیش‌پردازش مجدد داده‌ها لازم است.اما هنگام کار با تصاویر، به خصوص در طبقه بندی تصاویر، نیازی به جمع آوری اطلاعات بیشتر نیست. این مشکل را می توان با تکنیکی به نام تقویت تصویر (Image Augmentation) برطرف نمود.تقویت تصویر (Image Augmentation):ایده تقویت تصویر ایجاد تصاویر بیشتر با تغییر اندازه، بزرگنمایی، چرخش تصاویر و غیره برای ساخت تصاویر جدید است.با این رویکرد، مدل می‌تواند ویژگی‌های بیشتری را نسبت به قبل ثبت کند و به خوبی برای داده‌های دیده نشده بهینه شود.به عنوان مثال، بیایید اکثر گربه های موجود در مجموعه آموزشی خود را به صورت زیر فرض کنیم که بدن کامل یک گربه را دارند. مدل سعی خواهد کرد شکل بدن گربه را از این تصاویر یاد بگیرد.به همین دلیل، طبقه‌بندی‌کننده ممکن است نتواند تصاویری مانند تصویر زیر را به درستی شناسایی کند، زیرا با نمونه‌های مشابه آن آموزش ندیده است.اما با تقویت تصویر، می‌توانیم تصاویر جدیدی از تصاویر موجود بسازیم تا طبقه‌بندی کننده ویژگی‌های جدیدی را یاد بگیرد.با قابلیت بزرگنمایی در تقویت تصویر، می‌توانیم یک تصویر جدید مانند زیر بسازیم تا به یادگیرنده کمک کنیم تصاویری مانند بالا را که قبلاً به درستی طبقه‌بندی نشده‌اند طبقه‌بندی کند.تصویر زوم شده از تصویر اصلی با تقویت تصویر افزودن تصویر تقویتی با مولد تصویر (image generator) در تنسورفلو بسیار آسان است.هنگامی که تقویت تصویر اعمال می شود، مجموعه داده اصلی دست نخورده باقی می ماند و تمام دستکاری ها در حافظه انجام می شود.تکه کد زیر نحوه افزودن این قابلیت را نشان می دهد.افزودن تقویت تصویر هنگام بارگیری داده ها در اینجا چرخش تصویر، جابجایی، بزرگنمایی و چند تکنیک دستکاری تصویر دیگر برای تولید نمونه های جدید در مجموعه آموزشی اعمال می شود.هنگامی که ما تقویت تصویر را اعمال می کنیم، می توان 86٪ دقت آموزشی و 81٪ دقت تست را به دست آورد.همانطور که می بینید این مدل مانند قبل اضافه برازش (overfit) نشده است و با مجموعه داده بسیار کوچکی مانند این، این دقت چشمگیر است.علاوه بر این، می‌توانید دقت را با بازی با فراپارامترهایی مانند بهینه‌ساز، تعداد لایه‌های متراکم، تعداد نورون‌ها در هر لایه و غیره افزایش دهید.پایانلینک مقاله اصلی</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Sat, 13 Nov 2021 15:38:10 +0330</pubDate>
            </item>
                    <item>
                <title>ساخت ابر کلمه ای برای توییت های ترامپ یا هر شخص دیگر</title>
                <link>https://virgool.io/@hootan09/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%A7%D8%A8%D8%B1-%DA%A9%D9%84%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%AA%D9%88%DB%8C%DB%8C%D8%AA-%D9%87%D8%A7%DB%8C-%D8%AA%D8%B1%D8%A7%D9%85%D9%BE-%DB%8C%D8%A7-%D9%87%D8%B1-%D8%B4%D8%AE%D8%B5-%D8%AF%DB%8C%DA%AF%D8%B1-ndslc1bnh6bg</link>
                <description>سلام. توی این نوشته قصد داریم که یک ابر کلمه ای (wordcloud) برای توییت های دونالد ترامپ رئیس جمهور پیشین آمریکا درست کنیم تا دریابیم از چه کلماتی بیشتر استفاده کرده است.حساب POTUS (یک حساب کاربری توییتری مخفف President of the United States) این اواخر توییت های زیادی کرده بود ، دقیقا زمانی که این حساب کاربری دست دونالد ترامپ بود.شبکه های اجتماعی خصوصا توییتر اصطلاحا نون و کره شده بود برایش، یعنی از طریق این کانال رسمی ، پیام های خودش رو بدون دردسر برای حامیان خودش ارسال میکرد.اما من کنجکاو هستم که بدونم بیشتر چه کلماتی رو در توییت های خودش استفاده میکرده است، چطور این کلمات بخصوص رو انتخاب میکرده تا پیام خودش رو قدرتمند و رسا به دیگران برساند.بیاید با هم بفهمیم.ایمپورت کتابخانه های پایتونی:اول باید کتابخانه های مورد نیاز خودمون رو ایمپورت کنیم تا بتونیم با استفاده از اون ها ابر کلمه ای رو تولید کنیم.این پکیج ها شامل کتابخانه های پایه ای مثل numpy , pandas و matplotlib و همچنین wordcloud می باشد.(نگران نباشید سورس کد رو در انتها قرار میدم)import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
from PIL import Image
from matplotlib.colors import LinearSegmentedColormapمنبع داده های توییتی مورد نیاز:داده هایی که من در این آموزش استفاده میکنم یک مجموعه داده (دیتاست) خام از توییت های ترامپ هست که میتونید از اینجا دانلودش کنید.این توییت ها شامل بازه زمانی جولای 2020 تا اکتبر 2020 میباشد که به ما اجازه میده توییت های انتخاباتی 2020 و کمپین انتخاباتی ش رو دنبال کنیم.این داده ها به صورت یک فایل csv (مخفف Comma-separated values) میباشد و اون رو با استفاده از کتابخانه pandas به یک دیتافریم تبدیل میکنیم.df = pd.read_csv(&#039;./content/trump_tweets.csv&#039;)بیاید با هم ببینیم که چی داریم:df.head()به نظر میرسه که داده مورد علاقه ما ستون &quot;text&quot; هستش که شامل توییت ها میشه.شما میتونید باقی ستون ها رو دوربریزید اما من در دیتافریم نگه خواهم داشت ولی در ابرکلمه ای استفاده نخواهد شد.بررسی داده ای:اکنون بررسی میکنیم که ستون text مقادیرnull توی خودش داره یا نه.df.isnull().sum()همانطور که میبینید (عکس بالا) ستون hashtags مقدار null داره اما مشکلی نیست چون ما استفاده ای از این ستون نداریم.مهم اینه که ستون text مقدار null نداشته باشه.حالا تمام توییت ها رو در قالب یک رشته string به هم وصل میکنیم تا به صورت خوراک به توابع wordcloud به عنوان ورودی بدیم.tweets = &amp;quot &amp;quot.join(line for line in df[&#039;text&#039;])ایمپورت تصویر ماسک به عنوان عکس نمونه:در قدم بعدی ما تصویر ماسک رو به عنوان تصویر نمونه برای ساخت ابر کلمه ای ایمپورت میکنیم.میتونید ببینید که این ماسک به صور شکل سر دونالدترامپ هست.با استفاده از تابع open عکس رو وارد برنامه میکنیم و سپس با استفاده از numpy اونو به صورت ارایه ای از پیکسل ها در میاریمmask = np.array(Image.open(&amp;quot./content/mask.png&amp;quot))ساخت ابر کلمه ای:در نهایت ما میتونیم ابر کلمه ای خودمون رو بسازیم.توی این ابر کلمه ای ما از رنگ های ابی و قرمز به عنوان رنگ های اصلی استفاده خواهیم کرد و همچنین رنگ پس زمینه سفید خواهد بود(این ترکیب رنگ پرچم آمریکا هست)اول یک لیست بارنگ های ابی و قرمز درست میکنیمcolors = [&amp;quot#BF0A30&amp;quot, &amp;quot#002868&amp;quot]سپس این رنگ ها رو به عنوان ورودی به LinearSegmentedColormap از پیکیج matplotlib میدیم تا نقشه رنگ مون (کالر مپ) آماده بشه.cmap = LinearSegmentedColormap.from_list(&amp;quotmycmap&amp;quot, colors)یک نکته : چیزی به اسم stopwords داریم که معادل فارسی اون همون کلمات اضافه یا چیزی شبیه به این هست و هدف اینه که این کلمات توی ساخت ابر کلمه ای مون لحاظ نشه.stop_words = set(STOPWORDS)
stop_words.update([&amp;quothttps&amp;quot,&amp;quott&amp;quot,&amp;quots&amp;quot,&amp;quotwill&amp;quot,&amp;quotnow&amp;quot])سپس چیزهایی که تاکنون ساختیم شامل رشته ی توییت ها (متغیر tweets) و رنگ ها و ماسک و استاپ ورد رو به عنوان ورودی به تابع wordcloud میدیم تا تصویر رو برامون بسازه.wc = WordCloud(width = 600, height = 400, random_state=1, background_color=&#039;white&#039;, colormap=cmap , mask=mask, collocations=True, stopwords = stop_words).generate(tweets)در پایان با استفاده از matplotlib عکسمون رو نمایش میدیم.plt.figure(figsize=[15,15])
plt.imshow(wc, interpolation=&amp;quotbilinear&amp;quot)
plt.axis(&#039;off&#039;)
plt.show()تحلیل ابر کلمه ای:همان طور که میبینید کلمات بیشتر استفاده شده شامل &quot;Biden&quot; , &quot;MAGA&quot; , &quot;Fake News&quot; و همینطور &quot;Pennsylvania&quot; در ایالت مورد مناقشه انتخاباتی میشهنتیجه گیری:کتابخانه wordcloud یک کتابخانه مفید جهت داده کاوی و انالیز و پردازش زبان طبیعی (NLP) میباشد.میشه به صورت خلاقانه ای از این کتابخانه برای معرفی کردن خودتون در جمع مخاطبین استفاده نمود.پایانلینک مقاله اصلیسورس کد</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Sat, 06 Nov 2021 20:21:39 +0330</pubDate>
            </item>
                    <item>
                <title>برنامه ها و اپلیکیشن های  پایتون خودتون رو سریع تر اجرا کنید</title>
                <link>https://virgool.io/CodeLovers/%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%87%D8%A7-%D9%88-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C--%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-%D8%AE%D9%88%D8%AF%D8%AA%D9%88%D9%86-%D8%B1%D9%88-%D8%B3%D8%B1%DB%8C%D8%B9-%D8%AA%D8%B1-%D8%A7%D8%AC%D8%B1%D8%A7-%DA%A9%D9%86%DB%8C%D8%AF-d7kbdq9peh29</link>
                <description>اجرای برنامه های پایتون روی سخت افزار های به نسبت ضعیف تر از نظر منابع پردازشی مثل Raspberry pi می تونه مقداری کندتر از pc های معمولی و سرور اجرا بشه.خوشبختانه راه حل هایی برای این مشکل وجود داره که میتونه روند اجرا رو بهبود ببخشه.در پایتون پکیج ها و برنامه های جالبی وجود داره که به ما امکان کامپایل کد و یا حتی repackage کردن اون و یا اجرای خط به خط (مفسر) سریع تر برنامه رو به ما میده.توی این نوشته علاقمندم که دوتا از این مدل پکیج ها رو که خودم از کار باهاشون راضی بودم معرفی کنم.Pypy :یک جایگزین برای مفسر بومی پایتون هست که به شما امکان اجرای 4 برابر سریع تر کد رو میده!Nuitka :یک ابزار سومند بومی پایتون که کد های پایتون مارو به زبان C کامپایل میکنه!پایتون سریعتر با Pypy:سایت رسمی Pypy چندین نتیجه از بررسی پرفورمنس و کارایی خودش رو منتشر کرده که میانگین اونها نشون میده کدها نسبت به نسخه بومی پایتون 4.2 برابر سریع تر اجرا میشه.در اجرای تستی من Pypy حدود 9 برابر سریع تر از پایتون اصلی و بومی اجرا شد.در حقیقت عملکرد بهتر Pypy به دلیل کامپایلر به موقع آن (چه ترجمه بدی!!) یا همون JITjust-in-time compilerهستش و در مقایسه با مفسر خط به خطی پایتون بومی بهتر عمل میکنه.حتی یک نقل قول از Guido van Rossum یا همون خالق اصلی پایتون وجود داره که میگه:“If you want your code to run faster, you should probably just use PyPy.”برای پلتفرم ویندوز و لینوکس و مک دونسخه Pypy وجود داره یکی نسخه 2.7 و دیگری 3.6برای نصب Pypy 3 روی اوبونتو کافیه فرمان های زیر رو اجرا کنید:$ sudo add-apt-repository ppa:pypy/ppa$ sudo apt update$ sudo apt install pypy3نسخه Pypy 3 همچنین از طریق snap هم قابل نصبه (این روش ممکنه راحت ترین راه برای نصب روی Raspberry pi ) باشه.## if you need to install snap 
$ sudo apt install snapd 
## reboot after snap is installed 
$ sudo snap install pypy3 --classicنکته خوب راجع به Pypy  اینه که شما میتونید کدهای خودتون رو همونطور که هست اجرا کنید.مثلا اجرای ساده اون برای کدها تون به این شکله:$ pypy3 myapp.pyاگه شما برنامه هاتون رو به شکل یک اسکریپت در لینوکس یا مک اجرا میکنید به عنوان مثال دستور chmod +x myappبه کدتون دادید ، حتما به یاد داشته باشید که خط اول کدرو از #!/usr/bin/pythonبه#!/usr/bin/pypy3تغییر بدید.کتابخانه های Pypy  و محدودیت های آن :کتابخانه های بسیار خوب و عالی ای برای Pypy موجود هست با این حال به این نکته توجه داشته باشید که تمامی کتابخانه های نسخه بومی پایتون ممکنه توسط Pypy پشتیبانی نشود.قبل از اجرا کدهایتان با Pypy حتما بررسی کنید که کتابخانه های مورد استفاده توسط Pypy پشتیبانی شده باشند.متاسفانه TKinter جزء کتابخانه های مورد پشتیبانی Pypy نمی باشد.به منظور نصب کتابخانه های پایتون در Pypy3 یک نسخه از pip جهت نصب نیاز است.$ wget https://bootstrap.pypa.io/get-pip.py
$ pypy3 get-pip.pyبعد از نصب pip ، پکیج های پایتون قابل نصب هستند.$ pypy3 -m pip install some-pymoduleبعضی از پکیج های سبک نظیر bottle , requests, beautifulsoup4, pika و... بدون مشکل بر روی Pypy اجرا میشوند اما بعضی پکیج های سنگین تر نظیر Numpy, MatPlotLib ممکنه که مستقیم بارگذاری نشوند و بهتر هست که از virtualenv استفاده شود.کامپایلر پایتون Nuitka برای پایتون:کامپایلر پایتون Nuitka در پایتون نوشته شده است و می تواند تمامی کدهای نوشته در ورژن های مختلف پایتون را کامپایل کند. Nuitka به دو پیشنیاز نیازمند است.1- یک کامپایلر زبان C2- پایتون می بایست روی کامپیوتر هدف نیز نصب باشد.این کامپایلر روی تمامی سیستم های عامل نظیر ویندوز و لینوکس و مک کار میکند.برای سیستم عامل لینوکس کامپایلر GCC (5.1 به بالا) نیز می بایست نصب باشد.در سیستم عامل مک نیز کامپایلر clang  مورد نیاز است.در ویندوز نیز میتوان از MinGW64 و یا Visual Studio 2019 نیز جهت کامپایل استفاده نمود.جهت نصب Nuitka python -m pip install nuitkaجهت کامپایل یک کد تستی در پایتون خیلی ساده میتوانpython-m nuitka test1.pyپس از کامپایل در ویندوز یک فایل اجرای به نام test1.exe ایجاد میشود و همچنین در لینوکس نیز فایل اجرایی test1.bin ایجاد میگردد.تست بهینگی و پرفورمنس:بررسی کارایی و بهینگی کدهای کامپایل شده ، فاکتور های بسیاری را جهت بررسی نیاز دارد.جهت ایجاد یک استاندارد در نمایش کارایی و بنچ مارکینگ من از این کد استفاده نمودم و سخت افزار مورد استفاده نیز Raspberry pi 3 بود.من چندید نمونه از مفسر های موجود در پایتون را تست نمودم.- استفاده ازمفسر  Jython (Java VMS Python) ، حدودا 2 برابر کند تر از نسخه اصلی و بومی پایتون بود.- استفاده از ipython (بیشتر در jupyter notebooks استفاده میشود) تقریبا سرعتی برابر با نسخه اصلی و بومی پایتون داشت.- استفاده از pyinstall حدودا 33% از نسخه اصلی کند تر بود.- استفاده از Nuitka حدود 30% سریع تر از نسخه بومی و اصلی پایتون بود.استفاده از Pypy حدودا 9 برابر سریع تر از نسخه بومی و اصلی پایتون بود و حدود 6.5 برابر سریع تر از Nuitka بود.گزینه های دیگری نیز نظیر Cython هست که در این نوشته نیامده است. همچنین Nuitka تقریبا هیچ مشکلی با پکیج های سنگین ندارد و تقریبا با تمامی پکیج ها به خوبی کار میکند.منبع ترجمه</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Sat, 02 Jan 2021 17:11:07 +0330</pubDate>
            </item>
                    <item>
                <title>استفاده از AWK در شل اسکریپت نویسی لینوکس Bash Scripts</title>
                <link>https://virgool.io/@hootan09/%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-awk-%D8%AF%D8%B1-%D8%B4%D9%84-%D8%A7%D8%B3%DA%A9%D8%B1%DB%8C%D9%BE%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-bash-scripts-rdmu9xzhrmus</link>
                <description>در گذشته من از پایتون برای نوشتن اسکریپت های کوتاه و کاربردی استفاده میکردم با این حال بعد از مدتی من نیاز اینو پیدا کردم که برای پروژه هام از اسکریپت های کوچک Bash هم استفاده کنم.من فهمیدم که اضافه کردن مقداری از فرمان AWK در یک خط از اسکریپت Bash ، میتونه جایگزین چندین خط از کدهای پایتون بشه.کلمه AWK برگرفته از حرف اول نام نویسندگان این برنامه هست که به ترتیب نام اونها هست Alfred Aho, Peter Weinberger, and Brian Kernighanو این یک برنامه قدیمی (سال 1994) برای استخراج متن و گزارش گیری هست.نکته خوب راجع به AWK اینه که شما تنها با یاد گیری چند فرمان از اون میتونید استفاده های مناسب و کاربردی ای ازش بکنید.استفاده از Bash/AWK در یک مثال:من در حال کار روی یک سیستم هوشمندسازی منازل با استفاده از سخت افزار Raspberry pi بودم.روی این کامپیوتر کوچک مقدار زیادی کامپوننت های افزودنی اضافه کرده بودم و نگران این بودم که ممکنه این کار باعث سربار (overload) مقدار زیادی محاسبات پردازشی روی سیستم بشه و میخواستم که بتونم مقدار idle time در cpu رو در هر لحظه اندازه گیری و مانیتور کنم.این سیستم دستیار خانگی قابلیت دریافت فرمان در قالب command line رو دارا هست ، پس کافیه که idle time رو با استفاده از فرمان iostat در لینوکس دریافت کنم.مقدار idle time در خط چهارم و آیتم ششم خروجی این فرمان قابل دیدن هست.با استفاده از AWK من میتونم که فقط idle time رو بگیرم (با استفاده از فرمان زیر)~$ iostat | awk &#039;{if (NR==4) print $6}&#039;
96.92منطق و بررسی شروط مورد استفاده در فرمان AWK می بایست داخل براکت {} و سینگل کوت &#x27; قرار بگیرد .این منطق بما میگوید که اگر شماره خط (Number of Record ) یا همان NR برابر 4 بود ، مقدار آیتم ششم آن را چاپ کن.بعد از آنکه توانستم مقدار idle time رو بگیرم ،حالا میتونم این فرمان رو در قالب سنسور به سیستمم در مسیر (/config/configuration.yaml)اضافه کنم.این کار قابلیت مانیتور و باخبر کردن رو به من میده.sensor:

 platform: command_line 
 name: Idle Time
 command: &amp;quotiostat | awk &#039;NR==4&#039; | awk &#039;{print $6}&#039;&amp;quot
 unit_of_measurement: &amp;quot%&amp;quotبعضی از دستور های پر استفاده در AWKممکنه که یک نسخه پیشرفته از AWK یا GAWK (GNU AWK) قبلا در سیستم تون نصب باشه.برای نصب در Raspberry pi  میتونید از فرمان زیر استفاده کنید:sudo apt-get install gawkدر این لینک بعضی از بهترین مثال های AWK وجود داره که کاربردیه.گرفتن قسمتی از یک رشته substr(string,position,length)یک مثال برای استفاده از substr میتونه این باشه که دمای cpu رو بگیریم~$ sensors | grep CPU | awk &#039;{print substr($2,2,4)}&#039;
 44.0به تابع ()substr  گفتیم که آیتم دوم (+44.0°C) رو بما بده و از کاراکتر دوم شروع کن و چهار کاراکتر بعدش رو بما نمایش بده.چاپ ( ()print ) خروجی برحسب بررسی شرط ( ()if ) لازم در AWKدستور AWK میتونه از شرط ()if برای فیلتر گذاری مناسب در چاپ خروجی استفاده کنه.یک مثال در این رابطه میتونه فیلتر گذاری روی دستور ps باشه که یک دید کلی از وضعیت فعلی پراسس ها بما میده و ما میتونیم تنها خطوطی رو چاپ کنیم که زمانی برای نمایش داشته باشند (خطوط مورد نیاز ما)~$ # SHOW ALL PROCESSES
~$ ps -e 
   PID TTY          TIME CMD
     1 ?        00:00:03 systemd
     2 ?        00:00:00 kthreadd
     4 ?        00:00:00 kworker/0:0H
     6 ?        00:00:00 mm_percpu_wq
     7 ?        00:00:00 ksoftirqd/0
     8 ?        00:01:10 rcu_sched
...
~$  # SHOW ONLY PROCESSES WITH TIME
~$ ps -e | awk &#039;{if ($3 != &amp;quot00:00:00&amp;quot) print $0}&#039;
   PID TTY          TIME CMD
     1 ?        00:00:03 systemd
     8 ?        00:01:10 rcu_sched
    10 ?        00:00:06 migration/0
    15 ?        00:00:03 migration/1
...گرفتن زمان و فرمت بندی آن با ()strftime و ()systimeاین توابع زمانی به ما این امکان رو میده که time stamps رو در خروجی استفاده کنیم و زمان خودمون رو به دلخواه فرمت بندی کنیم.مثلا در مثال های قبل ما نحوه گرفتن دمای cpu را دیدیم ، حالا میتونیم تاریخ و زمان دریافت دما رو هم به خروجی اضافه کنیم.$ sensors | grep CPU | awk &#039;{print strftime(&amp;quot%H:%M:%S &amp;quot,systime()) $1 $2 }&#039;
 11:06:18 CPU:+45.0°Cسخن نهاییدر نهایت فهمیدیم که یادگیری و استفاده از AWK میتونه واقعا نتیجه خوبی رو بهمون بده.استفاده AWK میتونه توی اسکریپت نویسی خیلی کاربردی باشه اما اگه قراره که اسکریپت پیچیده بنویسیم بازهم پایتون بیشتر بدرد میخوره :)موفق باشید.</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Thu, 10 Dec 2020 15:55:04 +0330</pubDate>
            </item>
                    <item>
                <title>اتوماتیک سازی منازل با اپلیکیشن اندروید و ماژول بلوتوث</title>
                <link>https://virgool.io/@hootan09/%D8%A7%D8%AA%D9%88%D9%85%D8%A7%D8%AA%DB%8C%DA%A9-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%85%D9%86%D8%A7%D8%B2%D9%84-%D8%A8%D8%A7-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D9%88-%D9%85%D8%A7%DA%98%D9%88%D9%84-%D8%A8%D9%84%D9%88%D8%AA%D9%88%D8%AB-leebiqhnpubh</link>
                <description> تا حالا شده که بخواین خونتون رو اتوماتیک کنید ؟مثلا چراغ ها و بقیه وسایل خونه به وسیله گوشی هوشمندتون کنترل بشه ؟این  روزها مبحث IOT یا ترجمه شده اینترنت اشیاء که البته به غلط هم ترجمه شده خیلی همه گیر شده و خیلی از افراد و شرکت ها دنبال راهی برای پیاده سازی افکارشون در زمینه IOT هستند.امروز به شمااپلیکیشنی رو معرفی میکنم که بدون هیچچ پیش زمینه ای راجع به برنامه نویسی اندروید میشه ، ایده ها و افکارتون رو پیاده سازی بکنید.استفاده از این اپلیکیشن کاملا رایگان و قابل دانلوده و قابل توسعه نیز هست .به وسیله اپلیکیشن که عکسش رو بالا گذاشتم می تونیم 5 دستگاه مختلف رو به وسیله بلوتوث کنترل کنیم.پیاده سازیوسایل لازم :سخت افزار مورد نیاز:1- Arduino یا آردواینو clone شده و یا میکرو کنترلری که بوت لودر آردواینو روش ریخته شده باشه.برای خرید این برد میتونید اینجا رو ببینید.2- یک ماژول بلوتوث 5 ولت دارای قابلیت TTL -UART (نحوه اتصال) مثل HC-05 که توی بازار ایران خیلی راحت پیدا میشه . (نکته : اکثر ماژول های بلوتوث ولتاژ کاری 3.3 ولت تا 6 ولت رو قبول میکنند.)3- تعداد 5 عدد رله 5 ولت .(دقت کنید که حتما 5 ولت باشه)4- برد های کمکی و نمونه (Breadboard)5- تعدادی سیم جهت اتصال.نرم افزار مورد نیاز :محیط  Arduino IDE  که از سایت اصلیش قابل دریافت هستش.این برنامه چطوری کار میکنه ؟خب شما داخل منزلتون یا هر جای دیگه تعدادی دستگاه دارید که برای کنترل کردنشون اون ها رو باید وصل کنید به یکی از رله ها .وقتی که اپلیکیشن به ماژول بلوتوث وصل میشه ، فرمان خودشو از طریق بلوتوث به برد آردواینو می فرسته و برد آردواینو بسته به نوع فرمان دریافتی با رله ها تعامل داره و اون ها رو روشن یا خاموش میکنه .این نکته رو هم بگم که رله ها یک طرفشون به دستگاه مورد نظر شما وصل میشه و طرف دیگش به پین های دیجیتال (digital pin) برد آردواینو وصل هست . پیاده سازی اتصالات برای هوشمند سازی منازل به کمک آردواینو و بلوتوثشمای کلی اتصال وسایل به هم مانند شکل زیر هست : دقت داشته باشید که در شکل بالا پایه Tx ماژول بلوتوث به پایه Rx برد آردواینو (digital pin 0) متصل شده است و همینطور پایه RX ماژول بلوتوث به پایه TX برد آردواینو (digital pin 1) متصل است.همینطور پایه VCC (پایه مثبت) در ماژول بلوتوث به پایه 5 ولت آردواینو متصل است و GND یا پایه منفی این ماژول نیز به GND آردواینو متصل است. شکل بالا تصوری از پایه های رله 5 ولت را نشان می دهد. پایه های شماره 1 و 3 در حقیقت به برد آردواینو متصل می شود.پایه شماره 1 رله به یکی از digital pin های برد آردواینو و پایه شماره 3 به پایه منفی یا GND متصل می شود.پایه شماره 2 رله به برق 220 ولت شهری متصل می شود.(AC 220v) و در نهایت پایه شماره 4 رله به دستگاه مد نظر شما متصل میشود تا آن دستگاه را روشن و یا خاموش نماید.توجه:لطفا در انتخاب رله مناسب دقت کنید زیرا بسیار خطر ناک می باشد. همچنیناز مشورت مهندسین برق استفاده نمایید تا دچار سانحه برق گرفتگی و یا آتش سوزی نشوید. شکل کلی پیاده سازی برد :  در صورتی که می خواهید برد PCB خود را بسازید می توانید از شماتیک زیر نیز کمک بگیرید.برای این کار فقط کافیست که بوت لودر BootLoader مربوط به آردواینو را در میکرو کنترلر های ATmega 168/328 بریزید و دیگر نیازی به برد آردواینو نیز نخواهید داشت.  برنامه نویسی برد آردواینو جهت فرمان پذیری از ماژول بلوتوث ابتدا برنامه Arduino IDE را از لینک بالا دریافت کنید و سپس کد مورد نظر را در این محیط اجرا کنید . در صورتی که قبلا با این محیط آشنایی نداشته اید برای یادگیری آن به اینترنت مراجعه کنید. بارگذاری کد در برد های آردواینو بسیار راحت می باشد.به زودی آموزشی در این رابطه خواهیم داشت. /******************  Smartphone Controlled Home ******************//*Coder - Arvind Sanjeev*//*The following is the code required to be run on arduino to enable control of your home appliances from a smartphone, please install DIY SmartHome android application first on your android phone*//*This project requires 2 continous rotation servos connected to pins 9 and 10 of arduino*/ byte val;void setup(){  Serial.begin(115200);//Change the baud rate value depending on the default baud rate of your bluetooth module, for Bluesmirf-115200 and for JY-MCU-9600    pinMode(2, OUTPUT);//Light1 pin  pinMode(3, OUTPUT);//Light2 pin  pinMode(4, OUTPUT);//Light3 pin  pinMode(5, OUTPUT);//AC pin  pinMode(6, OUTPUT);//Door Lock}void loop(){ int a=0; if(Serial.available())  {    val=Serial.read();    Serial.println(int(val));//Display received value on Serial Monitorif(int(val)==49)//Turn Light1 ON   digitalWrite(2,HIGH); else if (int(val)==50)//Turn Light1 OFF         digitalWrite(2,LOW);if(int(val)==51)//Turn Light2 ON   digitalWrite(3,HIGH);    else if(int(val)==52)//Turn Light2 OFF      digitalWrite(3,LOW);      if(int(val)==53)//Turn Light3 ON   digitalWrite(4,HIGH); else if(int(val)==54)//Turn Light3 OFF       digitalWrite(4,LOW);       if(int(val)==55)//Turn AC ON   digitalWrite(5,HIGH);    else if(int(val)==56)//Turn AC OFF       digitalWrite(5,LOW);       if(int(val)==57)//Lock the DOOR   digitalWrite(6,HIGH);    else if(int(val)==48)//Unlock the DOOR       digitalWrite(6,LOW);}} کد بالا بسیار ساده و خوانا است و همچنین دارای کامنت جهت توضیح خطوط کد می باشد.این کد منتظر ورودی داده از ماژول بلوتوث می ماند و زمانی که داده ای دریافت می کند آن را با مقدار ASCII مورد نظر در شرط های if خود بررسی میکند و اگر شرط مورد نظر برقرار بود رله مربوطه را روشن می کند.این کار با دستور “(digitalWrite(pin,HIGH” انجام  می شود.و کمله HIGH در حقیقت ولتاژ 5 ولت را به رله می رساند.نکته: بسته به نوع ماژول بلوتوث شما ممکن است که برنامه درست کار نکند. علت آن هم Baud rate متفاوت این نوع ماژول ها است . برای رفع این اشکال مقدار 115200 را در خط کد Serial.begin(115200); به زیر تغییر دهید Serial.begin(9600); نکته : دقت داشته باشید که در هنگام آپلود این کد بر روی برد خود ماژول بلوتوث به برد متصل نباشد. و Tx و Rx را قطع کنید تا به ماژول بلوتوث آسیبی نرسد . پس از آپلود کد روی برد می توانید مجدد آنها را متصل کنید.دانلود اپلیکیشن اندروید و اتصال بلوتوث آن به برد آردواینو برای نصب اپلیکیشن این فایل apk. را دانلود و نصب نمایید.قبل از باز کردن برنامه شما نیاز خواهید داشت تا بلوتوث دستگاه هوشمند خود را با ماژول بلوتوث هنگام سازی (pair) کنید .برای این کار آردواینو را روشن کنید (به usb یا اداپتور و یا باتری وصل کنید) سپس بلوتوث دستگاه اندرویدی خود را روشن کرده و آن را برای بقیه دستگاه ها نمایان سازی کنید .(make it visible to other devices) سپس جستجوی دیگر دستگاه ها را بزنید . خواهید دید که ماژول بلوتوث در دستگاه شما نمایان می شود.کد هنگام سازی معمولا ‘1234’  و یا ‘0000’ می باشد.در این مثال نام ماژول باوتوث ما در دستگاه اندرویدی HC-06 می باشد . برنامه را باز کنید و در قسمت مربوطه این نام را بنویسید و دکمه OK را بزنید.اکنون دستگاه اندرویدی شما به برد متصل است و می توانید از آن استفاده کنید. نکات: 1- این نوشته هیچگونه مسئولیتی در قبال روش انجام این آموزش را نمی پذیرد. حتما قبل از انجام این کار با متخصص برق مشورت های لازم را انجام دهید.2- درصورتی که برنامه نتوانست تمامی رله ها را روشن کند بدانید که منبع تغذیه برد شما مناسب نیست و باید آمپر بیشتری به برد برسانید. پ.ن: باقی مطالب رو از www.nikitv.ir هم میتونید دنبال کنید. تمام شد.</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Mon, 06 Jul 2020 18:53:06 +0430</pubDate>
            </item>
                    <item>
                <title>تغییر اندازه و گسترش پارتیشن های لینوکس با فضای unallocated</title>
                <link>https://virgool.io/@hootan09/%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1-%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%D9%87-%D9%88-%DA%AF%D8%B3%D8%AA%D8%B1%D8%B4-%D9%BE%D8%A7%D8%B1%D8%AA%DB%8C%D8%B4%D9%86-%D9%87%D8%A7%DB%8C-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-%D8%A8%D8%A7-%D9%81%D8%B6%D8%A7%DB%8C-unallocated-lnux8ehhw1c0</link>
                <description>گاهی وقتا پیش میاد که میخواید فضای دیسک و ظرفیت پارتیشن خودتون رو توی لینوکس افزایش بدید.مثلا توی vmware یا virtual box یا حتی esxi ظرفیت اضافی به لینوکس خودتون دادید و لینوکس اون فضای اضافی رو unallocated تشخیص میده.شما نمیخواهید که پارتیشن جدید بسازید بلکه میخواید اون پارتیشنی که محدودیت فضا داره رو افزایش بدید.نکته:قبل از انجام این روش از داده های خود و یا سیستم عامل حتما بکاپ یا اسنپ شات بگیرید.چون یک حرکت اشتباه ممکنه باعث از دست رفتن داده هاتون بشه.این روش روی اکثر لینوکس ها از جمله CentOS, RHEL, Ubuntu, Debian و...  جواب میده.استفاده از فضای unallocated  روی لینوکس و تخصیص اون به یک پارتیشن خاص یکی از معضل های مدیران و ادمین های لینوکسی هست خصوصا که وقتی بحث فضای مجازی سازی (VM) مطرح باشه.سناریو زیر رو در نظر بگیرید.بازدن دستور cfdisk توی ترمینال لینوکسی میتونید تصویر بالا رو ببینید .(البته fdisk -l هم بهتون اطلاعات مشابهی رو میده )تصویر بالا داره میگه که:حجم 512 مگابایت برای بوت پارتیشن تون در نظر گرفته شده (sda1)حجم 6.8 گیگابایت اختصاص داده شده به سیستم عامل لینوکس و پکیج هاش (sda2)حجم حدود 100 گیگابایت فضای آزاد و تخصیص داده نشده داریم (unallocated)خیلی خوب میشد اگه این 100 گیکابایت رو به حافظه 6.8 گیگی خودمون اضافه کنیم ، در اینصورت سیستم عامل ما 106.8 گیگابایت فضا داشت.توی این آموزش ما همین کار رو خواهیم کرد.برای این آموزش ما از ابزار های خود لینوکس و بدون نصب ابزار خارجی (در centos ) استفاده خواهیم کرد.خود لینوکس ابزارهای خوبی از جمله fdisk , pvresize ,  lvdisplay , lvextend در اختیار ما گذاشته و این ابزار ها در اکثر توزیع ها مثل CentOS 5.x, CentOS 6.x, CentOS 7.x, RHEL, Ubuntu, Debian و... در دسترس هستند.1-تغییر جدول پارتیشن (partition table) :اول ما نیاز داریم که پارتیشن تیبل sda2 خودمون رو تغییر بدیم تا تمام ظرفیت موجود و خالی دیسک رو بگیره.نگران نباشید این در این مرحله داده های موجود در پارتیشن sda2 و کلا داده هاتون رو از دست نمیدید.در این مرحله ما نیاز داریم تا سیستم عامل مون رو reboot کنیم تا تغییرات لحاظ بشه و بعد از بالا آمدن تغییرات را مشاهده خواهیم کرد.بیایید شروع کنیم.اول دستور زیر رو بزنید تا وارد محیط برنامه fdisk بشیم:fdisk /dev/sda
بعد از زدن دستور بالا p را درکیبورد بفشارید تا پارتیشن تیبل موجود در sda را نمایش دهد.این نکته مهمه که مقدار عددی dev/sda2 در ستون های START و END رو یادداشت کنید .تا بعد از تغییرات مقایسه شون کنیم با مقدار جدیدی که گرفته اند.(اگه مقدارشون کمتر باشه یعنی توی مراحل بعد اشتباه کردید و مقداری داده از دست میره پس دقت کنید)سپس کلید d را بفشارید تا به حالت پاک کردن پارتیشن تیبل وارد بشید.توی سناریو ما عدد 2 همون پارتیشنی هست که قراره جدولش به روز بشه.بازم میگم انجام این دستور باعث از دست رفتن داده ما نمیشه.بلکه جدول map کننده ادرس ها از partition table پاک میشه و داده سرجاش هست.سپس n را بزنید تا یک پارتیشن جدید اضافه کنید.دقت کنید که مدل پارتیشن تون مثل قبلی باشه (توی این سناریو primary هست).سپس مقدار START فضای ادرس دهی را که از مرحله قبل یادداشت برداری کردید، وارد کنید(البته خود سیستم به صورت پیش فرض همون عدد رو باید پیشنهاد بده)همینطور دقت کنید که مقدار END هم پیشفرض خود سیستم باشه.در پایان این مرحله ما نیاز داریم که نوع پارتیشن رو از Linux به Linux LVM تغییر بدیم.برای این کار کلید t را بفشارید تا وارد partition mode شوید عدد 2 را بزنید تا پارتیشن مورد نظر ما (برای افزایش حجم) انتخاب شود.سپس مقدار 8e (عدد مربوط به Linux LVM) را وارد کنید تا این نوع پارتیشن انتخاب شود.اکنون میتوانید p را بزنید تا ببینید که مقدار ستون START دقیقا مثل قبل است و مقدار ستون END تغییر کرده و افزایش حجم داشته ایم.نکته:اگر اشتباهی رخ داد با زدن کلید d میتوانیم تغییرات را پاک کرده و از اول ادامه دهیم.تازمانی که کلید w را نزنیم &quot;هیچ&quot; تغییری در سیستم ما انجام نخواهد شد.(خیالتون راحت)اگه همه چی اوکی بود و درست پیش رفتید میتونید کلید w رو بزنید تا تغییرات لحاظ بشه.2- سیستم خود را راه اندازی مجدد کنید (Reboot)درست بعد از این تغییرات پیامی از سیستم مشاهده خواهید کرد که میگه: the partition table couldn’t be accessedو نمیتونه داده ازش بخونه.سیستم رو ری استارت کنید.3- توسعه دادن پارتیشن تغییر داده شده (LVM Partition)صبر کنید سیستم بالا بیاد و سپس دستور زیر را بزنید.pvresize /dev/sda2
سپس فرمان cfdisk را بزنید اگه همه چی درست باشه باید مثل تصویر زیر رو ببینید.تصویر بالا نشون میده که حافظه sda2 به حجم 106.8 گیگابایتی گسترش پیدا کرده.ری استارت (reboot) دوم لازمه تا تغییرات سرویس ها روی حافظه جدید اعمال بشه.4- گسترش مقدار حجم منطقی پارتیشن (Logical Volume)مورد بعدی اینه که ما باید بدونیم که این حجم (فضا) منطقی رو به کجا توسعه بدیم برای این منظور باید مسیرش رو بدونیم .با استفاده از فرمان زیر میتونیم مسیر رو پیدا کنیم.lvdisplay -v
معمولا باید اولین گزینه از لیست خروجی فرمان بالا باشه.با این حال با نگاه کردن به حجم شون میشه مسیر رو پیدا کرد .توی مثال ما /dev/vg/lv_root هست.حالا که مسیر رو داریم با فرمان زیر میتونیم حجم رو افزایش بدیم.lvextend -l +100%FREE /dev/vg/lv_root
5-افزایش فایل سیستم (Extend the File System)حالا که تمام فضای unallocated رو به logical volume خود اضافه کردیم نیاز داریم که مقادیر file system ما با logical volume یکی بشه و هر دو یک مقدار رو نشون بدند.با استفاده از دستور ساده زیر میتونید این کار رو بکنید.البته بستگی داره که شما از EXT4 (این روزا اکثرا همه از همین استفاده میکنند) یا XFS یکی از دو فرمان زیر رو بزنید.برای EXT4 فرمان:resize2fs /dev/vg/lv_root
برای XFS فرمان:xfs_growfs /root
بزنید.نکته برای فهمیدن اینکه کدام یک از EXT4 یا XFS رو انتخاب کنید از دستور زیر استفاده کنید تا اطلاعات کافی رو بدست بیارید. df -Thامیدوارم که این روش بدردتون خورده باشه.منبع: https://www.ryadel.com/en/resize-extend-disk-partition-unallocated-disk-space-linux-centos-rhel-ubuntu-debian/</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Fri, 30 Aug 2019 18:46:59 +0430</pubDate>
            </item>
                    <item>
                <title>دسترسی به سرور شخصی بدون داشتن رمز عبور و تنها با کلید عمومی</title>
                <link>https://virgool.io/@hootan09/%D8%AF%D8%B3%D8%AA%D8%B1%D8%B3%DB%8C-%D8%A8%D9%87-%D8%B3%D8%B1%D9%88%D8%B1-%D8%B4%D8%AE%D8%B5%DB%8C-%D8%A8%D8%AF%D9%88%D9%86-%D8%AF%D8%A7%D8%B4%D8%AA%D9%86-%D8%B1%D9%85%D8%B2-%D8%B9%D8%A8%D9%88%D8%B1-%D9%88-%D8%AA%D9%86%D9%87%D8%A7-%D8%A8%D8%A7-%DA%A9%D9%84%DB%8C%D8%AF-%D8%B9%D9%85%D9%88%D9%85%DB%8C-usavzukqjise</link>
                <description>توی این آموزش نحوه تولید کلید برای ssh را توضیح میدیم و اینکه چطور این کلید تولید شده را روی سرور شخصی قرار بدیم تا دیگه نیاز نباشه هربار موقع login پسورد رو وارد کنیم.اگه شما هم مثل من دسترسی به سرور های شخصی و شرکت براتون معضل هست و هر بار باید پسورد های هر سرور رو یادداشت و وارد کنید این آموزش بدردتون خواهد خورد.روال کار به این صورت هست که ما یک کلید عمومی روی کامپیوتر شخصی مون درست میکنیم و اون کلید رو روی سرور کپی میکنیم (دفعه اول با پسورد) بعد از اون دیگه نیازی به وارد کردن پسورد از سمت ما نیست و خود کلید کپی شده وظیفه اعتبار سنجی رو برای ما انجام میده.نکته:به روش استفاده از کلید جهت متصل شدن به ssh رو public key authentication میگیم.مرحله اول: تولید کلید در کامپیوتر شخصیبا استفاده از ابزار OpenSSH ما با فرمان ssh-keygen ، ابتدا کلید خود را می سازیم.فقط کافیه فرمان ssh-keygen رو توی ترمینال بنویسیم و به یک سری سوال پاسخ بدیم.مثال زیر نمونه ای از اجرای این فرمان است.در اینجا نام کلید ما mykey هست.# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ylo/.ssh/id_rsa): mykey
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in mykey.
Your public key has been saved in mykey.pub.
The key fingerprint is:
SHA256:GKW7yzA1J1qkr1Cr9MhUwAbHbF2NrIPEgZXeOUOz3Us ylo@klar
The key&#039;s randomart image is:
+---[RSA 2048]----+
|.*++ o.o.        |
|.+B + oo.        |
| +++ *+.         |
| .o.Oo.+E        |
|    ++B.S.       |
|   o * =.        |
|  + = o          |
| + = = .         |
|  + o o          |
+----[SHA256]-----+
#فرمان بالا برای ما یک جفت کلید میسازه (public key , private key)  این کلیدها معمولا در مسیر زیر ذخیره میشوند.~/.sshمرحله دوم: انقال کلید عمومی به سرور شخصیپس از ساختن کلید ، با فرمان ssh-copy-id لازم است تنها کلید عمومی را روی سرور قرار بدیم.برای این منظور فرمان زیر را مینویسیم .در اینجا نام کلید ما mykey هست.ssh-copy-id -i ~/.ssh/mykey user@host
حالا اگه توی ترمینال فقط فرمان زیر را بنویسیم ، به سرور خود بدون وارد کردن پسورد وارد خواهیم شد.ssh -i ~/.ssh/mykey user@host
نکته:به جای user نام کاربری سرور و بجای host ادرس سرور را وارد میکنیم.منبع: https://www.ssh.com/ssh/copy-id</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Fri, 30 Aug 2019 14:31:58 +0430</pubDate>
            </item>
                    <item>
                <title>پردازش تصویر ( سیستم تشخیص پلاک خودرو ) + پایتون</title>
                <link>https://virgool.io/dataio/%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%B4-%D8%AA%D8%B5%D9%88%DB%8C%D8%B1-%D8%B3%DB%8C%D8%B3%D8%AA%D9%85-%D8%AA%D8%B4%D8%AE%DB%8C%D8%B5-%D9%BE%D9%84%D8%A7%DA%A9-%D8%AE%D9%88%D8%AF%D8%B1%D9%88-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-r9yb0mokblpy</link>
                <description>پردازش تصویر این روزا علاقه مند های زیادی رو به خودش جلب کرده ، خصوصا اگه بشه که از این مبحث توی زندگی واقعی بهره برد .قبل تر من یک پایان نامه در رابطه با پردازش تصویر و تصاویر ویدیویی نوشتم و اون رو به رایگان توی نت منتشر کردم . توی اون پایان نامه زبان مورد نظر زبان شیرین سی شارپ بود . اما آدما کم کم که تجربشون بالا میره می فهمند که برای اهداف خاص برنامه نویسی از چه زبان برنامه نویسی استفاده کنند .پایتون یه شاهکار مهندسی هستش که توی پردازش تصاویر و کلا پردازش محاسباتی با قدرت هرچه تمام ظاهر شده و در عین حال سادگی خودشو از دست نداده.توی این آموزش ما می خواهیم یکم در رابطه با سیستم تشخیص پلاک خودرو با کمک زبان پایتون صحبت کنیم .مفهوم ANPR چیست؟انگلیسی زبان ها ، استاد خلاصه سازی کلمات و جملات هستند و اصولا برای هر چیزی یک کلمه خلاصه شده دارند .در اینجا هم ANPR مخفف Automatic number plate recognition که ترجمش “تشخیص اتوماتیک شماره ی پلاک” میشه . برای اطلاعات بیشتر می تونید به ویکی پدیا مراجعه کنید و اطلاعات دقیق تر رو اونجا بخونید .حرف از خلاصه سازی و مخفف سازی جملات شد این نکته رو هم اشاره کنم که کلمه wc رو ما به عنوان دستشویی توی زبان انگلیسی بکار میبریم مخفف water closet هست که به عبارت قدیمی “دست به آب” خیلی خیلی نزدیک تر بوده . ترجمه wc میشه “گنجه/منبع آب” .این نکته برای سرگرمی بود .روش تشخیص پلاک می شه گفت که تعداد روش های شناسایی پلاک خیلی خیلی زیاده و هرکس اصولا بسته به هدفش یکی از این روش ها رو انتخاب میکنه .پیشنهاد می کنم که حتما اون لینک ویکی پدیا رو بخونید تا حساب کار دستتون بیاد.اما مرسوم ترین روش های پردازش تصویر ، classification یا طبقه بندی هستش .توی نت می تونید سرچ کنید و مطالب مربوط به اون رو دنبال کنید . اما من می خوام توی این آموزش یک مثال عملی و نسبتا ساده از سیستم تشخیص پلاک رو پیاده سازی کنم .برای همین وارد مباحث دانشگاهیش نمیشم . فقط برای کسایی که علاقه به جنبه علمیش هم دارند بگم که الگوریتم مورد استفاده ما KNN هستش .عکس زیر یکم علمی قضیه رو توضیح میده .(می تونید رو عکس کلیک راست کنید و توی یک تب جدید با کیفیت کامل عکس رو ببینید) نحوه کارکرد برنامه :این برنامه (مجموعه برنامه ) یک عکس پلاک رو به عنوان ورودی می گیره و کاراکتر و حروف روی پلاک رو به صورت تک تک تشخیص میده .سپس معادل کاراکتری هر حرف یا عدد رو توی برنامه جایگزین میکنه مثلا اگه عکس زیر رو بهش بدیم : خروجی برنامه میشه : DEF456این رو هم بگم که برنامه ابتدا “جدول عدد وحروف پایه” رو به عنوان یک اصل از ما میگیره و سپس کاراکتر های تشخیصی خودش رو با اون “جدول عدد وحروف پایه” مقایسه میکنه . توی پیاده سازی ، کامل تر توضیح میدم .پیاده سازی برنامه :کد زیر (زیر عکس) به منظور تولید فایل های مربوط به طبقه بندی classification ، یک عکس رو به عنوان ورودی از ما میگیره .مثلا ما عکس زیر رو می دیم به برنامه و اون دو تا فایل طبقه بندی شده که حاصل پردازش این عکس هست به ما میده . اون دوتا فایل تولیدی از عکس بالا به ترتیب به نام های classifications.txt و flattened_images.txt هستند .  # GenData.py

import sys
import numpy as np
import cv2
import os

# module level variables ##########################################################################
MIN_CONTOUR_AREA = 100

RESIZED_IMAGE_WIDTH = 20
RESIZED_IMAGE_HEIGHT = 30

###################################################################################################
def main():
    imgTrainingNumbers = cv2.imread(&quot;training_chars.png&quot;)            # read in training numbers image

    if imgTrainingNumbers is None:                          # if image was not read successfully
        print &quot;error: image not read from file \n\n&quot;        # print error message to std out
        os.system&#40;&quot;pause&quot;&#41;                                  # pause so user can see error message
        return                                              # and exit function (which exits program)
    # end if

    imgGray = cv2.cvtColor(imgTrainingNumbers, cv2.COLOR_BGR2GRAY)          # get grayscale image
    imgBlurred = cv2.GaussianBlur(imgGray, (5,5), 0)                        # blur

                                                        # filter image from grayscale to black and white
    imgThresh = cv2.adaptiveThreshold(imgBlurred,                           # input image
                                      255,                                  # make pixels that pass the threshold full white
                                      cv2.ADAPTIVE_THRESH_GAUSSIAN_C,       # use gaussian rather than mean, seems to give better results
                                      cv2.THRESH_BINARY_INV,                # invert so foreground will be white, background will be black
                                      11,                                   # size of a pixel neighborhood used to calculate threshold value
                                      2)                                    # constant subtracted from the mean or weighted mean

    cv2.imshow(&quot;imgThresh&quot;, imgThresh)      # show threshold image for reference

    imgThreshCopy = imgThresh.copy()        # make a copy of the thresh image, this in necessary b/c findContours modifies the image

    imgContours, npaContours, npaHierarchy = cv2.findContours(imgThreshCopy,        # input image, make sure to use a copy since the function will modify this image in the course of finding contours
                                                 cv2.RETR_EXTERNAL,                 # retrieve the outermost contours only
                                                 cv2.CHAIN_APPROX_SIMPLE)           # compress horizontal, vertical, and diagonal segments and leave only their end points

                                # declare empty numpy array, we will use this to write to file later
                                # zero rows, enough cols to hold all image data
    npaFlattenedImages =  np.empty((0, RESIZED_IMAGE_WIDTH * RESIZED_IMAGE_HEIGHT))

    intClassifications = []         # declare empty classifications list, this will be our list of how we are classifying our chars from user input, we will write to file at the end

                                    # possible chars we are interested in are digits 0 through 9, put these in list intValidChars
    intValidChars = [ord(&#039;0&#039;), ord(&#039;1&#039;), ord(&#039;2&#039;), ord(&#039;3&#039;), ord(&#039;4&#039;), ord(&#039;5&#039;), ord(&#039;6&#039;), ord(&#039;7&#039;), ord(&#039;8&#039;), ord(&#039;9&#039;),
                     ord(&#039;A&#039;), ord(&#039;B&#039;), ord(&#039;C&#039;), ord(&#039;D&#039;), ord(&#039;E&#039;), ord(&#039;F&#039;), ord(&#039;G&#039;), ord(&#039;H&#039;), ord(&#039;I&#039;), ord(&#039;J&#039;),
                     ord(&#039;K&#039;), ord(&#039;L&#039;), ord(&#039;M&#039;), ord(&#039;N&#039;), ord(&#039;O&#039;), ord(&#039;P&#039;), ord(&#039;Q&#039;), ord(&#039;R&#039;), ord(&#039;S&#039;), ord(&#039;T&#039;),
                     ord(&#039;U&#039;), ord(&#039;V&#039;), ord(&#039;W&#039;), ord(&#039;X&#039;), ord(&#039;Y&#039;), ord(&#039;Z&#039;)]

    for npaContour in npaContours:                          # for each contour
        if cv2.contourArea(npaContour) &gt; MIN_CONTOUR_AREA:          # if contour is big enough to consider
            [intX, intY, intW, intH] = cv2.boundingRect(npaContour)         # get and break out bounding rect

                                                # draw rectangle around each contour as we ask user for input
            cv2.rectangle(imgTrainingNumbers,           # draw rectangle on original training image
                          (intX, intY),                 # upper left corner
                          (intX+intW,intY+intH),        # lower right corner
                          (0, 0, 255),                  # red
                          2)                            # thickness

            imgROI = imgThresh[intY:intY+intH, intX:intX+intW]                                  # crop char out of threshold image
            imgROIResized = cv2.resize(imgROI, (RESIZED_IMAGE_WIDTH, RESIZED_IMAGE_HEIGHT))     # resize image, this will be more consistent for recognition and storage

            cv2.imshow(&quot;imgROI&quot;, imgROI)                    # show cropped out char for reference
            cv2.imshow(&quot;imgROIResized&quot;, imgROIResized)      # show resized image for reference
            cv2.imshow(&quot;training_numbers.png&quot;, imgTrainingNumbers)      # show training numbers image, this will now have red rectangles drawn on it

            intChar = cv2.waitKey(0)                     # get key press

            if intChar == 27:                   # if esc key was pressed
                sys.exit()                      # exit program
            elif intChar in intValidChars:      # else if the char is in the list of chars we are looking for . . .

                intClassifications.append(intChar)                                                # append classification char to integer list of chars (we will convert to float later before writing to file)

                npaFlattenedImage = imgROIResized.reshape((1, RESIZED_IMAGE_WIDTH * RESIZED_IMAGE_HEIGHT))  # flatten image to 1d numpy array so we can write to file later
                npaFlattenedImages = np.append(npaFlattenedImages, npaFlattenedImage, 0)                    # add current flattened impage numpy array to list of flattened image numpy arrays
            # end if
        # end if
    # end for

    fltClassifications = np.array(intClassifications, np.float32)                   # convert classifications list of ints to numpy array of floats

    npaClassifications = fltClassifications.reshape((fltClassifications.size, 1))   # flatten numpy array of floats to 1d so we can write to file later

    print &quot;\n\ntraining complete !!\n&quot;

    np.savetxt(&quot;classifications.txt&quot;, npaClassifications)           # write flattened images to file
    np.savetxt(&quot;flattened_images.txt&quot;, npaFlattenedImages)          #

    cv2.destroyAllWindows()             # remove windows from memory

    return

###################################################################################################
if __name__ == &quot;__main__&quot;:
    main()
# end if



 این برنامه جدول حروف استاندارد خودشو میسازه که هر بار از طریق این جدول حروف پلاک رو تشخیص بده . باید بگم که این برنامه بالا هر حرف از جدول رو که تشخیص داد ، شما باید کلمه متناظر اون رو روی کیبورد فشار بدید .مثلا اگه روی حرف Z داخل عکس قرار گرفت شما باید z رو روی کیبورد فشار بدید.در نهایت وقتی کد اجراش تموم بشه سیستم تشخیص پلاک ما دیگه با عکس بالا کاری نداره و برای تشخیص به فایل های classifications.txt و flattened_images.txt رجوع میکنه .اما برنامه تشخیص پلاک :این کد پلاک رو از ما میگره و در نهایت به عنوان خروجی حروف و اعداد روی پلاک رو برای ما چاپ میکنه  # TrainAndTest.py

import cv2
import numpy as np
import operator
import os

# module level variables ##########################################################################
MIN_CONTOUR_AREA = 100

RESIZED_IMAGE_WIDTH = 20
RESIZED_IMAGE_HEIGHT = 30

###################################################################################################
class ContourWithData():

    # member variables ############################################################################
    npaContour = None           # contour
    boundingRect = None         # bounding rect for contour
    intRectX = 0                # bounding rect top left corner x location
    intRectY = 0                # bounding rect top left corner y location
    intRectWidth = 0            # bounding rect width
    intRectHeight = 0           # bounding rect height
    fltArea = 0.0               # area of contour

    def calculateRectTopLeftPointAndWidthAndHeight(self):               # calculate bounding rect info
        [intX, intY, intWidth, intHeight] = self.boundingRect
        self.intRectX = intX
        self.intRectY = intY
        self.intRectWidth = intWidth
        self.intRectHeight = intHeight

    def checkIfContourIsValid(self):                            # this is oversimplified, for a production grade program
        if self.fltArea &lt; MIN_CONTOUR_AREA: return False        # much better validity checking would be necessary
        return True

###################################################################################################
def main():
    allContoursWithData = []                # declare empty lists,
    validContoursWithData = []              # we will fill these shortly

    try:
        npaClassifications = np.loadtxt(&quot;classifications.txt&quot;, np.float32)                  # read in training classifications
    except:
        print &quot;error, unable to open classifications.txt, exiting program\n&quot;
        os.system&#40;&quot;pause&quot;&#41;
        return
    # end try

    try:
        npaFlattenedImages = np.loadtxt(&quot;flattened_images.txt&quot;, np.float32)                 # read in training images
    except:
        print &quot;error, unable to open flattened_images.txt, exiting program\n&quot;
        os.system&#40;&quot;pause&quot;&#41;
        return
    # end try

    npaClassifications = npaClassifications.reshape((npaClassifications.size, 1))       # reshape numpy array to 1d, necessary to pass to call to train

    kNearest = cv2.ml.KNearest_create()                   # instantiate KNN object

    kNearest.train(npaFlattenedImages, cv2.ml.ROW_SAMPLE, npaClassifications)

    imgTestingNumbers = cv2.imread(&quot;test1.png&quot;)          # read in testing numbers image

    if imgTestingNumbers is None:                           # if image was not read successfully
        print &quot;error: image not read from file \n\n&quot;        # print error message to std out
        os.system&#40;&quot;pause&quot;&#41;                                  # pause so user can see error message
        return                                              # and exit function (which exits program)
    # end if

    imgGray = cv2.cvtColor(imgTestingNumbers, cv2.COLOR_BGR2GRAY)       # get grayscale image
    imgBlurred = cv2.GaussianBlur(imgGray, (5,5), 0)                    # blur

                                                        # filter image from grayscale to black and white
    imgThresh = cv2.adaptiveThreshold(imgBlurred,                           # input image
                                      255,                                  # make pixels that pass the threshold full white
                                      cv2.ADAPTIVE_THRESH_GAUSSIAN_C,       # use gaussian rather than mean, seems to give better results
                                      cv2.THRESH_BINARY_INV,                # invert so foreground will be white, background will be black
                                      11,                                   # size of a pixel neighborhood used to calculate threshold value
                                      2)                                    # constant subtracted from the mean or weighted mean

    imgThreshCopy = imgThresh.copy()        # make a copy of the thresh image, this in necessary b/c findContours modifies the image

    imgContours, npaContours, npaHierarchy = cv2.findContours(imgThreshCopy,             # input image, make sure to use a copy since the function will modify this image in the course of finding contours
                                                 cv2.RETR_EXTERNAL,         # retrieve the outermost contours only
                                                 cv2.CHAIN_APPROX_SIMPLE)   # compress horizontal, vertical, and diagonal segments and leave only their end points

    for npaContour in npaContours:                             # for each contour
        contourWithData = ContourWithData()                                             # instantiate a contour with data object
        contourWithData.npaContour = npaContour                                         # assign contour to contour with data
        contourWithData.boundingRect = cv2.boundingRect(contourWithData.npaContour)     # get the bounding rect
        contourWithData.calculateRectTopLeftPointAndWidthAndHeight()                    # get bounding rect info
        contourWithData.fltArea = cv2.contourArea(contourWithData.npaContour)           # calculate the contour area
        allContoursWithData.append(contourWithData)                                     # add contour with data object to list of all contours with data
    # end for

    for contourWithData in allContoursWithData:                 # for all contours
        if contourWithData.checkIfContourIsValid():             # check if valid
            validContoursWithData.append(contourWithData)       # if so, append to valid contour list
        # end if
    # end for

    validContoursWithData.sort(key = operator.attrgetter(&quot;intRectX&quot;))         # sort contours from left to right

    strFinalString = &quot;&quot;         # declare final string, this will have the final number sequence by the end of the program

    for contourWithData in validContoursWithData:            # for each contour
                                                # draw a green rect around the current char
        cv2.rectangle(imgTestingNumbers,                                        # draw rectangle on original testing image
                      (contourWithData.intRectX, contourWithData.intRectY),     # upper left corner
                      (contourWithData.intRectX + contourWithData.intRectWidth, contourWithData.intRectY + contourWithData.intRectHeight),      # lower right corner
                      (0, 255, 0),              # green
                      2)                        # thickness

        imgROI = imgThresh[contourWithData.intRectY : contourWithData.intRectY + contourWithData.intRectHeight,     # crop char out of threshold image
                           contourWithData.intRectX : contourWithData.intRectX + contourWithData.intRectWidth]

        imgROIResized = cv2.resize(imgROI, (RESIZED_IMAGE_WIDTH, RESIZED_IMAGE_HEIGHT))             # resize image, this will be more consistent for recognition and storage

        npaROIResized = imgROIResized.reshape((1, RESIZED_IMAGE_WIDTH * RESIZED_IMAGE_HEIGHT))      # flatten image into 1d numpy array

        npaROIResized = np.float32(npaROIResized)       # convert from 1d numpy array of ints to 1d numpy array of floats

        retval, npaResults, neigh_resp, dists = kNearest.findNearest(npaROIResized, k = 1)     # call KNN function find_nearest

        strCurrentChar = str(chr(int(npaResults[0][0])))                                             # get character from results

        strFinalString = strFinalString + strCurrentChar            # append current char to full string
    # end for

    print &quot;\n&quot; + strFinalString + &quot;\n&quot;                  # show the full string

    cv2.imshow(&quot;imgTestingNumbers&quot;, imgTestingNumbers)      # show input image with green boxes drawn around found digits
    cv2.waitKey(0)                                          # wait for user key press

    cv2.destroyAllWindows()             # remove windows from memory

    return

###################################################################################################
if __name__ == &quot;__main__&quot;:
    main()
# end if نکته: برای اجرای این برنامه ها شما نیاز دارید که کتابخانه opencv 3 به مجموعه کتابخانه های پایتون تون اضافه بشه.برای نصب opencv ابتدا باید کل کتابخانه opencv رو دانلود کنید سپس توی فولدر های مربوط به کتابخانه هاش یه فایل هست به اسم cv2.pyd یا همچین چیزی .سپس این فایل رو کپی کنید و ببیرید توی شاخه ای که پایتون رو نصب کردید .مثلا برای من میشه :C:\Python27\Lib\site-packagesو فایل رو داخل این شاخه قرار بدید. همین .دانلود کامل سورس کد ها و تصاویر به صورت یکجا در اینجا یا از گیت هابدرپایان یک ویدیو هم از تمام این چیز هایی که گفتم براتون گذاشتم .ولی زبانش انگلیسیه . https://www.aparat.com/v/2L3K8 برای تشخیص پلاک های ایرانی با همین آموزش ، فقط کافیه کمی ابتکار بخرج بدید.  پ.ن: باقی مطالب رو از www.nikitv.ir هم میتونید دنبال کنید.   موفق باشید.</description>
                <category>Niki</category>
                <author>Niki</author>
                <pubDate>Mon, 20 May 2019 22:50:54 +0430</pubDate>
            </item>
            </channel>
</rss>