مرکل پیٹریشیا ٹرائی
ایتھیریم کی حالت (تمام اکاؤنٹس، بیلنسز، اور سمارٹ کنٹریکٹس کا مجموعہ)، ڈیٹا سٹرکچر کے ایک خاص ورژن میں انکوڈ کی جاتی ہے جسے کمپیوٹر سائنس میں عام طور پر مرکل ٹری کے نام سے جانا جاتا ہے۔ یہ ساخت علمِ تشفیر میں بہت سی ایپلی کیشنز کے لیے مفید ہے کیونکہ یہ ٹری میں الجھے ہوئے ڈیٹا کے تمام انفرادی ٹکڑوں کے درمیان ایک قابلِ تصدیق تعلق پیدا کرتی ہے، جس کے نتیجے میں ایک واحد روٹ (root) ویلیو حاصل ہوتی ہے جسے ڈیٹا کے بارے میں چیزیں ثابت کرنے کے لیے استعمال کیا جا سکتا ہے۔
ایتھیریم کا ڈیٹا سٹرکچر ایک 'ترمیم شدہ مرکل پیٹریشیا ٹرائی' ہے، اس کا نام اس لیے رکھا گیا ہے کیونکہ یہ PATRICIA (the Practical Algorithm To Retrieve Information Coded in Alphanumeric) کی کچھ خصوصیات مستعار لیتا ہے، اور اس لیے کہ اسے ان آئٹمز کی موثر ڈیٹا بازیافت (retrieval) کے لیے ڈیزائن کیا گیا ہے جو ایتھیریم کی حالت پر مشتمل ہیں۔
ایک مرکل پیٹریشیا ٹرائی حتمی اور تشفیری طور پر قابلِ تصدیق ہے: حالت کا روٹ بنانے کا واحد طریقہ اسے حالت کے ہر انفرادی ٹکڑے سے شمار کرنا ہے، اور دو حالتیں جو ایک جیسی ہیں انہیں روٹ ہیش اور ان ہیشز کا موازنہ کر کے آسانی سے ثابت کیا جا سکتا ہے جو اس تک لے گئے (ایک مرکل ثبوت)۔ اس کے برعکس، ایک ہی روٹ ہیش کے ساتھ دو مختلف حالتیں بنانے کا کوئی طریقہ نہیں ہے، اور مختلف اقدار کے ساتھ حالت میں ترمیم کرنے کی کوئی بھی کوشش ایک مختلف حالت کے روٹ ہیش کا نتیجہ دے گی۔ نظریاتی طور پر، یہ ساخت داخل کرنے (inserts)، تلاش کرنے (lookups) اور حذف کرنے (deletes) کے لیے O(log(n)) کارکردگی کا 'ہولی گریل' (holy grail) فراہم کرتی ہے۔
مستقبل قریب میں، ایتھیریم کا ایک ورکل ٹری (Verkle Tree) ساخت میں منتقل ہونے کا ارادہ ہے، جو مستقبل میں پروٹوکول کی بہتری کے لیے بہت سے نئے امکانات کھولے گا۔
شرائط
اس صفحے کو بہتر طور پر سمجھنے کے لیے، ہیشز (opens in a new tab)، مرکل ٹریز (opens in a new tab)، ٹرائیز (opens in a new tab) اور سلسلہ بندی (opens in a new tab) کا بنیادی علم ہونا مددگار ثابت ہوگا۔ یہ مضمون ایک بنیادی ریڈکس ٹری (radix tree) (opens in a new tab) کی تفصیل سے شروع ہوتا ہے، پھر بتدریج ایتھیریم کے زیادہ بہتر ڈیٹا سٹرکچر کے لیے ضروری ترامیم متعارف کراتا ہے۔
بنیادی ریڈکس ٹرائیز
ایک بنیادی ریڈکس ٹرائی میں، ہر نوڈ اس طرح نظر آتا ہے:
[i_0, i_1 ... i_n, value]
جہاں i_0 ... i_n حروف تہجی کی علامتوں (اکثر بائنری یا ہیکس) کی نمائندگی کرتے ہیں، value نوڈ پر ٹرمینل ویلیو ہے، اور i_0, i_1 ... i_n سلاٹس میں اقدار یا تو NULL ہیں یا دوسرے نوڈز کے پوائنٹرز (ہمارے معاملے میں، ہیشز) ہیں۔ یہ ایک بنیادی (key, value) سٹور بناتا ہے۔
فرض کریں کہ آپ کلید-قدر (key-value) کے جوڑوں کے سیٹ پر ایک ترتیب کو برقرار رکھنے کے لیے ریڈکس ٹری ڈیٹا سٹرکچر استعمال کرنا چاہتے ہیں۔ ٹرائی میں کلید dog پر میپ کی گئی موجودہ قدر تلاش کرنے کے لیے، آپ پہلے dog کو حروف تہجی میں تبدیل کریں گے (جس سے 64 6f 67 حاصل ہوگا)، اور پھر اس راستے پر چلتے ہوئے ٹرائی میں نیچے جائیں گے جب تک کہ آپ کو قدر نہ مل جائے۔ یعنی، آپ ٹرائی کا روٹ نوڈ تلاش کرنے کے لیے ایک فلیٹ کلید/قدر DB میں روٹ ہیش کو تلاش کرنے سے شروع کرتے ہیں۔ اسے دوسرے نوڈز کی طرف اشارہ کرنے والی کلیدوں کی ایک صف (array) کے طور پر دکھایا جاتا ہے۔ آپ اشاریہ 6 پر موجود قدر کو بطور کلید استعمال کریں گے اور اسے فلیٹ کلید/قدر DB میں تلاش کریں گے تاکہ ایک سطح نیچے کا نوڈ حاصل کیا جا سکے۔ پھر اگلی قدر تلاش کرنے کے لیے اشاریہ 4 کا انتخاب کریں، پھر اشاریہ 6 کا انتخاب کریں، اور اسی طرح، یہاں تک کہ، ایک بار جب آپ نے راستے کی پیروی کر لی: root -> 6 -> 4 -> 6 -> 15 -> 6 -> 7، آپ نوڈ کی قدر تلاش کریں گے اور نتیجہ واپس کریں گے۔
'ٹرائی' اور بنیادی فلیٹ کلید/قدر 'DB' میں کچھ تلاش کرنے کے درمیان فرق ہے۔ وہ دونوں کلید/قدر کے انتظامات کی وضاحت کرتے ہیں، لیکن بنیادی DB کلید کی روایتی 1 قدمی تلاش کر سکتا ہے۔ ٹرائی میں کلید تلاش کرنے کے لیے اوپر بیان کردہ حتمی قدر تک پہنچنے کے لیے متعدد بنیادی DB تلاشوں کی ضرورت ہوتی ہے۔ ابہام کو ختم کرنے کے لیے آئیے مؤخر الذکر کو path کے طور پر حوالہ دیں۔
ریڈکس ٹرائیز کے لیے اپ ڈیٹ اور ڈیلیٹ آپریشنز کی وضاحت اس طرح کی جا سکتی ہے:
def update(node_hash, path, value):
curnode = db.get(node_hash) if node_hash else [NULL] * 17
newnode = curnode.copy()
if path == "":
newnode[-1] = value
else:
newindex = update(curnode[path[0]], path[1:], value)
newnode[path[0]] = newindex
db.put(hash(newnode), newnode)
return hash(newnode)
def delete(node_hash, path):
if node_hash is NULL:
return NULL
else:
curnode = db.get(node_hash)
newnode = curnode.copy()
if path == "":
newnode[-1] = NULL
else:
newindex = delete(curnode[path[0]], path[1:])
newnode[path[0]] = newindex
if all(x is NULL for x in newnode):
return NULL
else:
db.put(hash(newnode), newnode)
return hash(newnode)
ایک "مرکل" ریڈکس ٹری حتمی طور پر تیار کردہ تشفیری ہیش ڈائجسٹس کا استعمال کرتے ہوئے نوڈز کو جوڑ کر بنایا جاتا ہے۔ یہ مواد کی ایڈریسنگ (کلید/قدر DB key == keccak256(rlp(value)) میں) ذخیرہ شدہ ڈیٹا کی تشفیری سالمیت کی ضمانت فراہم کرتی ہے۔ اگر کسی دیے گئے ٹرائی کا روٹ ہیش عوامی طور پر جانا جاتا ہے، تو بنیادی لیف (leaf) ڈیٹا تک رسائی رکھنے والا کوئی بھی شخص یہ ثبوت بنا سکتا ہے کہ ٹرائی میں ایک مخصوص راستے پر ایک دی گئی قدر شامل ہے، ہر اس نوڈ کے ہیشز فراہم کر کے جو ایک مخصوص قدر کو ٹری کے روٹ سے جوڑتے ہیں۔
ایک حملہ آور کے لیے ایسے (path, value) جوڑے کا ثبوت فراہم کرنا ناممکن ہے جو موجود نہیں ہے کیونکہ روٹ ہیش بالآخر اس کے نیچے موجود تمام ہیشز پر مبنی ہوتا ہے۔ کوئی بھی بنیادی ترمیم روٹ ہیش کو بدل دے گی۔ آپ ہیش کو ڈیٹا کے بارے میں ساختی معلومات کی ایک کمپریسڈ نمائندگی کے طور پر سوچ سکتے ہیں، جو ہیشنگ فنکشن کے پری امیج (pre-image) تحفظ کے ذریعے محفوظ ہے۔
ہم ریڈکس ٹری کی ایک ایٹمی اکائی (مثلاً، ایک واحد ہیکس کریکٹر، یا 4 bit بائنری نمبر) کو "نبل" (nibble) کہیں گے۔ ایک وقت میں ایک نبل کے راستے کو عبور کرتے ہوئے، جیسا کہ اوپر بیان کیا گیا ہے، نوڈز زیادہ سے زیادہ 16 بچوں کا حوالہ دے سکتے ہیں لیکن ان میں ایک value عنصر شامل ہوتا ہے۔ لہذا، ہم انہیں 17 کی لمبائی والی صف کے طور پر پیش کرتے ہیں۔ ہم ان 17-عنصری صفوں کو "برانچ نوڈز" (branch nodes) کہتے ہیں۔
مرکل پیٹریشیا ٹرائی
ریڈکس ٹرائیز کی ایک بڑی حد ہے: وہ غیر موثر ہیں۔ اگر آپ ایک (path, value) بائنڈنگ کو ذخیرہ کرنا چاہتے ہیں جہاں راستہ، جیسا کہ ایتھیریم میں ہے، 64 کریکٹرز لمبا ہے (bytes32 میں نبلز کی تعداد)، تو ہمیں فی کریکٹر ایک سطح کو ذخیرہ کرنے کے لیے ایک کلو بائٹ سے زیادہ اضافی جگہ کی ضرورت ہوگی، اور ہر تلاش یا حذف کرنے میں پورے 64 مراحل لگیں گے۔ ذیل میں متعارف کرائی گئی پیٹریشیا ٹرائی اس مسئلے کو حل کرتی ہے۔
بہتری (Optimization)
مرکل پیٹریشیا ٹرائی میں ایک نوڈ درج ذیل میں سے ایک ہوتا ہے:
NULL(خالی سٹرنگ کے طور پر دکھایا گیا ہے)branchایک 17-آئٹم نوڈ[ v0 ... v15, vt ]leafایک 2-آئٹم نوڈ[ encodedPath, value ]extensionایک 2-آئٹم نوڈ[ encodedPath, key ]
64 کریکٹر کے راستوں کے ساتھ یہ ناگزیر ہے کہ ٹرائی کی پہلی چند تہوں کو عبور کرنے کے بعد، آپ ایک ایسے نوڈ پر پہنچیں گے جہاں کم از کم راستے کے کچھ حصے کے لیے کوئی مختلف راستہ موجود نہیں ہے۔ راستے میں 15 تک بکھرے ہوئے NULL نوڈز بنانے سے بچنے کے لیے، ہم [ encodedPath, key ] کی شکل کا ایک extension نوڈ ترتیب دے کر نزول کو مختصر کرتے ہیں، جہاں encodedPath میں آگے بڑھنے کے لیے "جزوی راستہ" (partial path) شامل ہوتا ہے (نیچے بیان کردہ کمپیکٹ انکوڈنگ کا استعمال کرتے ہوئے)، اور key اگلی DB تلاش کے لیے ہے۔
ایک leaf نوڈ کے لیے، جسے encodedPath کے پہلے نبل میں ایک فلیگ کے ذریعے نشان زد کیا جا سکتا ہے، راستہ پچھلے تمام نوڈ کے راستے کے ٹکڑوں کو انکوڈ کرتا ہے اور ہم براہ راست value تلاش کر سکتے ہیں۔
تاہم، یہ مندرجہ بالا بہتری ابہام پیدا کرتی ہے۔
نبلز میں راستوں کو عبور کرتے وقت، ہمارے پاس عبور کرنے کے لیے نبلز کی طاق (odd) تعداد ہو سکتی ہے، لیکن چونکہ تمام ڈیٹا bytes فارمیٹ میں محفوظ کیا جاتا ہے۔ مثال کے طور پر، نبل 1، اور نبلز 01 کے درمیان فرق کرنا ممکن نہیں ہے (دونوں کو <01> کے طور پر محفوظ کیا جانا چاہیے)۔ طاق لمبائی کی وضاحت کرنے کے لیے، جزوی راستے کے شروع میں ایک فلیگ لگایا جاتا ہے۔
تفصیلات: اختیاری ٹرمینیٹر کے ساتھ ہیکس ترتیب کی کمپیکٹ انکوڈنگ
جیسا کہ اوپر بیان کیا گیا ہے، طاق بمقابلہ جفت باقی جزوی راستے کی لمبائی اور لیف بمقابلہ ایکسٹینشن نوڈ دونوں کی فلیگنگ کسی بھی 2-آئٹم نوڈ کے جزوی راستے کے پہلے نبل میں رہتی ہے۔ ان کا نتیجہ درج ذیل ہے:
| ہیکس کریکٹر | بٹس | نوڈ کی قسم جزوی | راستے کی لمبائی |
|---|---|---|---|
| 0 | 0000 | ایکسٹینشن | جفت |
| 1 | 0001 | ایکسٹینشن | طاق |
| 2 | 0010 | ٹرمینیٹنگ (لیف) | جفت |
| 3 | 0011 | ٹرمینیٹنگ (لیف) | طاق |
جفت باقی راستے کی لمبائی (0 یا 2) کے لیے، ایک اور 0 "پیڈنگ" (padding) نبل ہمیشہ پیروی کرے گا۔
def compact_encode(hexarray):
term = 1 if hexarray[-1] == 16 else 0
if term:
hexarray = hexarray[:-1]
oddlen = len(hexarray) % 2
flags = 2 * term + oddlen
if oddlen:
hexarray = [flags] + hexarray
else:
hexarray = [flags] + [0] + hexarray
# ہیکس ایرے کی اب ایک جفت لمبائی ہے جس کا پہلا نبل فلیگز ہے۔
o = ""
for i in range(0, len(hexarray), 2):
o += chr(16 * hexarray[i] + hexarray[i + 1])
return o
مثالیں:
> [1, 2, 3, 4, 5, ...]
'11 23 45'
> [0, 1, 2, 3, 4, 5, ...]
'00 01 23 45'
> [0, f, 1, c, b, 8, 10]
'20 0f 1c b8'
> [f, 1, c, b, 8, 10]
'3f 1c b8'
مرکل پیٹریشیا ٹرائی میں نوڈ حاصل کرنے کے لیے توسیعی کوڈ یہ ہے:
def get_helper(node_hash, path):
if path == []:
return node_hash
if node_hash == "":
return ""
curnode = rlp.decode(node_hash if len(node_hash) < 32 else db.get(node_hash))
if len(curnode) == 2:
(k2, v2) = curnode
k2 = compact_decode(k2)
if k2 == path[: len(k2)]:
return get(v2, path[len(k2) :])
else:
return ""
elif len(curnode) == 17:
return get_helper(curnode[path[0]], path[1:])
def get(node_hash, path):
path2 = []
for i in range(len(path)):
path2.push(int(ord(path[i]) / 16))
path2.push(ord(path[i]) % 16)
path2.push(16)
return get_helper(node_hash, path2)
مثال ٹرائی
فرض کریں کہ ہم ایک ٹرائی چاہتے ہیں جس میں چار راستے/قدر کے جوڑے ('do', 'verb')، ('dog', 'puppy')، ('doge', 'coins')، ('horse', 'stallion') شامل ہوں۔
سب سے پہلے، ہم راستوں اور اقدار دونوں کو bytes میں تبدیل کرتے ہیں۔ ذیل میں، راستوں کے لیے اصل بائٹ کی نمائندگی <> سے ظاہر کی گئی ہے، حالانکہ اقدار کو اب بھی سٹرنگز کے طور پر دکھایا گیا ہے، جنہیں '' سے ظاہر کیا گیا ہے، تاکہ آسانی سے سمجھا جا سکے (وہ بھی، دراصل bytes ہوں گے):
<64 6f> : 'verb'
<64 6f 67> : 'puppy'
<64 6f 67 65> : 'coins'
<68 6f 72 73 65> : 'stallion'
اب، ہم بنیادی DB میں درج ذیل کلید/قدر کے جوڑوں کے ساتھ ایسی ٹرائی بناتے ہیں:
rootHash: [ <16>, hashA ]
hashA: [ <>, <>, <>, <>, hashB, <>, <>, <>, [ <20 6f 72 73 65>, 'stallion' ], <>, <>, <>, <>, <>, <>, <>, <> ]
hashB: [ <00 6f>, hashC ]
hashC: [ <>, <>, <>, <>, <>, <>, hashD, <>, <>, <>, <>, <>, <>, <>, <>, <>, 'verb' ]
hashD: [ <17>, [ <>, <>, <>, <>, <>, <>, [ <35>, 'coins' ], <>, <>, <>, <>, <>, <>, <>, <>, <>, 'puppy' ] ]
جب ایک نوڈ کا حوالہ دوسرے نوڈ کے اندر دیا جاتا ہے، تو جو شامل ہوتا ہے وہ keccak256(rlp.encode(node)) ہے، اگر len(rlp.encode(node)) >= 32 ورنہ node جہاں rlp.encode RLP انکوڈنگ فنکشن ہے۔
نوٹ کریں کہ ٹرائی کو اپ ڈیٹ کرتے وقت، کسی کو کلید/قدر کے جوڑے (keccak256(x), x) کو ایک مستقل لک اپ ٹیبل میں ذخیرہ کرنے کی ضرورت ہوتی ہے اگر نئے بنائے گئے نوڈ کی لمبائی = 32 ہو۔ تاہم، اگر نوڈ اس سے چھوٹا ہے، تو کسی کو کچھ بھی ذخیرہ کرنے کی ضرورت نہیں ہے، کیونکہ فنکشن f(x) = x قابلِ واپسی (reversible) ہے۔
ایتھیریم میں ٹرائیز
ایتھیریم کی عمل درآمد کی تہہ میں تمام مرکل ٹرائیز ایک مرکل پیٹریشیا ٹرائی کا استعمال کرتی ہیں۔
ایک بلاک ہیڈر سے ان میں سے 3 ٹرائیز کے 3 روٹس ہوتے ہیں۔
- stateRoot
- transactionsRoot
- receiptsRoot
حالت کی ٹرائی
ایک عالمی حالت کی ٹرائی ہے، اور جب بھی کوئی کلائنٹ کسی بلاک پر کارروائی کرتا ہے تو اسے اپ ڈیٹ کیا جاتا ہے۔ اس میں، ایک path ہمیشہ: keccak256(ethereumAddress) ہوتا ہے اور ایک value ہمیشہ: rlp(ethereumAccount) ہوتا ہے۔ خاص طور پر ایک ایتھیریم account [nonce,balance,storageRoot,codeHash] کی 4 آئٹم کی صف ہے۔ اس مقام پر، یہ بات قابل غور ہے کہ یہ storageRoot ایک اور پیٹریشیا ٹرائی کا روٹ ہے:
ذخیرہ ٹرائی
ذخیرہ ٹرائی وہ جگہ ہے جہاں تمام کنٹریکٹ کا ڈیٹا رہتا ہے۔ ہر اکاؤنٹ کے لیے ایک الگ ذخیرہ ٹرائی ہوتی ہے۔ کسی دیے گئے پتے پر مخصوص سٹوریج پوزیشنز پر اقدار بازیافت کرنے کے لیے سٹوریج کا پتہ، سٹوریج میں ذخیرہ شدہ ڈیٹا کی انٹیجر پوزیشن، اور بلاک ID درکار ہوتے ہیں۔ پھر انہیں جے سن آر پی سی API میں بیان کردہ eth_getStorageAt میں دلائل (arguments) کے طور پر پاس کیا جا سکتا ہے، مثلاً، پتے 0x295a70b2de5e3953354a6a8344e616ed314d7251 کے لیے سٹوریج سلاٹ 0 میں ڈیٹا بازیافت کرنے کے لیے:
curl -X POST --data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"], "id": 1}' localhost:8545
{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000004d2"}
سٹوریج میں دیگر عناصر کو بازیافت کرنا قدرے زیادہ پیچیدہ ہے کیونکہ ذخیرہ ٹرائی میں پوزیشن کا پہلے حساب لگانا ضروری ہے۔ پوزیشن کا حساب پتے اور سٹوریج پوزیشن کے keccak256 ہیش کے طور پر لگایا جاتا ہے، دونوں کو بائیں طرف صفر کے ساتھ 32 bytes کی لمبائی تک پیڈ (padded) کیا جاتا ہے۔ مثال کے طور پر، پتے 0x391694e7e0b0cce554cb130d723a9d27458f9298 کے لیے سٹوریج سلاٹ 1 میں ڈیٹا کی پوزیشن یہ ہے:
keccak256(decodeHex("000000000000000000000000391694e7e0b0cce554cb130d723a9d27458f9298" + "0000000000000000000000000000000000000000000000000000000000000001"))
ایک گو ایتھیریم (geth) کنسول میں، اس کا حساب اس طرح لگایا جا سکتا ہے:
> var key = "000000000000000000000000391694e7e0b0cce554cb130d723a9d27458f9298" + "0000000000000000000000000000000000000000000000000000000000000001"
undefined
> web3.sha3(key, {"encoding": "hex"})
"0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9"
لہذا path keccak256(<6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9>) ہے۔ اسے اب پہلے کی طرح ذخیرہ ٹرائی سے ڈیٹا بازیافت کرنے کے لیے استعمال کیا جا سکتا ہے:
curl -X POST --data '{"jsonrpc":"2.0", "method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9", "latest"], "id": 1}' localhost:8545
{"jsonrpc":"2.0","id":1,"result":"0x000000000000000000000000000000000000000000000000000000000000162e"}
نوٹ: ایک ایتھیریم اکاؤنٹ کے لیے storageRoot پہلے سے طے شدہ طور پر خالی ہوتا ہے اگر یہ کنٹریکٹ اکاؤنٹ نہیں ہے۔
ٹرانزیکشنز ٹرائی
ہر بلاک کے لیے ایک الگ ٹرانزیکشنز ٹرائی ہوتی ہے، جو دوبارہ (key, value) جوڑوں کو ذخیرہ کرتی ہے۔ یہاں ایک راستہ یہ ہے: rlp(transactionIndex) جو اس کلید کی نمائندگی کرتا ہے جو اس قدر سے مطابقت رکھتی ہے جس کا تعین اس سے ہوتا ہے:
if legacyTx:
value = rlp(tx)
else:
value = TxType | encode(tx)
اس بارے میں مزید معلومات EIP-2718 (opens in a new tab) دستاویزات میں مل سکتی ہیں۔
رسیدوں کی ٹرائی
ہر بلاک کی اپنی رسیدوں کی ٹرائی ہوتی ہے۔ یہاں ایک path یہ ہے: rlp(transactionIndex)۔ transactionIndex اس بلاک کے اندر اس کا اشاریہ ہے جس میں اسے شامل کیا گیا تھا۔ رسیدوں کی ٹرائی کو کبھی اپ ڈیٹ نہیں کیا جاتا ہے۔ ٹرانزیکشنز ٹرائی کی طرح، موجودہ اور پرانی (legacy) رسیدیں ہوتی ہیں۔ رسیدوں کی ٹرائی میں کسی مخصوص رسید کو تلاش کرنے کے لیے، اس کے بلاک میں ٹرانزیکشن کا اشاریہ، رسید کا پے لوڈ اور ٹرانزیکشن کی قسم درکار ہوتی ہے۔ واپس کی گئی رسید Receipt قسم کی ہو سکتی ہے جسے TransactionType اور ReceiptPayload کے ملاپ کے طور پر بیان کیا گیا ہے یا یہ LegacyReceipt قسم کی ہو سکتی ہے جسے rlp([status, cumulativeGasUsed, logsBloom, logs]) کے طور پر بیان کیا گیا ہے۔
اس بارے میں مزید معلومات EIP-2718 (opens in a new tab) دستاویزات میں مل سکتی ہیں۔