OUT_OF_ENERGY: lỗi TRON tiêu mất phí mà không gửi được gì
Một lần chuyển USDT được gửi đi, ví hiện ra là đã bị lấy phí, còn người nhận thì chẳng có gì. Giao dịch nằm trong một khối — nó không bị từ chối và không nút nào chết — và ở chỗ một lần chuyển hoàn tất ghi SUCCESS thì lần này ghi OUT_OF_ENERGY. Trên TRON lỗi đó mang đúng nghĩa của tên nó: lệnh gọi cạn năng lượng giữa chừng và dừng lại. Đây là cơ chế của nó, ba đường mà một đợt chi trả đâm vào nó, và các cách sửa, từ cách nhanh nhất tới cách thực sự tốn ít nhất.
Kết quả ấy thực ra nói gì
Gửi USDT là một lệnh gọi vào một hợp đồng thông minh, và việc chạy hợp đồng trên TRON được đo bằng năng lượng chứ không bị tính như một khoản phí cố định (năng lượng là gì). Năng lượng của chính người gửi được tiêu trước. Khi nó không đủ cho lệnh gọi, mạng lưới không dừng ở đó — nó mua phần còn lại bằng cách đốt TRX của người gửi theo mức của giao thức là 100 sun cho mỗi đơn vị, nhưng chỉ tới chừng mà fee_limit của chính giao dịch cho phép. Trường đó không phải là một khoản phí ai đó thu. Nó là cái trần cho lượng TRX của người gửi mà riêng lệnh gọi này được phép đốt.
Chạm trần khi lệnh gọi chưa xong thì việc chạy dừng lại. Mọi thay đổi trạng thái nó đã làm đều bị hoàn tác, nên không token nào dịch chuyển, và mạng lưới ghi phán quyết của mình lên giao dịch, đúng chỗ mà một lệnh gọi hoàn tất mang SUCCESS. Phán quyết đó là chữ mà một block explorer hiện ra bên cạnh một giao dịch hỏng, và là trường mà một nút trả về dưới tên contractRet. Không có gì ở giao dịch này là không tới được mạng lưới. Nó đã tới mạng lưới và được thực hiện cho tới khi tiền dành cho nó cạn.
Đằng nào TRX cũng đã tiêu
Việc hoàn tác xóa bỏ lần chuyển. Nó không xóa bỏ phần đã đốt: năng lượng đã tiêu là năng lượng đã trả tiền, và người gửi hụt đi đúng bằng những gì lần thử ấy đốt trên đường chạm trần, không token nào dịch chuyển, và còn lại một mã băm giao dịch mà một kịch bản đối soát sẽ vui vẻ xếp vào mục đã thanh toán.
Chính vì thế phản xạ thử lại là nước đi đắt đỏ. Cùng lần chuyển ấy với cùng cái trần ấy tiêu đúng lượng TRX ấy để lấy đúng kết quả ấy, và một vòng lặp chi trả tự động thử lại các lần hỏng có thể làm vậy vài lượt trước khi một con người kịp đọc chuỗi kết quả. Phần lớn những hỏng hóc mà một bộ phận chi trả gặp phải đều lặng lẽ ngốn tiền. Cái này ngốn tiền ồn ào mà vẫn bị bỏ sót, vì ví báo là có phí còn tiền thì chẳng thấy đâu.
Vì sao một cái trần hôm qua còn chạy được nay lại thôi
Người nhận đã đổi còn cái trần thì không. Đây là trường hợp một đợt chi trả gặp trước nhất. Một lần chuyển tới địa chỉ đã giữ USDT tốn khoảng 65,000 đơn vị năng lượng; tới một địa chỉ chưa từng giữ, lần chuyển còn phải tạo cả tài khoản token cho người nhận, và như thế là khoảng 131,000 — gấp đôi khẩu phần cho cùng một khoản tiền được chuyển đi. Đốt cho trường hợp thứ nhất tốn 6.5 TRX theo mức của giao thức, còn cho trường hợp thứ hai là 13.1 TRX. Một cái trần đặt theo trường hợp thông thường sẽ hụt một nửa ngay lần đầu trả tiền cho một khách hàng mới, và trong cả lô chẳng có gì trông khác đi: đó là thuộc tính của người nhận, không phải của lần chuyển.
Năng lượng từng có ở đó và giờ thì không. Một lần ủy quyền là một lượng cho một kỳ hạn, không phải một gói thuê bao. Cái mà lần chuyển đầu tiêu đi thì không còn cho lần thứ hai, và khi hết kỳ hạn, phần dư quay về với chủ của nó. Một chiếc ví đã gửi một lần trong cùng cửa sổ ấy có ít hơn con số nổi bật của lần ủy quyền, và lần chuyển sau mới là lần dừng lại — nhìn từ bên ngoài, chuyện đó đọc ra thành cùng một lần chuyển hỏng một cách ngẫu nhiên.
Cái trần bằng không. Không là giá trị đúng khi năng lượng đã chắc chắn, và ở mode B, nơi chúng tôi giao năng lượng và tự phát giao dịch lên mạng, fee_limit là thứ chúng tôi xin bạn cứ để trống: một cái trần ở đó chẳng là gì ngoài một giấy phép thường trực cho việc đốt TRX của người gửi nếu có chuyện trục trặc. Nó cũng là một điểm dừng cứng. Không cho phép gì nghĩa là một lệnh gọi thiếu đúng một đơn vị sẽ dừng lại chứ không trả tiền cho đơn vị ấy, nên để cái trần ở không là một quyết định chỉ đứng vững chừng nào năng lượng còn đứng vững.
Nhìn thấy nó trước khi giao dịch được ký
Câu hỏi nằm sau nguyên nhân đầu tiên — người nhận này tốn bao nhiêu năng lượng — là trả lời được trước khi có gì được ký, bằng hai lệnh gọi không giữ gì và không tính tiền gì.
GET /v1/address-check trả lời cho một địa chỉ: activated, holds_usdt, blacklisted, is_contract, và expected_kind suy ra từ chúng. holds_usdt là trường quyết định cỡ của lệnh gọi.
curl -s -H "Authorization: Bearer $KEY" \ "https://api.nrg.market/v1/address-check?address=TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9" # expected_kind là "single", "double" hoặc "custom"
POST /v1/estimate trả lời cho cả một lô — tối đa 500 người nhận trong một lệnh gọi, mỗi người kèm một kind, số energy_units đứng sau nó và mọi warnings nếu có. single là lần chuyển thông thường còn double là lần mở thêm một tài khoản token. custom là mục mà không quy tắc áng chừng nào phủ được: người nhận hóa ra là một hợp đồng với logic transfer của riêng nó, nên không con số tiêu chuẩn nào áp được và energy_units đến từ một lần chạy thử chính lệnh gọi ấy. Đó là những mục phải đặt trần cho từng cái một, và cảnh báo contract_recipient bên cạnh chúng thường là dấu hiệu đầu tiên cho thấy một địa chỉ nhận tiền hoàn toàn không phải là một chiếc ví.
Các cách sửa, dễ trước
Nâng cái trần lên. Một trường, không cần hạ tầng gì, và chuyện dừng biến mất: lệnh gọi chạy xong và mạng lưới lấy đúng phần nó cần. Chi phí thì không biến mất theo. Một cái trần là giấy phép để đốt, nên một cái trần hào phóng là một lần đốt hào phóng — lần chuyển từng dừng lại nay hoàn tất ở mức 13.1 TRX. Đúng khi làm sàn đỡ cho một đợt chi trả, sai khi làm kế hoạch.
Đưa năng lượng lên ví gửi. Khi đó chẳng còn gì để cái trần cho phép nữa. Năng lượng đến từ việc stake TRX, tức là khóa lại một khoản vốn đang làm việc và phải trông chừng một ngân sách tài nguyên, hoặc từ việc thuê một lần ủy quyền cho đúng vài phút mà một lần chuyển cần (việc đó tốn bao nhiêu ngay lúc này). Đằng nào thì lệnh gọi cũng được trả bằng năng lượng và không hề có lần đốt nào.
Hãy gửi trong cửa sổ. Năng lượng đi thuê nằm trên ví trong một kỳ hạn, và phản hồi của một đơn hàng mang theo send_before — thời điểm cửa sổ giao đóng lại, ba giây trước khi kỳ hạn bạn mua kết thúc. Hãy canh trường ấy xuất hiện chứ đừng canh chữ ready: đơn hàng hoàn tất bằng việc giao năng lượng, nên completed theo ngay sau. Gửi sau mốc send_before là đẩy lần chuyển vào mạng lưới lúc năng lượng có thể đã về nhà, và đó đúng là chỗ thiếu hụt mà bài này nói tới.
Hãy giao lại giao dịch đã ký. Mode B lấy hẳn cái cửa sổ đó khỏi phía bạn: bạn gửi lên một lần chuyển đã ký sẵn, chúng tôi giữ nó, và chúng tôi phát nó đi ngay khi năng lượng được xác nhận on-chain. Nếu một giao dịch chúng tôi phát đi vẫn vào được một khối mà không chạy, vị thế ấy đóng lại ở failed với transaction_reverted và toàn bộ phần giữ lại được nhả ra — nguyên nhân đó, như mọi nguyên nhân khác trong danh sách, không bao giờ bị tính tiền. Hãy hiểu cái tên đúng như nó là: transaction_reverted là chữ của chúng tôi cho «nó vào được một khối và đã không chạy», bất kể chữ của chính mạng lưới cho lời từ chối ấy là gì, nên một OUT_OF_ENERGY và một REVERT của chính hợp đồng đều về dưới cùng một trạng thái. Lượng TRX mà một giao dịch như thế đã đốt trên đường tới phán quyết đó là của người gửi chứ không phải của chúng tôi, và đó là lý lẽ ủng hộ cái ước tính ở đầu mục này chứ không phải một vòng lặp thử lại ở cuối mục.