למה חוזה claim של טוקנים נשבר תחת עומס
בחוזה claim של טוקנים, כשלים בהקצאת תגמולים מצטמצמים לשלוש מכניקות. מרוץ: הבדיקה ש'הכתובת עוד לא קיבלה' ורישום ההקצאה מופרדים, כך ששתי טרנזקציות באותו בלוק עוברות אותה. קבלה כפולה של טוקנים — דרך אותה חתימה או כתובת שנייה, כאשר הזכות לתגמול אינה מקושרת לחשבון. תרחיש שלישי — קריאה חיצונית בעת התשלום, שמחזירה שליטה לפני עדכון המצב.
עומס שיא ברשת הופך מרוץ נדיר לדפוס קבוע. העמלה עולה, חלק מהטרנזקציות נתקעות בתור, הארנק חוזר על השליחה עם nonce חדש — ושתי המופעים נכנסים לבלוק. צומת תחת מגבלת בקשות מחזיר שגיאה בקריאת המצב, והממשק שוב מציג את התגמול כזמין, ומעודד ניסיונות חדשים.
לכן בונים את לוגיקת ההקצאה סביב מפתח אידמפוטנטי לכל תשלום, מונה תקופות מונוטוני במקום דגל בוליאני, ומגבלה לכתובת לתקופה. חישוב הסכום מופרד מההעברה, ושינוי המצב קורה לפני קריאות חיצוניות.
ביקורת לוגיקת ההקצאה בודקת לא את הקוד בעין, אלא את המצב לפני ואחרי על עותק של הרשת — עם בקשות מקבילות, rollback וכניסה חוזרת למתודה.
בנפרד אנו בודקים התנהגות תחת כשל: צומת לא זמין, אישור שמגיע פעמיים, תשלום שנקטע באמצע. החוזה צריך להישאר במצב עקבי, ולמקבל — להיות דרך ברורה להשלים את קבלת התגמול בלי הנפקה כפולה. בהמשך נפרט איך זה נסגר ברמת הסכימה והקוד.
מה משתנה אחרי עיצוב מחדש של לוגיקת הקצאת הטוקנים
אופטימיזציה של לוגיקת ההקצאה בחוזה claim היא שינוי קטגוריה של התנהגות תחת עומס, לא קוסמטיקה בקוד. בבדיקת הקבלה אנחנו מסתכלים על מדדים מדידים, ולא על הניסוח 'נהיה מהיר יותר'.
- שיעור ההקצאות המוצלחות. קודם לכן הקצאה המונית התפרקה חלקית: חלק מהמשתתפים קיבלו טוקנים, אחרים — סירוב בגלל אזילת מגבלת הגז בתוך הלולאה. אחרי הוצאת החישוב לתהליך נפרד ושליחה בקבוצות, שיעור הפעולות המוצלחות בשיא נשאר מעל 99%, ומספר הטיפולים הידניים בפניות קטן בסדר גודל.
- זמן התגובה של החוזה. אישור הבקשה כבר לא ממתין לחישוב מלא של הזכויות: הממשק מקבל תשובה מיד, והחישוב מתבצע ברקע. זמן התגובה מפסיק לגדול באופן ליניארי עם מספר הבקשות במקביל.
- ירידה בשיעור הסירובים. נעלם סוג שלם: בקשות כפולות ומתחרות, ניסיונות לקבל טוקנים פעמיים. אידמפוטנטיות הבקשה סוגרת אותם ברמת החוזה, לא בתמיכה.
- עמלת הרשת בהקצאה המונית. שליחה בקבוצות מחליפה מאות העברות בודדות בפעולה אחת — העלות של הקצאה לאלפי כתובות יורדת פי כמה.
- היקף הטיפולים הידניים. יומן אירועים ומצבים מפורשים של הבקשה מאפשרים לנתח תקלה לפי הלוגים, בלי התאמות ידניות.
אנחנו בודקים זאת על סביבת בדיקות עם פרופיל עומס קרוב לזה של הפרודקשן, ואת פרימיטיבי הטוקן הסטנדרטיים ניקח מספריות שעברו ביקורת (OpenZeppelin) — את הבסיס לא ממציאים מחדש.
השוואה בין גישות להקצאת זכויות ולחישוב תגמולים
ההחלטה הראשונה — איך הזכויות מגיעות לרשת: במאגר חתימות אחד או בהעברות נפרדות לכל כתובת. השנייה — היכן נשמרת אמת התגמול: במצב החוזה או במרשם off-chain שרק מאשר את הזכות. השלישית — מי משלם את עמלת הרשת. שלוש הבחירות האלה בלתי תלויות, אבל יחד הן קובעות את עלות ההקצאה ואת דרישות האמון.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| מאגר חתימות אחיד | יש הרבה תגמולים, והמקבלים אוספים אותם בעצמם | דרוש חישוב נכון של הסט והגנה מפני קבלה כפולה |
| העברות בודדות | יש מקבלים בודדים או שהתגמול מוקצה אירועית | מספר הטרנזקציות גדל ליניארית, ואיתו גם סך העמלה |
| בדיקת זכויות on-chain | עצמאות מהשירות שלנו קריטית | כל בדיקה כותבת למצב — בהקצאה המונית זה יקר יותר |
| מרשם תגמולים off-chain | הנתונים משתנים לעיתים קרובות, וחשובה מהירות ההקצאה | דורש אמון במרשם או אישור בחתימה |
| סבסוד עמלת הרשת | המקבל לא צריך להחזיק כספים לעמלה | ההוצאות על כל ההקצאה נופלות על בעל החוזה |
| המקבל משלם את העמלה | הפרויקט לא לוקח על עצמו התחייבות להקצאה | חסם כניסה: בלי יתרה אי אפשר לממש את הזכות |
חוזה claim תפעולי לטוקנים כמעט תמיד מורכב משתיים-שלוש שורות בטבלה הזו. הקלאסיקה — מרשם תגמולים off-chain בתוספת מאגר חתימות אחיד: השירות מחשב את הסט, והחוזה סוגר את ההקצאה לקבוצת כתובות בטרנזקציה אחת.
האמון במרשם מוסר כאן על ידי בדיקת חתימה ברשת, ולמקבל נשארת טרנזקציה אחת — עליה הוא גם משלם עמלה, אם לא הוגדר סבסוד.
שלבי הטמעה: מביקורת ועד מסירה לתפעול
הטמעת חוזה claim היא לא העלאה חד-פעמית של קוד, אלא רצף שבו כל שלב נשען על הקודם. אנחנו מתחילים בניתוח לוגיקת ההקצאה הנוכחית ומסיימים במסירה לתפעול עם הדרכת הצוות — כך בכל שלב רואים מה כבר נבדק ומה עדיין לא.
- ניתוח לוגיקת ההקצאה הנוכחית. איך נספרות הזכויות לטוקנים, היכן נשמרים הנתונים, מי יוזם את התשלום. בפלט — מפת פערים בין המסמכים לבין ההתנהגות בפועל של המערכת.
- סכימת החוזה. תפקידים, מגבלות, השהיה, מכניקת פנייה חוזרת. מסכמים לפני הקוד: תיקון בסכימה לוקח שעות, אחרי ההשקה — שבועות.
- ביקורת חוזה חכם. בודקים קריאות חיצוניות ותלויות, בודקים מה יקרה בכשל של מקור חיצוני ואיך זה ישפיע על זכויות שכבר הוקצו.
- סביבת בדיקות. פורסים עותק של הרשת עם נפח נתונים והיסטוריית פעולות אמיתיים — עליו מריצים תרחישים שאסור לבדוק בבטחה ברשת הראשית.
- מצבי קצה. פנייה אפסית ופנייה חוזרת, בקשה על גבול החלון, תשלומים במקביל, עצירה וחידוש. כל תרחיש — בדיקה נפרדת עם תוצאה צפויה.
- מסירה לתפעול. מוסרים מפתחות לפי סכימת ריבוי חתימות, תיעוד ותקנון עדכונים. מדריכים את הצוות: איך לקרוא אירועים, איך להגיב לעצירה, איך להכין גרסה חדשה.
המסירה היא לא נקודת סיום: התיעוד מתאר איך להעלות עדכון לחוזה בלי לאבד זכויות שכבר הוקצו.
תיעוד, סכימות גישה ותקנוני הנפקה
אנחנו מוסרים לא רק קוד, אלא גם תיעוד עבודה: בלעדיו צוות התמיכה לא יוכל לא להנפיק טוקנים לפי התקנון ולא לשלול תפקיד מעובד שעזב.
כל ארטיפקט אנחנו כותבים לקהל קוראים מוגדר — מהנדס תמיכה, מבקר, אחראי הנפקה — ובודקים אותו בסביבה חיה: פורסים לפי ההוראות ומוודאים שהשלבים משוחזרים בלי הכותב.
- סכימת תפקידי גישה — מי שולט במה: הנפקה, השהיית פעולות, שינוי שיעורים או מגבלות, משיכת אמצעים; מסומן אילו תפקידים מותר להחזיק על מפתח אחד, ואילו מופרדים למפתחות שונים.
- תיעוד החוזה — ייעוד המתודות, אירועים והשדות שלהם, מצב ומעברים, התנהגות בכשלים; עם דוגמאות קריאה ותוצאות צפויות.
- תקנון הנפקת טוקנים — סדר הפעולות מהבקשה ועד האישור: מי יוזם, מי מאשר, אילו בדיקות קודמות לחתימה, מה נבדק אחר כך.
- הוראות עדכון — סדר שלב-אחר-שלב לגרסה חדשה: הכנה, בדיקת תאימות מצב, חלון מיגרציה, קריטריון הצלחה וסדר חזרה לגרסה הקודמת.
- מדיניות עבודה עם מפתחות — היכן נשמרים, כמה חתימות נדרשות לפעולות קריטיות, איך מתבצעת רוטציה ומה עושים בעת פגיעה.
- צ'קליסט קבלה ופריסה — נקודות בקרה לפני פרסום: בדיקות על עותק של הרשת הראשית, בדיקת זכויות, מגבלות, נכונות כתובות.
- תזכורת לתמיכה — פניות אופייניות ופעולות בהן, אילו אירועים נחשבים לנורמה ואילו דורשים עצירת הנפקה וניתוח.
הארטיפקטים נמצאים במאגר שלכם לצד הקוד ומתעדכנים יחד איתו — בעבודה הבאה המהנדס מרים את ההקשר לפי המסמכים, ולא לפי התכתבות.
מקרה בוחן: איך צומצם שיעור ההקצאות הכושלות
נקודת התחלה
הלקוח — שירות עם צבירת נקודות והנפקת טוקנים תקופתית: חישוב ההקצאות חי בשירות אחד, ואותה מערכת גם ביצעה חתימה ושליחת טרנזקציות, בלי מפתחות אידמפוטנטיים לכל הקצאה.
בשיאי עומס פריטים נשלחו שוב בגלל התנגשויות בתור החתימה, חלק נדחו בגלל מגבלת גז שאזלה, וחלק אבדו בין שני ניסיונות retry. שיעור ההקצאות המוצלחות החזיק סביב 94% — כלומר אחת מכל עשרים הקצאות דרשה התערבות ידנית.
מה שינו ומה התקבל
את המערכת פיצלו לשלושה חלקים בלתי תלויים: חישוב ההקצאות, תור הקצאות עם מזהה ייחודי לפריט, וחתם נפרד שעובד על טרנזקציה אחת בכל פעם. את החוזים הרכיבו על בסיס שעבר ביקורת OpenZeppelin — עם הגנה מפני כניסה חוזרת, תפקיד מפעיל והשהיה על כל המערכת.
כל הקצאה קיבלה מפתח אידמפוטנטיות: ניסיון חוזר נדחה ברמת החוזה, לא ברמת השירות, ולכן הקצאה כפולה הפכה לבלתי אפשרית מבחינת המבנה.
הוסיפו בדיקת גז מקדימה וניסיון חוזר עם חישוב מחדש של המגבלה, ופריטים כושלים עברו לתור נפרד עם התראה — המפעיל רואה מה בדיוק נתקע, ולא מנתח את זה ידנית לפי לוגים.
| מדד | לפני | אחרי |
|---|---|---|
| שיעור ההקצאות המוצלחות | ~94% | 99.8% |
| זמן התאוששות אחרי תקלה | 30–40 דק' | עד 3 דק' |
| פניות לתמיכה לשחרור | 20–30 | בודדות |
| הקצאות כפולות | באופן קבוע | סוג הכשל נעלם |
ההקצאות הפסיקו להיות צוואר בקבוק בשחרור: התור מתפרק מעצמו, ולטיפול ידני נשארים רק פריטים עם נתוני קלט שגויים.
מה לעשות אם ההנפקה כבר פרוסה ברשת
חוזה הנפקה פרוס הוא כבר מצב רשת קבוע, לא קובץ במאגר שאפשר לדרוס. הכתובת, היסטוריית ההקצאות וזכויות המחזיקים אינן משתנות, ולכן המשימה היא לא החלפה, אלא מעבר זהיר: לוגיקת ההקצאה החדשה מתחילה לעבוד, וההיסטוריה הישנה נשארת ניתנת לבדיקה.
לרוב משאירים את נקודת הכניסה כפי שהיא, ומוציאים את הלוגיקה אל מחוצה לה: חוזה עטיפה מקבל פניות באותה כתובת ומעביר אותן למודול חדש. המחזיקים והאינטגרציות החיצוניות לא צריכים לשנות דבר — הארנקים ממשיכים לפנות לאותו מקום שאליו פנו קודם, והעדכון עובר בלי שהשירות יבחין.
העברת מצב המרשם מתבצעת בסנאפשוט: קובעים חתך של התגמולים שנצברו והסימונים על קבלתם בבלוק מסוים, מעבירים למודול החדש ומשווים תוצאות לפני ואחרי — סכומים לפי כתובות, מספר תגמולים שלא נאספו, סכומי ביקורת.
בנפרד מחשבים דלתא: מי שקיבל זכות לתגמול בין צילום הסנאפשוט להפעלת הלוגיקה החדשה לא צריך לא ליפול מהמרשם ולא לקבל הקצאה כפולה.
תאימות גרסאות נסגרת בתקופת עבודה במקביל: המודול החדש קורא היסטוריה מהחוזה הישן בפורמט הקודם, ורושם רשומות חדשות כבר אצלו. כל עוד שני המקורות מחזירים תשובה זהה לשאילתות יתרות, אפשר לבטל את המעבר בלי לאבד נתונים ובלי לעצור את ההקצאה.
בדיוק בגלל זה מתכננים מיגרציה כרצף של צעדים הפיכים, ולא כהחלפת לוגיקה בבת אחת. סדר כזה שומר על המשכיות השירות עבור המחזיקים, משאיר עקבות לבדיקה, ומסיר את הסיכון להקצאות כפולות — סוג כשל שהכי יקר לתקן בדיעבד.
שאלות טכניות שכדאי לסגור לפני ההתחלה
לפני ההתחלה בדרך כלל עולים אותם ספקות טכניים: מה מונע לקבל הקצאה פעמיים, אפשר להגביל את מעגל הכתובות, מי משלם את עמלת הרשת ואיך להשתכנע שהסכומים בטבלת ההקצאות נכונים. להלן תשובות קצרות שאנחנו סוגרים בשלב תכנון החוזה, ולא אחרי ההשקה.
איך נמנעת קבלה כפולה של טוקנים?
הזכות להקצאה נקבעת בצילום מצב בזמן הדיפלוי — בעץ הקצאות או בטבלת חשבונאות. כל claim מסמן את הכתובת ככזו שנוצלה, והדגל מוצב לפני העברת האמצעים: אחרת קריאה חוזרת במסגרת אותה טרנזקציה עלולה לעבור פעמיים. פנייה חוזרת פשוט מבוטלת על ידי הטרנזקציה.
אפשר להגביל קבלה לפי כתובות או אזורים?
כן: רשימה לבנה, מגבלה לכתובת בסיבוב אחד, חיתוך טווחים מוגדרים. את האזור החוזה לא בודק — הוא לא יודע את תחום השיפוט של הארנק. רשימת המורשים נוצרת לפני הדיפלוי, והכללים האזוריים פועלים בכניסה, לא בקוד.
מי משלם את עמלת הרשת?
כברירת מחדל המקבל: את ה-claim מפעיל המחזיק עצמו, והחוזה לא גובה תשלום. אם צריך ספונסר לעמלה, מוסיפים relayer עם הרשאות חתומות, ומחשבים מגבלות בנפרד כדי שה-relayer לא יבזבז אמצעים על ספאם זורם.
איך נבדקת נכונות ההקצאות?
את הסכומים משווים בשתי דרכים בלתי תלויות: חישוב מחדש מנתוני המקור ולפי יומן החוזה. לפני פתיחת ה-claim מריצים בדיקת הוכחות יבשה למדגם כתובות — פערים נראים לפני הטרנזקציות הראשונות, ולא לפי פניות של מחזיקים.
נדון במשימה ונחזור עם הערכה ותוכנית
הערכת המשימה נגזרת מהלוגיקה הנוכחית שלכם: ככל שהקלט מתואר מדויק יותר, כך יש פחות הנחות בתוכנית העבודה וכך מהר יותר אנחנו נוקבים בלוחות זמנים. שלחו את מה שכבר קיים — זה מספיק כדי לחזור עם ניתוח, הערכה והצעה לארכיטקטורה.
- סכימה או תיאור טקסטואלי של לוגיקת ההקצאה הנוכחית: מי יוזם, באיזה סדר, מה קורה בפנייה חוזרת ואיך מטופלת שגיאה.
- תרחישי הנפקת טוקנים: חד-פעמיים וגליים, עם חלונות זמן, מגבלות לכתובת וכללים למקבל.
- אילוצים: תפקידים והרשאות גישה, בדיקות חיצוניות חובה, דרישות לדיווח ולביקורת פעולות.
- רשימת מקבלים או כלל ליצירתה: צילום מצב של מחזיקים, מרשם מתעדכן, חישוב לפי נוסחה מתנאים נתונים.
- מה כבר פרוס: כתובות חוזים, רשתות יעד, קיום סביבת בדיקות, נתונים על הרצות קודמות ותקלות ידועות.
- דרישות לליווי: מי מחזיק במפתחות, איך מתואמים שינויים, איזו רמת גישה נדרשת לנו כדי לתמוך בהקצאה.
בהמשך — תוכנית עבודה עם שלבים, נקודות בקרה ותוצאה ניתנת לבדיקה בכל צעד. ליווי החוזה אנחנו מנסחים כתקנון: ניטור הקצאות, סדר תגובה לאירועים, עדכוני לוגיקה זהירים בלי לעצור את התמיכה בהקצאת טוקנים. תארו את המשימה — נחזור עם הערכה ותוכנית.







