Tôi dựng thử lõi sàn rồi rút điện giữa lúc nó đang ghi — 48 phép thử sau, tôi hiểu vì sao luật đòi 10.000 tỷ

node --test là chạy. Đây là lõi, không phải cái sàn — phần thiếu tôi liệt kê đủ ở cuối bài.Tôi đọc Nghị quyết 05 nhiều lần trong 2 tháng qua. Mỗi lần tới Điều 8, tới cụm vốn điều lệ đã góp tối thiểu 10.000 tỷ đồng, tôi đều dừng lại đúng một nhịp và nghĩ cùng một câu: con số này để làm gì.
Câu trả lời phổ biến ngoài kia là "để lọc, để chỉ ông lớn làm nổi". Câu đó không sai. Nhưng nó là câu trả lời của người đứng ngoài nhìn vào, và tôi thấy nó chưa chạm tới đáy.
Nên tôi làm một việc mà cho tới giờ tôi chưa thấy ai ở Việt Nam viết lại: tôi dựng thử cái lõi bên trong một sàn giao dịch. Không phải giao diện, không phải trang chủ. Cái ở trong cùng — chỗ tiền đổi chủ, chỗ lệnh gặp lệnh, và chỗ cả hai phải sống sót qua một lần mất điện.
Rồi tôi cho nó mất điện thật.
Thứ tôi dựng, và thứ tôi cố ý không dựng
Nói phạm vi trước, để không ai đọc bài này rồi hiểu nhầm thành "anh này làm được cái sàn".
Thứ tôi dựng là lõi tài chính: sổ cái hai vế, máy khớp lệnh, và tầng ghi bền để tỉnh lại sau sự cố. 2.653 dòng mã, trong đó phần lõi là 1.474 dòng. Không phụ thuộc một thư viện ngoài nào — tải về, gõ node --test, chạy được ngay bằng Node bản trần.
Lý do dựng phần này trước, chứ không dựng giao diện trước, gói gọn trong một câu tôi viết vào đầu tài liệu của chính mình:
Giao diện đẹp sửa lúc nào cũng được. Sổ cái sai thì sửa xong vẫn mất tiền.
Thứ tôi cố ý không dựng, và sẽ còn cố ý không dựng lâu nữa: ví trên chuỗi, nạp rút thật, và việc giữ khoá. Đó là phần không được để một cỗ máy viết mã tự đưa vào chỗ có tiền thật. Phần đó phải đi qua người, qua kiểm toán độc lập, qua thử tấn công, và qua thiết bị giữ khoá chuyên dụng.
Một lõi tài chính giấu phần thiếu là một lõi không đáng tin. Nên phần thiếu tôi để nguyên một mục ở cuối bài.
3 nguyên tắc tôi phải khắc vào cấu trúc, không phải viết vào tài liệu
Đây là chỗ tôi học được nhiều nhất, và là chỗ tôi nghĩ người viết luật và người viết mã đang nói cùng một thứ bằng 2 thứ tiếng.
1 · Máy khớp lệnh không được phép chạm vào số dư.
Trong toàn bộ mã nguồn không tồn tại hàm nào tên là addBalance() hay setBalance(). Cách duy nhất để một con số tiền thay đổi là đi qua một cửa duy nhất, với bút toán mà tổng các vế theo từng tài sản bằng 0.
Hệ quả của cách dựng đó đáng giá hơn tôi tưởng lúc bắt đầu: "tiền tự sinh ra" không còn là một lỗi phải đi tìm. Nó trở thành một trạng thái mà cấu trúc dữ liệu không biểu diễn nổi.
2 · Máy không đọc đồng hồ, và không gọi hàm ngẫu nhiên.
Thứ tự đến của một lệnh là con số truyền vào, không phải giờ hệ thống. Nghe như một chi tiết kỹ thuật khô khan. Nó không khô chút nào.
Vì nhờ vậy, phát lại từ lệnh số 1 luôn dựng đúng trạng thái cũ. Và đó là thứ biến việc đối chiếu với cơ quan quản lý thành một phép tính, thay vì thành một cuộc tranh luận. Sàn nói sổ tôi thế này. Cơ quan quản lý phát lại nhật ký. Hai bên ra cùng một vân tay hoặc không. Không có chỗ cho câu "hệ thống lúc đó nó thế".
3 · Tiền không bao giờ đi qua số thực.
Mọi lượng đều là số nguyên tuyệt đối theo đơn vị nhỏ nhất. Khi tôi đưa vào một lượng nhỏ hơn mức máy biểu diễn nổi, máy ném lỗi thay vì làm tròn. Nó từ chối tự ý quyết định thay người dùng một phần tiền, dù phần đó nhỏ tới đâu.
7 bất biến, và cái ít ai viết ra
Tôi cắm vào lõi 7 điều kiểm được sau mọi lệnh, kể cả lúc đang chạy thật. 6 điều đầu là thứ ai làm sổ sách cũng gật đầu: tổng nợ bằng tổng có xét riêng từng tài sản, không ví nào âm, một lệnh không khớp quá lượng đã đặt, phát lại nhật ký ra đúng số dư hiện tại, nghĩa vụ với khách bằng đúng tổng tài sản đang giữ, sổ lệnh không còn lệnh chết nằm lại.
Điều thứ tư mới là điều tôi muốn kể:
Tiền đang khoá ở sổ cái phải khớp từng đồng với nhu cầu của các lệnh đang nằm trên sổ lệnh.
Đây là bất biến ít được viết ra nhất, và là chỗ tiền thật hay rò rỉ nhất. Lý do: một máy khớp lệnh đúng và một sổ cái đúng vẫn có thể cùng nhau làm mất tiền, nếu số đang bị khoá không còn khớp với số mà các lệnh trên sổ thực sự cần. Mỗi bên tự soi mình đều thấy sạch. Chỗ hở nằm ở giữa 2 bên.
Nên bộ chạy loạn của tôi kiểm bất biến này sau từng lệnh một — 7.500 lệnh sinh ngẫu nhiên qua 5 hạt giống khác nhau. Không gãy lần nào.
Rồi tôi rút điện giữa lúc nó đang ghi
Phần này là phần tôi thích nhất, vì nó là phần không đọc tài liệu nào thay được.
Tôi viết một bộ phép thử mà tôi gọi là bộ phá máy. Chúng cắt tệp thật và sửa byte thật trên đĩa, chứ không giả lập sự cố bằng cách gọi một hàm giả.
4 thứ tôi làm với nó, và 4 thứ nó trả lời:
Tôi cắt cụt dòng cuối của tệp nhật ký — đúng hình dạng một lần mất điện giữa lúc máy đang ghi xuống đĩa.
Máy tỉnh lại được, mất đúng một lệnh cuối và không mất hơn, ghi tiếp được ngay, và chuỗi băm nối liền.
Tôi sửa một chữ số ở giữa nhật ký, sửa khéo sao cho dòng đó vẫn là JSON hợp lệ.
Máy bắt được, và nêu đích danh dòng nào. Vì mỗi dòng mang băm nối tiếp dòng trước, sửa một ký tự ở giữa làm mọi dòng phía sau lệch băm — muốn vá lại phải viết lại toàn bộ phần đuôi.
Tôi làm hỏng nhật ký ở giữa.
Máy từ chối khởi động. Nó không im lặng bỏ qua phần hỏng rồi chạy tiếp — vì chạy tiếp trên một cuốn sổ khuyết là cách tạo ra một sàn tin rằng mình đúng.
Tôi xoá thẳng tệp ảnh chụp trạng thái.
Máy dựng lại đủ từ nhật ký thuần, vân tay trùng khít với trước khi xoá.
Và một phép thử tôi thấy đáng giá nhất trong cả bộ: đang chạy vẫn tự nghiệm thu được. Máy dựng bản thứ hai từ đĩa, rồi so vân tay với bản đang chạy trong bộ nhớ. 2 bản khớp thì cuốn sổ trên đĩa đúng là cuốn sổ trong đầu nó.
48 phép thử — và con số duy nhất không chịu dao động
Sáng nay tôi chạy lại toàn bộ trước khi ngồi viết bài này. Đây là chỗ tôi phải nói rõ một chuyện, vì nó là chuyện đáng giá nhất tôi rút ra được.
Lệnh node --test in ra 48 phép thử, 48 xanh, 0 đỏ. Đếm cho đúng: trong 48 dòng đó có 1 tệp dùng chung không chứa phép thử nào, nên số phép thử có tên là 47. Tôi để nguyên con số 48 ở đây vì đó là con số bạn sẽ thấy nếu bạn tự chạy — và tôi khai luôn phần dư ra để bạn không phải tự phát hiện.
Còn đây là chỗ tôi bị chính cái máy của mình dạy cho một bài.
Hôm 16/08 tôi ghi vào tài liệu con số thông lượng đo được. Sáng nay tôi chạy lại, cùng mã đó, cùng máy đó, không sửa một dòng nào:
Tôi để cả 2 con số ở đây, kể cả con số làm tôi trông kém đi, vì đó chính là điều bài này muốn nói:
Thông lượng dao động. Tính đúng thì không.
Bộ dọn rác của máy ảo JavaScript vừa tạo đuôi trễ vừa làm thông lượng trồi sụt. Đây cũng chính là lý do lõi khớp lệnh của một sàn nghiêm túc không viết bằng JavaScript — và tôi ghi điều đó vào tài liệu của mình thay vì giấu đi, vì bất kỳ ai chạy lại cũng sẽ thấy.
Nếu bạn tải lõi này về và chạy bench, con số thông lượng của bạn sẽ khác của tôi. Con số lượt khớp và số bút toán của bạn sẽ giống hệt. Cái dao động là cái không đáng đem đi hứa. Cái trùng khít mới là cái đáng đem đi đối soát.
Cái giá của việc không mất lệnh khi mất điện
Còn một con số tôi nghĩ ít người ngoài nghề từng nhìn thấy, và nó là một quyết định kinh doanh chứ không phải quyết định kỹ thuật.
Ép từng lệnh xuống mặt đĩa trước khi trả lời khách: khoảng 2.000 tới 2.900 lệnh mỗi giây.
Gom nhóm 100 lệnh rồi mới ép xuống: khoảng 40.000 tới 48.000 lệnh mỗi giây.
Chênh nhau khoảng 20 lần. Và cái giá của 20 lần đó rất cụ thể: gom nhóm 100 nghĩa là chấp nhận mất tối đa 100 lệnh cuối khi điện tắt đột ngột.
Ai chọn con số đó? Không phải người viết mã. Đó là câu hỏi của người chịu trách nhiệm với khách hàng. Nên lõi của tôi để hệ số này thành một tham số và không chọn hộ.
Tôi nghĩ đây là chỗ nhiều người hiểu sai về phần mềm tài chính. Người ta tưởng nó là bài toán "làm cho nhanh". Nó là bài toán "quyết định đánh đổi cái gì lấy cái gì, rồi chịu trách nhiệm về lựa chọn đó".
Đọc lại Điều 8 sau khi đã dựng xong
Bây giờ quay lại câu hỏi mở đầu.
Điều 8 Nghị quyết 05/2025/NQ-CP đặt điều kiện để một tổ chức được cấp phép cung cấp dịch vụ tài sản mã hoá. Tôi đọc lại nó một lượt nữa sau khi đã dựng xong lõi, và lần này tôi đọc ra thứ khác.
Điều 8 đòi vốn điều lệ đã góp tối thiểu 10.000 tỷ đồng, góp bằng Đồng Việt Nam. Đòi ít nhất 65% do tổ chức nắm, trong đó trên 35% đến từ ít nhất 2 tổ chức thuộc nhóm ngân hàng, chứng khoán hoặc quản lý quỹ. Đòi tổng giám đốc tối thiểu 2 năm kinh nghiệm tài chính hoặc ngân hàng, giám đốc công nghệ tối thiểu 5 năm công nghệ thông tin. Đòi hệ thống đạt an toàn cấp độ 4. Đòi dữ liệu đặt trên máy chủ tại Việt Nam và giữ tối thiểu 10 năm.
Đọc hết cả điều, tôi không tìm thấy một dòng nào đo tốc độ khớp lệnh.
Không có ngưỡng lệnh mỗi giây. Không có trần độ trễ. Không có chỉ tiêu thông lượng nào cả.
Và sau 2 tuần ngồi với cái lõi này, tôi hiểu vì sao. Tốc độ là phần rẻ nhất. Nó là phần một người ngồi 2 tuần với một cái máy tính có thể chạm tới. Nó cũng là phần dao động 2 lần giữa 2 buổi sáng, nên nó là phần tệ nhất để viết vào luật.
Thứ Điều 8 đo là thứ khác hẳn: khả năng chịu trách nhiệm khi máy sai.
10.000 tỷ đồng đã góp không mua phần mềm. Nó là câu trả lời cho câu hỏi "lấy gì đền" — khi tệp nhật ký cụt mất một khúc lớn hơn một lệnh, khi ai đó sửa được nhiều hơn một chữ số, khi cái máy tỉnh lại với một cuốn sổ khuyết. 2 năm kinh nghiệm của tổng giám đốc và 5 năm của giám đốc công nghệ không mua tốc độ; chúng mua người biết chọn con số 100 kia và dám ký tên dưới lựa chọn đó. 10 năm lưu trữ không mua hiệu năng; nó mua khả năng phát lại được một phiên giao dịch của năm 2026 vào năm 2035.
Có một điều khoản nữa mà tôi chỉ thật sự hiểu sau khi tự dựng sổ cái: Điều 15 khoản 2 điểm a buộc tổ chức cung cấp dịch vụ phải quản lý tách biệt tiền và tài sản mã hoá của khách hàng với tiền và tài sản của chính mình. Trên giấy đó là một dòng. Trong mã, đó là toàn bộ cách dựng sổ cái hai vế với các ngăn riêng — thứ tôi mất phần lớn thời gian để làm cho đúng, và là thứ mà bất biến số bốn ở trên tồn tại để canh.
Còn thiếu gì — tôi viết đủ, không giấu
Lõi này chưa phải một cái sàn, và khoảng cách còn xa. Đây là danh sách đầy đủ:
Điều tôi không nói trong bài này
Tôi không nói lõi này đủ để mở một cái sàn. Danh sách ngay bên trên là câu trả lời.
Tôi không nói sàn nào trong số các ứng viên đang xin phép dựng tốt hay dựng dở. Tôi chưa nhìn thấy mã của họ, và sẽ không nhìn thấy.
Tôi không đưa ra lời khuyên mua, bán hay nắm giữ bất kỳ tài sản nào, và không giới thiệu bất kỳ nền tảng giao dịch nào.
Việc nào phải soi vào hoàn cảnh riêng của bạn thì việc đó cần một luật sư hành nghề ngồi xuống cùng bạn với đầy đủ giấy tờ. Tôi làm phần trước đó: mở bản gốc ra đọc, dẫn đúng số hiệu điều khoản, rồi kể lại thứ tôi đo được bằng chính tay mình — để lúc gặp luật sư, bạn biết mình cần hỏi điều gì.
Tóm lại
2 tuần dựng một cái lõi dạy tôi 3 điều mà 10 lượt đọc văn bản không dạy được.
Thứ nhất, phần khó của một sàn không nằm ở chỗ khớp lệnh nhanh. Nó nằm ở chỗ cuốn sổ phải đúng sau khi điện tắt, và phải chứng minh được là mình đúng.
Thứ hai, con số dao động thì không đáng đem đi hứa. Tôi vừa tự bắt mình ở đúng chỗ đó: một con số tôi ghi hôm 16/08 sáng nay đo lại chỉ còn chưa tới một nửa. Còn con số trùng khít cả 4 lượt chạy thì mới là con số đem đi đối soát được.
Thứ ba, 10.000 tỷ đồng trong Điều 8 không phải cái giá của phần mềm. Nó là cái giá của việc đứng ra chịu trách nhiệm khi phần mềm sai — và sau khi tự tay làm cái máy đó sai bằng cách rút điện của nó, tôi thấy con số ấy hợp lý hơn nhiều so với lần đầu tôi đọc.
Kim đồng hồ đang ở đâu: tính tới sáng nay 18/08/2026, chưa tổ chức nào cầm giấy phép trong tay. Còn 14 ngày nữa Nghị định 284/2026 có hiệu lực.
node --test: 48 dòng, trong đó 47 là phép thử có tên.
Chúc bạn khai ngộ
Master Xuân Hùng
P.s: Sống trong mục tiêu.
Từ mục tiêu, con đường sẽ hiện dần ra.