前にできなかったやつが色々試したらできた。でもまだ送金はできない。

ブツ

実行

REPO=Html.MpurseAPI.CounterPartyAPI.CreateSend.20220928164021
git clone https://github.com/ytyaru/$REPO
cd $REPO/docs
./server.sh

PythonでHTTPSローカルサーバを立ててMpurse APIを実行する。

結果

開発者ツールのコンソールを開くと以下のような感じになる。

以下のようにcreate_send APIを実行する。

const cpParams = {
    source: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    destination: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    asset: 'MONA',
    quantity: 11411400,
    memo: null,
    memo_is_hex: false,
    fee_per_kb: 10 * 1000,
    allow_unconfirmed_inputs: true,
    extended_tx_info: true,
    disable_utxo_locks: true,
};
await window.mpurse.counterParty('create_send', cpParams);

その結果、以下のようなJSONが返ってくる。

{
    "btc_change": 101556960,
    "btc_fee": 2250,
    "btc_in": 112970610,
    "btc_out": 11411400,
    "tx_hex": "0100000001737a59194d5705b49f8e7c262d97d5cfd1e31ba5f6a7590402634bcbd71c53e9010000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888acffffffff02c81fae00000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888ace0a20d06000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888ac00000000"
}

これで送金するためのトランザクションが作成された、ということになるのだろうか? よくわからない。

このあと送金するためには何をどうしたらいいのだろう。たぶんtx_hexを使って署名したあと、サーバにブロードキャストする。そういうキーワードだけは聞きかじっているから、いかにも理解しているふうに書いているが、何をどうするかまったく理解できてない。日本語では書けてもコードで書けない。

とにかく今回はcreate_send APIが動かせたという話。

経緯

Mpurseは内部でmpchain APIを呼び出していた。mpchain APIcounterParty APIcounterBlock APIを呼び出していると思う。それを使って送金しているのだと思われる。その工程のうちのひとつにcounterParty APIcreate_sendがある。

ブラウザでHTTPSサイトにアクセスし開発者ツールのコンソールで以下コードを実行する。

const cpParams = {
    source: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    destination: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    asset: 'MONA',
    quantity: 0.00000000,
    memo: null,
    memo_is_hex: false,
    fee_per_kb: 0,
    allow_unconfirmed_inputs: true,
    extended_tx_info: true,
    disable_utxo_locks: true,
};
await window.mpurse.counterParty('create_send', cpParams);
//const unspentTxouts = await window.mpurse.counterParty('create_send', cpParams);

以下のようなエラーが返ってきた。

{
    "code": -32000,
    "data": {
        "args": [
            "{\"message\": \"Error composing send transaction via API: Destination output is dust.\", \"code\": -32001}"
        ],
        "message": "{\"message\": \"Error composing send transaction via API: Destination output is dust.\", \"code\": -32001}",
        "type": "Exception"
    },
    "message": "Server error"
}

以下のようなコメントをいただいた。

トランザクションを作成する機能は counterparty-server 側の API にあります。counterblock にはありません。 API で無事トランザクションが生成されても無署名なのでブロックチェーンに投げ込んでも、無効扱いされ、手持ちの MONA は消えません。秘密鍵による署名が完了するまでは気軽に試しても問題ありません。testnet の API は存在しますが、testnet の MONA の入手は難しく、mainnet で試したほうが手間が少ないと思います。

create_sendしても手持ちのモナコインが減ることはない。だから好きなだけcreate_sendで動作確認すればいい。そういう意味だと捉えた。

前回はそれ以来なにもしてなかったが、今回いろいろ々試して成功した。

試行錯誤ログ

蛇足。試行錯誤したときのメモをほぼそのまま書いてあるのでまとまってない。

気づいたこと

前回のJSON結果を改めて読み直すといくつか気づいたことがあった。

  • Destination output is dust
    • quantity: 0.00000000,
    • fee_per_kb: 0,

エラーメッセージによると「出力がゴミ」と言ってる。何のことだかよくわからないがググってみると手数料が少なすぎるせいらしい。fee_per_kbの値を上げたらいいのでは? でも出力ってたぶんquantityのことでは? そっちを上げるべきか? いずれにせよどちらも0なのが悪いのでは? 署名してないから手持ちのモナが減ることはないらしいので、安心してテストできるはず。色々数値を変えて試してみよう。

また、quantityは少数値でなく整数値で入力するのが正しい気がする。以前トランザクションデータのどこかでそんなのを見た。つまり最小値0.00000001 MONAquantity: 1で表す。counterParty APIcreate_sendをみてみると引数説明のところにquantities-and-balancesリンクがあって、そこに書いてあった。

試してみた

以下2点を改善して実行してみたら成功した!

  • quantity: 114114000.11411400 MONA
  • fee_per_kb: 10(Mpurseで設定できる最小値と同じ?)
