Глоссарий / Справочник разработчика
CompactSize
Другие названия: Compact size unsigned integer, беззнаковое целое CompactSize, VarInt.
- Что такое CompactSize?
- CompactSize представляет собой целое число переменной длины, которым биткоин предваряет счетчики и длины в сериализованных данных: один байт для значений ниже 253 и до девяти байтов для остальных.
Двоичные форматы биткоина не содержат имен полей, поэтому анализатору нужно сообщить, сколько входов следует дальше и какова длина каждого скрипта. CompactSize отвечает всего одним байтом: значения от 0 до 252 занимают только этот байт, а большие значения занимают маркер плюс еще два, четыре или восемь байтов. Каждый отправляемый вами платеж содержит не менее четырех таких значений.
Как это работает
CompactSize читает свой первый байт, чтобы решить, сколько байтов от него осталось.
Первый байт ниже 0xfd является самим значением, поэтому счетчики от 0 до 252 занимают один байт и ничего больше. Первый байт 0xfd означает, что значение находится в следующих двух байтах, 0xfe означает следующие четыре, а 0xff означает следующие восемь. Все эти последующие числа записаны в порядке little endian. Самая длинная возможная кодировка составляет девять байтов и может выразить значение выше 18 квинтиллионов, а ни одна реальная структура и близко не нуждается в таком.
Кодировки должны быть минимальными. Запись числа 5 как 0xfd 0x05 0x00 арифметически идентична записи 0x05, и Bitcoin Core отвергает ее, выдавая ошибку неканонической кодировки при десериализации. Без этого правила одну и ту же транзакцию можно было бы записать несколькими способами, каждый со своим идентификатором, а это проблема пластичности транзакций, а не аккуратности.
Существует практический потолок, намного ниже арифметического. Bitcoin Core отказывается выделять память для любого десериализованного объекта, который заявляет размер больше 0x02000000 байтов, около 33,5 мегабайта, а сетевой протокол отдельно ограничивает одно сообщение 4 000 000 байтами. Поэтому враждебный узел не может заставить ваш узел зарезервировать гигабайты памяти, объявив транзакцию с квинтиллионом входов.
Стоит отметить одну ловушку в названиях. Документация сетевого формата называет это var_int, библиотеки кошельков обычно называют это VarInt, а исходный код самого Bitcoin Core использует имя VarInt для совершенно другой и несвязанной кодировки, которая встречается только в его файлах базы данных и отмены. Читая код, проверьте, с чем из двух вы имеете дело.
Где это встречается
CompactSize является структурой, которая чаще всего встречается в биткоине и при этом вообще не имеет названия, видимого пользователю.
В сырой транзакции она появляется сразу после четырехбайтовой версии или после маркера segwit и флага, когда они присутствуют, и содержит счетчик входов. Она снова появляется перед каждым скриптом, чтобы задать его длину, и еще раз перед счетчиком выходов. В сериализованном блоке она содержит счетчик транзакций. В сетевом протоколе она открывает полезную нагрузку сообщений inv, getdata, addr и headers.
Сообщение headers показывает эту кодировку в самом странном виде. За каждым заголовком блока из 80 байт следует счетчик транзакций CompactSize, равный нулю, поэтому одна запись занимает 81 байт вместо 80, а полный пакет из 2000 заголовков занимает в сети 162 000 байт. Счетчик всегда равен нулю, потому что сообщение headers никогда не несет транзакций. Поле существует для того, чтобы один и тот же код разбора мог прочитать заголовок независимо от того, следует ли за ним тело блока.
Если вы вручную декодируете сырую транзакцию и поля на середине перестают иметь смысл, почти всегда причина в неверно прочитанном CompactSize. Если прочитать на один байт меньше, все последующее смещается.