انجمن توسعه قرارداد هوشمند

حافظه‌ی (Storage) قرارداد های هوشمند اتریوم

حافظه‌ی (Storage) قرارداد های هوشمند اتریوم

قراردادهای هوشمند اتریوم و در واقع ماشین مجازی اتریوم (EVM) از یک ساختار غیرمعمول برای ذخیره سازی داده‌های دائمی در قراردادهای هوشمند استفاده می‌کند. در این پست به توضیح این مطلب که داده ها چگونه در حافظه‌ی Storage ذخیره می‌شوند می‌پردازیم.

یک آرایه‌ی بسیار بسیار بزرگ

هر قرارداد هوشمند در ماشین مجازی اتریوم یک حالت یا به اصطلاح State را در حافظه‌ی دائمی خود نگه می‌دارد. این حافظه‌ی Storage را می‌توان مانند یک آرایه‌ی بسیار بسیار بزرگ دید که در ابتدا تمامی خانه‌های این آرایه با صفر پر شده‌اند. هر خانه از این آرایه یک متغیر ۳۲ بایتی است. تعداد خانه‌های این آرایه 256**2 تاست. قرارداد هوشمند می‌تواند در این خانه‌ها بنویسد و یا از این خانه‌ها مقادیری را بخواند.
حافظه‌ی Storage شبیه به تصویر زیر است:

همانطور که گفته شد در ابتدا در تمامی این خانه‌ها مقدار صفر در نظر گرفته می‌شود. پس در واقع می‌توان این خانه ها را خالی گذاشت و در صورت نیاز اگر به خانه‌ای مقداری تخصیص داده شد آنگاه این مقدار ذخیره شود.

پس در هر قرارداد هوشمند این امکان وجود دارد که همه‌ی 256**2 خانه‌ها با مقادیر مختلف مقدار دهی شوند ولی در ابتدای امر و اگر هیچ داده‌ای در قرارداد هوشمند ذخیره نشده باشد این آرایه حجمی را هم اشغال نخواهد کرد و میزان حجمی که این آرایه در آینده اشغال کند به میزان داده‌ای که در آن ذخیره شود بستگی دارد.

در این آرایه از ساختار دوگانه‌ی کلید-مقدار (Key-Value pair) استفاده میشود که از ۳۲ بایت به ۳۲ بایت است. یعنی برای پیدا کردن یک مقدار ابتدا باید به شماره‌ی خانه ای که داده در آن است اشاره شود (یک مقدار ۳۲ بایتی مربوط به Key) و در آن خانه یک مقدار ۳۲ بایتی را برداشت کنیم (Value).

اما بگذارید به بررسی این بپردازیم که وقتی می‌خواهیم یک داده جدید در یک قرارداد هوشمند ذخیره کنیم ماشین مجازی اتریوم (EVM) چگونه این کار را انجام خواهد داد.

نحوه‌ی ذخیره سازی داده توسط EVM به نوع داده‌ای که می‌خواهیم ذخیره کنیم بستگی دارد. در این مرحله ۲ دسته ذخیره سازی داریم:

  • متغیرهای با طول ثابت
  • متغیر های با طول متغیر

متغیرهای با طول ثابت

در این حالت EVM از ابتدا و به ترتیبی که متغیر ها تعریف شده‌اند شروع به اختصاص خانه ها به متغیر‌ها می‌کند. بگذارید با یک مثال این قضیه را توضیح دهم:

فرض کنید قراردادی مانند قرارداد زیر داریم و می‌خواهیم ببینیم ‌EVM چگونه داده ها را ذخیره می‌کند.

contract StorageTest {
    uint256 a;
    uint256[2] b;

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;
}

در این کد:

  • متغیر a در خانه‌ی ۰ از Storage ذخیره می‌شود.
  • متغیر b در خانه‌ی ۱ و ۲ ذخیره می‌شود.
  • متغیر ‌‌c از خانه‌ی ۳ شروع می‌شود و دو خانه را به خود تخصیص می‌دهد به خاطر اینکه structure ای که تحت عنوان c معرفی شده از ۲ متغیر ۳۲ بایتی تشکیل شده است.

همانطور که متوجه شدید متغیرهای با طول ثابت بر اساس ترتیب تعریف شدن در کد به خانه‌های مختلف تخصیص می‌یابند.

متغیرهای با طول متغیر

متغیر های با طول متغیر به طور کلی به ۲ دسته تقسیم می‌شوند.

  • آرایه با طول متغیر
  • نگاشت (Mapping)

استفاده از خانه‌های حافظه, به ترتیب تعریف شدن, برای متغیر های با طول ثابت جوابگو است ولی برای متغیر های با طول متغیر چه باید کرد؟ اگر بخواهیم همانند متغیرهای با طول ثابت خانه‌ها را تخصیص دهیم به مشکل خواهیم خورد. زیرا EVM نمیداند چند خانه را باید به این متغیر اختصاص دهد و از کدام خانه شروع به تخصیص برای متغیرهای بعدی کند.

