20 января 2014 в 23:41

Неподтвержденная транзакция, или Возвращаем криптовалюту из небытия

Волею судеб довелось мне иметь дело с криптовалютами. Не то что бы плотно работаю с ними, но иногда то отправлю монетки, то получу. Скажем так, понемногу прощупываю новую сферу изнутри.

И вот однажды беда приключилась. Отослал я криптомонетки, а до получателя они не дошли. Собственно, пост о том, как средства возвращались. Ну и размышления и советы по сложившейся ситуации на десерт. Сразу отмечу, что нижесказанное применимо не к какой-то конкретной валюте, а к большинству форков (если не ко всем).

Начну с того, что пост местами пронизан догадками относительно происходящего. Где-то я могу ошибаться. Объективные поправки и дополнения только приветствуются.

Немного о терминологии.
  • Кошельком буду называть файл wallet.dat.
  • Публичный ключ (адрес, на который перечисляются средства, например) временами тоже удобнее назвать кошельком, но, во избежание путаницы, пусть он будет просто номером счета.
  • Комиссия — Transaction Fee. Называть эту штуку комиссией, я считаю, не совсем правильно, но это наиболее привычный и не режущий слух вариант, потому пусть будет комиссией.
  • Размер транзакции — размер блока данных, в котором содержится вся информация о транзакции.

Ко всей этой криптовалютной кухне я изначально подошел как типичный юзер — не особо вникая в систему. Установил, запустил, работает — и ладно. Иногда при попытке послать куда-то средства клиент выдавал сообщение вроде «Размер транзакции слишком велик, нельзя просто взять и послать ее. Но вы можете добавить комиссию в размере N, и тогда все будет хорошо» — я соглашался с добавлением комиссии, и все действительно было хорошо.

Казалось бы, если меня просят дополнительно заплатить, когда это нужно, значит я и буду платить, когда попросят (если комиссия будет устраивать). Это и было главной ошибкой.

Делаю очередной перевод на сумму значительно крупнее, чем обычно. Средства со счета уходят, предложения заплатить комиссию не было и… Ничего. До получателя средства не доходят, статус транзакции «0/Не подтверждено». И такую картину я наблюдал больше недели, попутно перегугливая и перечитывая интернеты в поисках информации по решению подобной проблемы. Причем искал и для конкретной криптовалюты, и в целом для всех — проблем куча, решения нет.

А, собственно, что же это за комиссия? Идея в том, что транзакции могут проходить без комиссии, но только в случае соблюдения некоторых условий:
  • Размер транзакции должен быть не больше определенной величины.
  • Переводимая сумма должна быть больше некоего порога.
  • Транзакция должна обладать достаточным приоритетом.

Если первые два пункта более-менее понятны (конкретные величины приводить не стал, полагаю, они могут варьироваться от форка к форку), то в третьем вся загвоздка. Грубо говоря, транзакции при создании попадают в очередь, отсортированные по приоритету. При генерации очередного блока в него включаются транзакции с комиссией (которая идет в награду тому, кто нашел блок), а также транзакции без комиссии с наиболее высоким приоритетом.

Сам приоритет прямо зависит от объема переводимых средств и от числа их подтверждений (чем дольше именно эти деньги лежали у вас на счету, тем больше должен быть приоритет, по сути) и обратно зависит от размера транзакции. Таким образом, при неудачном стечении обстоятельств транзакция без комиссии может оказаться в зависшем неподтвержденном состоянии, с которым я и столкнулся.

Поиски решения показали, что это сравнительно частая для криптовалют проблема. К сожалению, все советы сводились к нижеперечисленному и часто не помогали:
  • Подождите сутки-двое, вашу транзакцию включат в очередной блок.
  • Подождите сутки-двое, программе-клиенту надоест ждать, и она сама отменит транзакцию.
  • Загрузите заново всю цепочку блоков.
  • Запустите клиент с каким-нибудь волшебным ключом (-rescan / -reindex / -salvagewallet).