const cpParams = {
    source: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    destination: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    asset: 'MONA',
    quantity: 11411400,
    memo: null,
    memo_is_hex: false,
    fee_per_kb: 10,
    allow_unconfirmed_inputs: true,
    extended_tx_info: true,
    disable_utxo_locks: true,
};
await window.mpurse.counterParty('create_send', cpParams);
{
    "btc_change": 101559208,
    "btc_fee": 2,
    "btc_in": 112970610,
    "btc_out": 11411400,
    "tx_hex": "0100000001737a59194d5705b49f8e7c262d97d5cfd1e31ba5f6a7590402634bcbd71c53e9010000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888acffffffff02c81fae00000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888aca8ab0d06000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888ac00000000"
}

ちなみにquantity11000だとDestination output is dustエラーになった。0.00001000 MONAでもダメらしい。最小値0.00000001 MONAを支払額にすることはできないようだ。なんてこったorz。使える最少額っていくらなんだろう。そこまで細かくは調べなかった。成功した
11411400以下で、かつ失敗した1000より大きい値なのだろう。

出力結果の解析

この出力結果おかしくない? まずbtcってビットコインのことだと思う。モナコインなんですけど? asset: 'MONA',で指定したんですけど?

あとbtc_fee2だったらbtc_in11411402になるんじゃないの? 112970610が送金額11411400の10倍以上なんですけど。これって送金者が112970610出して支払先アドレスに11411400だけ支払ったあと、残った101559210と手数料2の合計101559212は全額マイナーにトランザクション承認処理のインセンティブとして支払われちゃうのでは? (※ちがう。後述するがbtc_changeをみてない) さすがに大金すぎない? Mpurseの手数料はそんなにバカ高くなかったはず。

考えられる可能性としては以下2つ。

  • 私の読み方がまちがっている
  • 手数料の設定にコツがいる

Mpurseのソースコードsend.component.ts#L301を読んでみた。手数料を設定しているのは以下の部分。

new Decimal(this.feeControl.value)
.times(new Decimal(1000))
.toNumber(),
  • this.feeControl.valueはMpurseのUIにある手数料を決めるスライダーの値。デフォルトは101で最小10になるやつ
  • Decimalは正確な浮動小数点で計算するための外部ライブラリだと思う
  • Decimal.times()は乗算。つまり101などの値を1000倍している
  • Decimal.toNumber()で数値化する

Decimalの情報源は以下。

というわけで、Mpurseに出てくる手数料101の最小値101000倍してみる。

const cpParams = {
    source: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    destination: 'MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu',
    asset: 'MONA',
    quantity: 11411400,
    memo: null,
    memo_is_hex: false,
    fee_per_kb: 10 * 1000,
    allow_unconfirmed_inputs: true,
    extended_tx_info: true,
    disable_utxo_locks: true,
};
await window.mpurse.counterParty('create_send', cpParams);
{
    "btc_change": 101556960,
    "btc_fee": 2250,
    "btc_in": 112970610,
    "btc_out": 11411400,
    "tx_hex": "0100000001737a59194d5705b49f8e7c262d97d5cfd1e31ba5f6a7590402634bcbd71c53e9010000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888acffffffff02c81fae00000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888ace0a20d06000000001976a91445fc13c9d3a0df34008291492c39e0efcdd220b888ac00000000"
}

お、btc_feeの値が2250になった。これはMpurseの手数料スライダーで10にしたとき表示される0.00002250 MONAの整数値版2250と一致する。

それはいいのだが、btc_inがおかしくない? 入力inは出力outと手数料feeの合計以上になる値だと思うのだけど。つまり11411400 + 225011413650となり、それ以上の額を入力inとして指定するのが正しいのでは?(※まだ勘違いしてる)

out + fee <= in
11411400 + 2250 = 11413650 <= in

と思ったのだが、btc_changeとかいう謎の値がかかわっているっぽいことに気づいた。

btc_change = btc_in - (btc_out + btc_fee)
101556960 = 112970610 - (11411400 + 2250)

いや、謎じゃなかった。btc_changeは「おつり」のことだった。英語でおつりのことをchangeというらしい。そして計算してもその意味として理解できる。

でも、なぜbtc_inをそんな巨額にする必要があるのかわからない。11411400 + 225011413650となり、おつりゼロのほうがよいのでは? そもそもbtc_in112970610という値はどこからやってきたの? それを計算するための引数があるのかな?

引数disable_utxo_locksが関係しているかとあたりをつけた。これだけがよくわからないし変数になってたので思って調べた。MpurseのソースコードからdisableUtxoLocksで検索[]してコードを色々と眺めてみた。

create_sendするときはtrueであり、mpchain APIsend_txするときはfalseらしい。ええと、UTXOは未使用トランザクションのことで、そのロックを無効化するフラグがtrueってことは、create_sendするときは未使用トランザクションのロックを無効化するってことか。で、未使用トランザクションのロックって何?

よくわかんない。たぶんAPI側が勝手にやったことだと思う。送金者アドレスが持っている未使用トランザクションの中から手数料+送金額以上のトランザクションを取得したのだろう。その金額がbtc_in112970610だったのではないかと予想する。

まあいいや。とにかくcreate_sendできた。

所感

create_sendまでできたのはいいとして、このあとどうしたら送金できるのか。まだまだ先は長そう。