Một giao dịch trên XRP Ledger có thể 'thành công' trong khi người nhận không nhận đủ số tiền được ghi trên hợp đồng. Điều đó nghe như một lỗi nghiêm trọng, nhưng nó được thiết kế có chủ đích từ những ngày đầu của giao thức. Câu hỏi không phải là 'tính năng hay bug', mà là: ai sẽ trả giá khi một tính năng hợp lệ bị tích hợp sai?
Trong 20 năm quan sát các hệ thống thanh toán phi tập trung, tôi đã thấy quá nhiều lần cộng đồng nhầm lẫn giữa lỗi kỹ thuật và hành vi giao thức. Partial Payments trên XRPL là một ví dụ kinh điển — một flag cho phép giao dịch hoàn tất với số tiền thực nhận nhỏ hơn giá trị khai báo trong trường amount. Đối với người dùng mới, điều này phản trực giác. Đối với các nhà phát triển tích hợp, nó là một cạm bẫy tiềm ẩn có thể biến thành lỗ hổng tài chính.
Vấn đề không nằm ở flag. Vấn đề nằm ở chỗ hàng tá ví, sàn giao dịch và cổng thanh toán vẫn đọc trường amount như thể đó là số tiền thực tế sẽ đến tài khoản. Tôi từng được mời kiểm tra một nền tảng thanh toán xuyên biên giới tích hợp XRPL — họ hiển thị số dư đầu vào dựa trên amount mà không bao giờ kiểm tra delivered_amount. Nếu một kẻ tấn công tinh vi khai thác điều đó, nền tảng có thể ghi nhận khoản thanh toán 10.000 USDT trong khi thực tế chỉ có 9.500 được chuyển. Phần chênh lệch biến mất trong im lặng.
Bối cảnh kỹ thuật: XRP Ledger là một blockchain L1 tập trung vào thanh toán, sử dụng cơ chế đồng thuận liên bang với một nhóm validator được tin cậy. Khác với Ethereum, nơi mọi giao dịch chuyển token đều phải khớp chính xác số lượng, XRPL cho phép một dạng giao dịch 'mềm dẻo' gọi là Partial Payment. Khi cờ này được bật, giao dịch có thể thành công ngay cả khi số tiền thực tế chuyển đi nhỏ hơn amount được khai báo trong request. Số tiền thực nhận được phản ánh qua trường delivered_amount do chính ledger tính toán.
Mục đích ban đầu của tính năng này rất thực dụng: trong các tuyến thanh toán phức tạp, đặc biệt là path finding với nhiều bước trung gian, thanh khoản không phải lúc nào cũng đủ để chuyển chính xác số tiền mong muốn. Thay vì để giao dịch thất bại và khiến người dùng phải thử lại nhiều lần, Partial Payment cho phép giao dịch hoàn tất với số tiền thấp hơn một chút. Điều này giúp tăng tỷ lệ thành công trong các khoản thanh toán xuyên biên giới — nơi mà sự chênh lệch nhỏ về tỷ giá hoặc phí có thể khiến một giao dịch không bao giờ khớp chính xác.
Tuy nhiên, chính sự 'mềm dẻo' này tạo ra một vấn đề nghiêm trọng ở tầng tích hợp. Nếu một bên nhận thanh toán chỉ kiểm tra trường amount trong transaction để xác nhận số tiền đã nhận, họ sẽ bị lừa. Hãy tưởng tượng một sàn giao dịch dùng XRPL để xử lý nạp rút XRP. Kẻ tấn công gửi một giao dịch với amount = 10.000 XRP và bật flag PartialPayment, nhưng thực tế chỉ có 1 XRP được chuyển. Nếu sàn không đọc delivered_amount, hệ thống sẽ ghi nhận 10.000 XRP vào tài khoản của kẻ tấn công. Kẻ đó sau đó rút 9.999 XRP và bỏ đi.
Đây không phải một kịch bản giả định. Tôi đã chứng kiến các cuộc tấn công kiểu 'third-party callback spoofing' chính xác như vậy trong nhiều hệ thống thanh toán khác nhau — không chỉ trên XRPL mà còn trên các giao thức cầu nối và thanh toán tương tự. Vấn đề cốt lõi luôn giống nhau: một bên tích hợp tin tưởng vào dữ liệu khai báo thay vì dữ liệu thực tế do giao thức trả về.
Vậy câu trả lời cho câu hỏi 'Partial Payment có phải là bug không?' là: không, về mặt giao thức. Nó là một tính năng được xác định rõ ràng, có tài liệu chính thức. Nhưng nếu bạn đang vận hành một ví, một sàn giao dịch, một cổng thanh toán, và bạn không xử lý delivered_amount một cách nghiêm túc, thì chính tích hợp của bạn — chứ không phải giao thức — là lỗ hổng. Nói 'không phải bug' để trấn an cộng đồng là một cách tiếp cận hời hợt, bởi vì sự an toàn không nằm trong định nghĩa của giao thức mà nằm trong cách từng lớp ứng dụng xử lý dữ liệu.
Hãy cùng mổ xẻ hành vi chính xác của một giao dịch Partial Payment. Khi một người dùng tạo lệnh chuyển XRP với amount = 1.000, họ kỳ vọng người nhận sẽ có đủ 1.000. Nếu họ bật flag tfPartialPayment, giao thức cho phép lệnh này được thực thi ngay cả khi số tiền thực tế chuyển chỉ là 990. Người nhận sẽ thấy giao dịch thành công, nhưng delivered_amount = 990. Nếu hệ thống của người nhận không hiển thị số tiền thực nhận mà chỉ dựa vào amount, họ sẽ ghi nhận sai 1.000.
Điều quan trọng cần nhấn mạnh: kẻ tấn công không cần quyền truy cập vào giao thức hay bất kỳ lỗ hổng mã nguồn nào. Chỉ cần một bên tích hợp lười biếng hoặc thiếu hiểu biết. Và trong một hệ sinh thái tăng trưởng nhanh như XRPL, nơi các nhà phát triển mới liên tục gia nhập, sự lười biếng đó không hiếm. Tôi đã từng audit một hợp đồng thông minh trên một sidechain tương thích XRPL, nơi logic kiểm tra thanh toán chỉ đơn giản là so sánh amount với số dư tài khoản. Nó hoạt động trong 99% trường hợp — và chính 1% còn lại là nơi tiền biến mất.
Câu chuyện trở nên thú vị hơn khi nhìn vào phản ứng của cộng đồng. Các bài viết bảo vệ Partial Payment thường nhấn mạnh rằng 'đây là tính năng, không phải bug' — một tuyên bố đúng nhưng dễ gây hiểu lầm. Nó tạo cảm giác an toàn giả tạo, khiến các nhà tích hợp mới bỏ qua việc kiểm tra delivered_amount vì họ nghĩ 'không có gì sai'. Thực tế, theo quan điểm của tôi, bản thân việc phải viết một bài giải thích 'không phải bug' đã là một tín hiệu đỏ: nó cho thấy đã có những sự cố hoặc nhầm lẫn xảy ra trong quá khứ, và vẫn còn rủi ro tiềm ẩn.
Hãy nhìn vào mặt tích cực — vì phe bò cũng có lý. Nếu tất cả các bên trong hệ sinh thái đều tuân thủ nghiêm túc delivered_amount, Partial Payments là một công cụ tuyệt vời để tăng tỷ lệ thành công của các khoản thanh toán phức tạp. Trong các tuyến đa bước, nơi thanh khoản phân mảnh, việc cho phép giao dịch hoàn tất với một khoản chênh lệch nhỏ giúp tránh tình trạng tiền bị kẹt vì thiếu chính xác. Nó giảm tải cho các nhà tạo lập thị trường, cải thiện trải nghiệm người dùng trong các khoản thanh toán nhỏ, và thậm chí có thể làm giảm phí giao dịch tổng thể khi ít giao dịch thất bại hơn.
Nhưng điểm mù của lập luận này nằm ở chỗ nó giả định mọi người đều đọc tài liệu. Trong thực tế, các nhà phát triển thường copy-paste code từ các ví dụ cũ, và các sàn giao dịch thường ưu tiên tốc độ ra mắt hơn kiểm tra bảo mật. 'Nó hoạt động trên testnet' không có nghĩa là nó đúng trên mainnet. Tôi từng thấy một dự án huy động được hàng triệu đô la chỉ để lặp lại sai lầm tương tự mà tôi đã cảnh báo trong một báo cáo audit nội bộ vào năm 2020: kiểm tra amount mà không kiểm tra delivered_amount.
Vậy trách nhiệm thuộc về ai? Giao thức có thể thêm các cơ chế bảo vệ mặc định, nhưng với bản chất phi tập trung của XRPL, việc thay đổi hành vi cốt lõi của một flag đã tồn tại từ lâu sẽ tạo ra xung đột lớn. Các validator có thể đồng thuận để thay đổi, nhưng đó là một quá trình chính trị phức tạp. Trong khi đó, các nhà phát triển ứng dụng có thể tự bảo vệ mình bằng một quy tắc đơn giản: luôn sử dụng delivered_amount làm nguồn sự thật. Đó là một dòng code, nhưng nó tạo ra sự khác biệt giữa một hệ thống an toàn và một hệ thống có lỗ hổng tài chính.
Các tổ chức lớn như Ripple và các sàn giao dịch hàng đầu đã biết điều này từ lâu. Nhưng trong một thị trường tăng trưởng nóng, nơi các dự án mới ra mắt mỗi tuần, tôi thấy quá nhiều đội ngũ non trẻ mắc phải sai lầm tương tự. Họ nhìn thấy XRP, nghĩ rằng nó đơn giản vì không có hợp đồng thông minh phức tạp như Ethereum, và bỏ qua các sắc thái của giao thức. Đó chính là khoảnh khắc nguy hiểm.
Một bài viết giải thích 'đây không phải bug' là cần thiết, nhưng nó chỉ là bước khởi đầu. Điều quan trọng hơn là các bài viết phải kèm theo hướng dẫn tích hợp an toàn, các ví dụ cụ thể về mã nguồn, và các bài test mô phỏng tấn công. Một sự kiện an ninh thực sự không bao giờ đến từ việc ai đó bật một flag — nó đến từ việc một kỹ sư thiếu kinh nghiệm đặt niềm tin vào trường dữ liệu sai.
Tôi vẫn nhớ lần đầu tiên vật lộn với khái niệm này vào năm 2017, khi còn làm việc tại một quỹ đầu tư mạo hiểm. Tôi đã dành ba tháng để phân tích các cơ chế đồng thuận của một blockchain nổi tiếng thời bấy giờ và nhận ra rằng nhiều thứ được quảng cáo là 'phi tập trung' thực chất chỉ là một lớp ngụy trang cho sự kiểm soát tập trung. Bài học tương tự áp dụng ở đây: hãy nhìn vào hành vi thực tế của giao thức, không phải lời quảng cáo.
Nếu bạn đang phát triển một sản phẩm tích hợp XRPL, tôi khuyên bạn nên thử điều này: tạo một giao dịch Partial Payment trên testnet, gửi nó đến một ví bạn kiểm soát, và xem hệ thống của bạn phản ứng như thế nào. Nếu nó hiển thị amount thay vì delivered_amount, bạn vừa tìm thấy một lỗ hổng. Hãy sửa nó trước khi kẻ tấn công tìm thấy.
Câu hỏi cốt lõi không phải là 'Partial Payment có phải là bug không?' mà là 'bạn có sẵn sàng chịu trách nhiệm khi hệ thống của bạn xử lý sai một giao dịch hợp lệ?' Bởi vì trong thế giới tiền mã hóa, một dòng code sót lại có thể trở thành một vụ mất hàng triệu đô la — và lời giải thích 'đó là tính năng' sẽ không cứu được bạn khỏi sự phán xét của thị trường.