Ждал я больше недели. Ни в какой блок транзакцию не включили. Даже после повторных отправок через sendrawtransaction. Блокчейн говорил, что о той транзакции ничего не знает, и на счету лежат те самые средства, никуда они не ушли. И только клиент стоял на своем: «Я транзакцию отправил, дальше как хочешь. Уже потраченными деньгами распоряжаться не позволю».

Итак, в чем же суть проблемы? Транзакция не попала в блок и уже не попадет. В кошельке хранится информация о том, что транзакция в общем-то была, поэтому средства, которые должны были с ней отправиться, недоступны для использования. Возможно, спустя еще какое-то время транзакция будет отменена, на этот счет у меня несколько предположений:
  • Зависит от валюты, где-то быстро отменяется, где-то нужно ждать долго.
  • Баг конкретного клиента.
  • Информация об отмене неверна.

В любом случае, неделя — это достаточно большой срок. Если за это время не прошло само, то есть основания полагать, что и не пройдет.

Как нормальный человек, после того, как все уже сломалось, я наконец-то пошел читать мануалы. Попытка повторить транзакцию с включением в нее комиссии потерпела фиаско. Зато бесконечные прыжки по ссылкам между форумами (в выдаче гугла ничего стоящего не нашлось) довели меня до некоего поста, где и нашлась дельная подсказка. К сожалению, теперь найти этот пост не удается, даже не знаю, что был за форум. Полагаю, некий околобиткойновый.

Решение буду описывать на примере клиента, наиболее распространенного для форков, известного как Satoshi Client. Насколько понимаю, оно применимо и к прочим клиентам, но, возможно, со своими нюансами.

Несмотря на простоту и очевидность решения, к нему мало кто приходит, судя по многостраничным веткам форумов. Средства на счете блокируются кошельком, и все, что требуется, это получить доступ к счету вне текущего кошелька.

Итак, если транзакция зависла и не имеет подтверждений:
  1. Проявите терпение. Не поднимайте сразу панику. Подождите пару дней, вдруг и правда само пройдет.
  2. Убедитесь, что транзакция зависла. Зайдите в Block Explorer (обычно гуглится по запросу «blockchain %названиекриптовалюты%») и проверьте, что про зависшую транзакцию там ничего не знают, а на счете деньги на самом деле есть.
  3. Перейдите в отладочную консоль (Помощь — Окно отладки — Консоль)
  4. Если кошелек зашифрован (он же зашифрован?), то для начала необходимо получить доступ, используя команду walletpassphrase <passphrase>.
  5. Теперь нужно получить приватный ключ от нужного счета. dumpprivkey <address>. Вместо <address> нужно подставить публичный номер счета, на котором лежат заблокированные средства. В ответ получите приватный ключ данного счета. Его нужно куда-нибудь скопировать, он еще понадобится. Если средства для транзакции брались с нескольких счетов, то и импортировать нужно их все. И да, никогда не храните приватные ключи в доступном для кого-либо месте. Знание ключа дает полный доступ к соответствующему ему счету.
    Обратите также внимание на то, что на каждую команду в отладочной консоли приходит ответ. Он может быть пустым, но он есть всегда. Позже будет понятно, к чему я это.
  6. Закройте клиент и удалите кошелек. Расположение кошелька (wallet.dat) зависит от конкретного клиента и ОС. Естественно, совсем удалять его не стоит, лучше переименовать или переместить в надежное место.
  7. Запустите клиент заново. Создастся новый кошелек. В него необходимо импортировать полученный ранее ключ (ключи). Идем в отладочную консоль и пишем importprivkey <privkey>. Импорт может производиться достаточно долго. Позвольте ему завершиться — дождитесь получения ответа на команду.
  8. В новом кошельке должен появиться счет с реальным его состоянием. Для надежности можно перезапустить клиент с ключом -rescan, но, полагаю, это уже лишнее. Ранее заблокированные средства снова доступны для отправки, шлите их заново, на этот раз не забудьте включить комиссию. (есть важные дополнения по этому пункту в upd3)
  9. Если на старом кошельке остались прочие используемые и важные счета, можно снова вернуться к нему.

Таким нехитрым образом удалось вернуть криптомонетки. Хотя я уже и начал думать, что они потеряны совсем. В общем, не надейтесь на чудо, перед использованием нового клиента убедитесь, что комиссия всегда включена. Рекомендуемый размер комиссии, думаю, лучше искать на ресурсах, посвященных используемой криптовалюте.