همانطور که گفته شد حافظه‌ی storage هر قرارداد هوشمند 256**2 خانه دارد. پس یک راهکار این است که برای هر متغیر با طول متغیر از یک خانه‌ی Random حافظه شروع کنیم. چون تعداد خانه‌های حافظه بسیار زیاد است احتمال اینکه این متغیر ها با هم تداخل داشته باشند بسیار کم است. البته اگر خانه هایی که اختصاص می‌دهیم به طور واقعی Random باشند و در نتیجه از هم فاصله‌ی معقولی داشته باشند.

اما ما به یک ساختار مشخص نیاز داریم برای این که این عددهای random از کجا می‌آیند و اینکه در حال حاضر این آرایه‌ی با طول متغیر چند خانه را اشغال کرده است. این مقدار Random نباید برای ما ناشناس باشد وگرنه برای دسترسی به آن به مشکل خواهیم خورد. EVM از تابع هش برای حل کردن این قضایا استفاده می‌کند. بگذارید جزئی تر بررسی کنیم که EVM چگونه این اعمال را انجام می‌دهد.

آرایه با طول متغیر

بگذارید به قراردادمان در مثال قبلی یک آرایه‌ی متغیر اضافه کنیم.

contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;
}

در قسمت قبل نشان دادیم که تا خانه‌ی چهارم از Storage برای ۳ متغیر a,b,c استفاده شد. اما متغیر d کجا و چگونه ذخیره می‌شود؟

برای ذخیره‌ی متغیر d, ماشین مجازی اتریوم (EVM), در خانه‌ی ۵ ام Storage طول آرایه‌ی d را ذخیره می‌کند. به طور مثال اگر در اولین بار تعریف این آرایه ۲ داده‌ ذخیره شده باشد, عدد ۲ را در این خانه ذخیره می‌کند و اگر ۱ داده دیگر به این آرایه اضافه کنیم,مقدار ذخیره شده در خانه‌ی ۵ام storage از ۲ به ۳ تغییر می‌کند.

حال سوال اینجاست که داده‌های مربوط به متغیر d کجای storage ذخیره می‌شوند؟

برای این کار EVM از شماره‌ی خانه‌ای که طول متغیر در آن ذخیره شده است هش می‌گیرد. یعنی در اینجا هش عدد ۵ را محاسبه می‌کند. عدد به دست آمده را به عنوان خانه‌ای در نظر می‌گیرد که از‌ آن به بعد داده های مربوط به d در آن ذخیره می‌شوند. مانند شکل زیر:

حال اگر EVM بخواهد به داده ی دوم از متغیر d دسترسی پیدا کند کافی است ابتدا به خانه‌ای برسد که طول این متغیر در آن ذخیره شده (خانه‌ی ۵). سپس هش این خانه را حساب کند (Hash عدد ۵) . این خانه اولین داده از d است و خانه‌ی بعدی داده‌ی دوم از آرایه‌ی d.

نگاشت یا Mapping

بگذارید به مثال قبل ۲ نگاشت e و f را اضافه کنیم:

contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;     // slot 5 for length, keccak256(5)+ for data

    mapping(uint256 => uint256) e;
    mapping(uint256 => uint256) f;
}

ماشین مجازی اتریوم (EVM) برای متغیر e خانه‌ی 6ام و برای متغیر f خانه‌ی ۷ام را ذخیره می‌کند. البته در این خانه‌ها چیزی ذخیره نمی‌کند چون mapping سایز ندارد که آن را ذخیره کند. اما داده‌های این ‌Mapping ها کجا ذخیره می‌شوند؟
برای ذخیره کردن, برای هر داده, EVM تابع هش را برای شماره ی خانه‌ی mapping به همراه مقدار کلید (Key) حساب می‌کند. عدد به دست آمده شماره‌‌ی خانه ای است که مقدار mapping یا همان value در آن ذخیره می‌شود.

ماشین مجازی EVM برای پیدا کردن مقدار (Value) مربوط به هر کلید (Key) ابتدا خانه‌ی اولیه را پیدا کرده (در اینجا 6) و سپس از آن در کنار کلید هش می‌گیرد تا شماره‌ی خانه‌ای که مقدار در آن ذخیره شده است را پیدا کند.


با تخلیص و تالیف از بلاگ programtheblockchain

4 Likes

مهدی جان مرسی از مقاله خوبت.

یک سوال٫ آیا memory در سالیدیتی هم به همین صورت کار می کنه؟ فرق memory و storage چیه؟

1 Likes

با اجازه من بپرم وسط بحثتون :grimacing:

تا جایی که میدونم وقتی کی وورد memory رو استفاده میکنیم برای یه متغیر اون فقط موقع اجرا شدن تابعی که توش تعریف شده زنده اس (اونم فقط تو نرم افزار نود ها دیگه)

Storage توی world state اتریوم نوشته میشه و فضا اشغال میکنه تو state اتریوم
اما متغیر هایی که با memory تعریف شدن بعد از تموم شدن اجرای تابع باعث اشغال شدن فضایی نمیشن(معمولا هم به عنوان متغیر های کمکی برای محاسبات و از این دست چیز ها استفاده میشن)

تو این نشست هم درباره موضوع انواع حافظه مرتبط با EVM حرف زده شده

سرفصل هایی که درباره اش بحث میکنن :

لینک نشست : https://youtu.be/RxL_1AfV7N4