Upd: Необходимость описанной операции зависит от используемого клиента (а так как у многих криптовалют по одному клиенту, то и от используемые валюты). Похоже, в некоторых случаях информация о транзакции записывается не в файл кошелька, а только в локальную цепочку блоков. При таком раскладе может помочь удаление имеющейся цепочки или запуск клиента с ключами.

Upd2: Значительно упростить описанный процесс переноса счетов в новый кошелек может ключ -salwagewallet, уже упомянутый ранее. При запуске клиента с этим ключом создается новый wallet.dat, в который импортируются все счета из старого, а история транзакций к нему берется из цепочки блоков (за описание спасибо grich). К сожалению, запуск с данным ключом реализован не во всех клиентах.

Upd3: Если после импорта перечислить не всю сумму, имеющуюся на счету, то часть средств (сдача от используемых выходов) будет перечислена на другой счет нового кошелька. Учитывайте это, если планируете вернуться к старому кошельку:
  • Импортировав счет в новый кошелек, переведите все средства с него на другой свой счет, вернитесь к старому кошельку и после этого уже распоряжайтесь возвращенными средствами.
  • После совершения транзакции с нового кошелька определите, на какой счет упала сдача, и импортируйте этот счет в старый кошелек.
Владлен Грачев @gwer
карма
41,0
рейтинг 0,0
Пользователь
Самое читаемое Разработка

Комментарии (32)

  • +2
    Была та же проблема, когда пересылал кварки на криптси… Просто удалил все файлы в директории \AppData\Roaming\Quarkcoin\chainstate, запустил кошель. Он закачал по новой базу данных транзакций и монетки вернулись. Это куда быстрее, чем описанные выше пункты).

    Фишка в том, что о не отправленной транзакции знает только кошелек. Удаляя базу данных транзакций мы чистим и ту не отправленную операцию по переводу. Кошель качает у людей базу и не находит отправки- коины возвращатся назад.
    • +1
      В списке часто рекомендуемых операций это было (загрузить заново всю цепочку блоков). Пробовал, даже не раз. Не помогло.

      Я так понимаю, информация о транзакции хранится в wallet.dat. Вероятно, есть зависимость от клиента.
  • +1
    Плохо, что нет в апи метода resendtransaction. Этот ручной метод совсем не подходит для автоматизированных систем.

    Как раз вчера в одной из криптовалют скрипт отправил несколько переводов, 1 дошел, 4 висят в мемори пуле на клиенте. Каждый раз вручную так проталкивать — сумасшествие.
    • +1
      Насколько я понял, эту роль выполняет связка getrawtransaction <txid> / sendrawtransaction <hex>. Но это лишь в теории, мне оно не помогло. Возможно, просто очень «повезло».
      • 0
        Мне тоже не помогло, TX rejected.
        • 0
          Странно. Конкретно при данной операции у меня TX rejected вылезал только когда я полученный HEX случайно прогнал через signrawtransaction, а уже результат кинул в sendrawtransaction. Если переотправлять результат в чистом виде, должен быть возвращен либо номер транзакции, либо «transaction already in block».
        • 0
          Факт того, что на локальном компьютере мы «забыли» о транзакции (импортировали приватный ключ в девственный валлет) ничего не говорит о том, что об этой транзакции забыли наши пиры и вообще вся биткоин-сеть. Я нигде не нашел данных о том, сколько времени в мемори-пулах на узлах сети хранится низкоприоритетная транзакция, которую никто не хочет включать в блок. Но, как правило, речь идет о таких копейках, которые не стоят того, чтобы о них задумываться.
          • 0
            Фактически же, если новая транзакция, отсылающая «зависшие» средства, попала в блок, то даже если вдруг проблемная транзакция отвиснет, она просто отмениться должна.
            • 0
              Велик и могуч русский язык (впрочем, в данном случае я думаю и в английском такая же ситуация).
              Словосочетание «если транзакция попала в блок» можно понять как «транзакция заблокирована, застопорена, посажена в тюрьму», так и «транзакция подтверждена, проведена, закреплена в блокчейне».
              Правильное толкование в данном случае — второе.
              Вопрос мой в том, что «вторая транзакция» может попросту не дойти до майнеров, потому что ее не будут распространять узлы сети, имеющие в мемори-пулах первую транзакцию. А первая транзакция не будет попадать в блокчейн, потому что она низкоприоритетная.
              • 0
                Полагаю, что в контексте криптовалют попадание в блок всегда должно подразумевать именно положительное явление.

                Вопрос интересный и хотелось бы получить разъяснение его от компетентных лиц. Мнения встречал противоречивые по этому поводу. Где-то говорили, что достаточно просто отправить транзакцию, которая потратит зависшие деньги, якобы она нормально уйдет, а зависшая будет отменена. С другой стороны, описание схем даблспенда подразумевало наличие своих значительных мощностей, что говорит об ошибочности слухов о простой отмене.
                • 0
                  Эта тема регулярно всплывает в разных местах. Например, вот тут bitcointalk.org/index.php?topic=232979.0
                  Честно говоря, шерстить код и проверять сложно, тем более, что на обычных узлах сети и у майнеров софт может отличаться.
                  Да и со временем логика работы с низкоприоритетными транзакциями может меняться.
                  Повторю: проблема с зависшими транзакциями на сегодняшний день возникает только в случае экстра-малых переводов и не стоит того, чтобы тратить на это время.
                  • 0
                    Что вы подразумеваете под экстра малыми переводами? В моем случае завис перевод на 113к, что в рублях порядка 7к. Все-таки экстра-малые, в моем понимании, это несколько рублей. Или хотя бы в пределах сотни.

                    Что послужило причиной зависания в данном случае — не знаю. Средства лежали на счете не первый день и обросли подтверждениями. Была ли это ошибка сети, ошибка клиента или неизвестные параметры в формуле приоритета — не знаю. Но факт остается фактом.
  • +4
    Я для этого когда-то написал небольшую программку, позволяющую просмотреть хранящиеся в кошельке транзакции и удалить ненужные (или все). Если интересно, могу поделиться.
    • 0
      Конечно, интересно. Структуру файла сами изучали? Не встречал толковой спецификации по содержимому. Выпилить из кошелька информацию — это была первая идея, но до нее так и не дошло.
      • +1
        Структура взята из оригинальных исходников (да и код большей частью оттуда подёрган), там вполне себе Berkeley DB. Программа при сборке соответственно требует libdb4, ну и libssl. Удобств никаких, делал для себя, уж извините.
        Расшарил на гуглодрайве, голый исходник. Собирать GCC.
        UPD: кой-чего не хватает на ссылку, вот голый адрес: drive.google.com/file/d/0B7jd_pg52CH7RXcyT2FVT3lWaUk/edit?usp=sharing
    • 0
      Нельзя сразу подозревать плохое :) Поставил плюсик.
      • 0
        Программа в исходнике, подозревайте на здоровье. Конечно, слегка говнокод, но понять, что она делает, вполне можно.
  • 0
    Была обратная проблема. При маленьком платеже стандартный биткоин клиент требует комиссию.
    Погуглил — понял что это как раз чтобы избежать проблемы как у автора поста, и кстати другие клиенты могут не требовать комиссию.
    Получается для микроплатежей биткоин счас не очень то подходит. Скажем в моём случае при платеже $2 или $5 комиссия $0.1 (5+ процентов).
    • 0
      Переводимая сумма должна быть больше некоего порога.

      Такое ограничение выступает в качестве средства борьбы с транзакционным спамом.

      На микроплатежи якобы был ориентирован лайткойн. Но слышал, что у него порой комиссия выходит еще больше.
  • 0
    Хм. Я всегда думал, что в кошельке хранятся только приватные ключи, а все остальное — в цепочке блоков. Оказывается, что там хранится немного больше информации:

    keypairs for each of your addresses
    transactions done from/to your addresses
    user preferences
    default key
    reserve keys
    accounts
    a version number
    Key pool
    Since 0.3.21: information about the current best chain, to be able to rescan automatically when restoring from a backup.

    Я думаю, что хранение транзакций в кошельке — это, своего рода, оптимизация. Т.к. мы можем в новый пустой кошелек импортировать только ключи и баланс «найдется», т.е. будет получен из цепочки блоков.

    А к Вам у меня вопрос. Вы пишете:

    Теперь нужно получить приватный ключ от нужного счета. dumpprivkey <address>. Вместо <address> нужно подставить публичный номер счета, на котором лежат заблокированные средства.

    Как Вы узнали нужный адрес? Опять же, насколько я знаю, при отправке средств у транзакции может быть несколько входов — т.е. Ваши деньги будут идти с нескольких счетов.
    • 0
      Я думаю, что хранение транзакций в кошельке — это, своего рода, оптимизация. Т.к. мы можем в новый пустой кошелек импортировать только ключи и баланс «найдется», т.е. будет получен из цепочки блоков.

      Получается, что так и есть. Именно поэтому в некоторых случаях достаточно обновить цепочку блоков на актуальную.

      Как Вы узнали нужный адрес? Опять же, насколько я знаю, при отправке средств у транзакции может быть несколько входов — т.е. Ваши деньги будут идти с нескольких счетов.

      У меня всего пара счетов, и я знал, на каком были средства. Для надежности проверил в блок эксплорере, что средства именно там, где я думаю.

      Но вообще, определить счет возможно. Входами транзакции являются не счета, а другие транзакции. Если счетов много, и неизвестно, какой надо импортировать, можно сделать следующее:
      1. Получить «расшифрованную» транзакцию. Варианта два:
        • getrawtransaction <txid> 1.
        • Результат getrawtransaction <txid> передать в decoderawtransaction.
      2. Взять txid всех транзакций, являющихся входами для проблемной транзакции и пройтись по ним командой gettransaction <txid>. В поле address содержится счет, на который переводились средства в данной транзакции. Соответственно, полученные счета и являются нужными.
      3. ???????
      4. PROFIT
      • 0
        У меня всего пара счетов, и я знал, на каком были средства.

        Вы при приеме денег используете один и тот же адрес? И как же быть со сдачей, ведь она приходит на новый адрес?

        Но вообще, определить счет возможно. Входами транзакции являются не счета, а другие транзакции. Если счетов много, и неизвестно, какой надо импортировать, можно сделать следующее:

        Я задал вопрос потому, что способ который Вы описали в статье, подходит не всем. Например, если человек пользуется обычным клиентом и особо не вникает в детали, то у него будет 100500 адресов по которым размазан его баланс. И тут простое «Теперь нужно получить приватный ключ от нужного счета...» не пройдет.
        • 0
          В данном случае все средства были на одном счете.

          Если счетов много, придется импортировать все. При автоматизации процесса он должен пойти веселее. Также остается вариант с удалением локальных упоминаний транзакций (перезагрузка блоков, правка кошелька). Ну и можно совершать профилактическую дефрагментацию кошелька, периодически переводя средства на один счет, хоть и полезность этого сомнительна.

          Пока что использовать криптовалюты, не вникая в детали, — не лучшая затея. Это все еще довольно специфичная штука.
  • +2
    Вы даете очень плохой совет в общем случае.

    Кошелек в bitcoin и форках устроен так, что он одновременно хранит пул из 100 секретных ключей, с каждым из которых связан свой адрес и своя сумма денег (например, сдача с каждой тразакции всегда идет на новый неиспользованный адрес). Командой dumpprivkey вы получаете ОДИН приватный ключ и, удаляя wallet.dat, теряете информацию об остальных. Создав новый wallet.dat, вы получите 100 ДРУГИХ ключей и, импортировав один старый, восстановите средства только с него.

    Конкретно эту задачу можно решить разными способами. Один из простых — ключ запуска -salvagewallet, которая создаст новый файл wallet.dat, импортирует ВСЕ приватные ключи из старого файла и просканирует весб блокчейн.
    • 0
      После совершения транзакции с зависшими средствами можно вернуться к старому кошельку, об этом написано.

      Ключ -salvagewallet также упомянут в посте. Но он не во всех клиентах реализован. В моем случае его просто не было. Но информацией о том, что он делает, пост дополню. Спасибо.
      • 0
        После совершения транзакции с зависшими средствами можно вернуться к старому кошельку, об этом написано.

        Тоже плохо. Во-первых, «вернуться» — это значит снова просканировать весь блокчейн, чтобы кошелек был синхронизирован с блоками и транзакциями. Во-вторых, когда вы отправите с нового wallet.dat транзакцию, сдача пойдетна новый адрес, которого нет в старом файле. Придется возиться с импортом и этого ключа. Или постоянно переключаться и пересинхронизироваться между кошельками.

        Собственно, проблема в том, что оригинальный клиент не рассчитан на использование одних и тех же ключей в разных местах, поэтому любые манипуляции с wallet.dat чреваты неожиданными проблемами. Следует избегать их всегда, когда можно.

        Ключ -salvagewallet также упомянут в посте.

        Сорри, не заметил той строчки. Впрочем, она все равно неинформативна =) («Запустите клиент с каким-нибудь волшебным ключом (-rescan / -reindex / -salvagewallet»). Этот ключ, кстати, реализован больше года назад
        • 0
          В целом да. Проблема со сдачей решается отсылкой ее на один из своих счетов.

          Я понимаю, что описанное решение неидеально. Но, допустим, зависла транзакция. Информация о ней в кошельке, скачивание новой цепочки блоков ничего не дает. Ключ -salwagewallet в клиенте не реализован. Инструмента для выпиливания из кошелька информации под рукой нет. Как поступить?

          И есть вопрос насчет пула из 100 ключей в кошельке. 100 штук — это ограничение? Допустим, при создании кошелька в нем имеется 100 ключей. При импортировании в него нового ключа их там станет 100 или 101? Просто интересно, можно ли полноценно имитировать -salwagewallet, вытащив все ключи из старого кошелька.
          • 0
            Инструмента для выпиливания из кошелька информации под рукой нет. Как поступить?

            Ну мы все же не в пустыне. Можно этот инструмент скачать: по времени это сравнимо с пересканированием блокчейна. Особенно учитывая специфичность Вашего метода (заблокированы деньги только с одного адреса, других ключей нет).

            Кстати, Ваш способ я бы исправил так: на шаге 8 отправить ВСЕ доступные на адресе монеты сначала на один из своих адресов (можно даже на этот же). И после подтверждения транзакции вернуться к старому wallet.dat и пересканировать блокчейн. Потеряем только сумму комиссии.

            Вообще, суть моего недовольства не в самом способе или его отсутствиии, а в том, что на любых рецептах всегда должна находиться информация о побочных эффектах. Потеря денег иногда приводит к потере здоровья =)

            100 штук — это ограничение?

            Нет, это не лимит, а именно пул «неиспользованных адресов», генерируемый при создании wallet.dat. Они задействуются по мере необходимости (обычно — для получения сдачи). Как только не останется ни одного «чистого» адреса — будет сгенерировано еще 100. При этом старые ключи никуда не удаляются, даже если на соответствующих адресах пусто. Salvagewallet испортирует вообще все ключи, что есть.
            • 0
              Можно этот инструмент скачать: по времени это сравнимо с пересканированием блокчейна

              А можно подробнее? Первый и единственный на данный момент инструмент я встретил в комментарии к этому же посту. Не там искал? Или не то?

              Информацию об избежании проблем в пост добавил.
              • +1
                Ну, не знаю. Первый ответ по запросу «bitcoin delete transaction» — это bitcointalk.org/index.php?topic=35214.0 — наверное, самое сложное там — это установка python. Я знаю, что есть еще утилиты для работы напрямую с wallet.dat, уверен, что их можно найти по схожему запросу (еще ключевое слово — «unconfirmed tx»)
                • 0
                  Хм, этот топик я видел, но почему-то упорно пропускал его. Видимо решил, что решение сугубо для биткойна.
                  • 0
                    А, ну, конечно, в самом скрипте все пути прописаны для биткоина. Но удобство всех его незамысловатых форков в том, что достаточно сделать замену «bitcoin» -> «anycoin» в коде

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.