Page MenuHomePhabricator

[ecash-wallet] Properly construct postage tx to support serialization and server deser
ClosedPublic

Authored by bytesofman on Mon, Oct 27, 11:24.

Details

Summary

The current approach works locally, but is impractical for real-world postage as we are not able to serialize a signatory and send it to a server.

The correct workflow is

  • Create a partially signed tx with defined outputs and no any required token inputs
  • Serialize this (so it can be sent to a server)

Then, the postage payer

  • Receives and deserializes the tx to a PostageTx
  • Calls addFuelAndSign to add postage and create a tx that will broadcast

There are some general postage "gotchas" that require either more iterative experience in this lib or are left to the discretion of the implementer. For example, the partially signed tx must have finalized outputs. So, it is not trivial for the postage payer to receive change, or to exactly hit a 1 sat/byte tx fee. This lib could be extended to support this, but it would not be a universal use case (sender needs to know postage servers utxo size, availability, change address, etc).

This diff makes postage usable for a client-server workflow, making it easy to build apps that use 0-fee token txs (or in-kind-fee token txs).

Test Plan

npm test, CI

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

Failed tests logs:

====== selectUtxos: Return success false and missing sats for a non-token tx with insufficient sats.Support functions selectUtxos Return success false and missing sats for a non-token tx with insufficient sats ======
AssertionError: expected { success: false, …(4) } to deeply equal { success: false, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:1061:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

           "Insufficient sats to complete tx. Need 1000 additional satoshis to complete this Action."
         ]
         "missingSats": "1000"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Return success true for a non-token tx with insufficient sats if NO_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with insufficient sats if NO_SATS strategy ======
AssertionError: expected { success: true, utxos: [], …(3) } to deeply equal { success: true, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:1081:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "1000"
         "requiresTxChain": false
      -  "satsStrategy": "NO_SATS"
         "success": true
         "utxos": []
       }
====== selectUtxos: Return success true for a non-token tx with insufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with insufficient sats if ATTEMPT_SATS strategy ======
AssertionError: expected { success: true, …(4) } to deeply equal { success: true, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:1102:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "200"
         "requiresTxChain": false
      -  "satsStrategy": "ATTEMPT_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Return success true for a non-token tx with sufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with sufficient sats if ATTEMPT_SATS strategy ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1126:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "ATTEMPT_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Return success true for a token tx with sufficient tokens but insufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a token tx with sufficient tokens but insufficient sats if ATTEMPT_SATS strategy ======
AssertionError: expected { success: true, …(4) } to deeply equal { success: true, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:1165:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "500"
         "requiresTxChain": false
      -  "satsStrategy": "ATTEMPT_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Return failure for a token tx with missing tokens even if ATTEMPT_SATS strategy.Support functions selectUtxos Return failure for a token tx with missing tokens even if ATTEMPT_SATS strategy ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1201:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "0"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "ATTEMPT_SATS"
         "success": false
       }
====== selectUtxos: Return success true for a token tx with sufficient tokens and sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a token tx with sufficient tokens and sats if ATTEMPT_SATS strategy ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1250:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "ATTEMPT_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: For an XEC-only tx, returns non-token utxos with sufficient sats.Support functions selectUtxos For an XEC-only tx, returns non-token utxos with sufficient sats ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1266:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Will return when accumulative selection has identified utxos that exactly equal the total output sats.Support functions selectUtxos Will return when accumulative selection has identified utxos that exactly equal the total output sats ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1284:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns expected object if we have insufficient token utxos.Support functions selectUtxos Returns expected object if we have insufficient token utxos ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1314:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "0"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Returns detailed summary of missing token inputs.Support functions selectUtxos Returns detailed summary of missing token inputs ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1396:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "0"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Returns detailed summary of missing token inputs and missing sats.Support functions selectUtxos Returns detailed summary of missing token inputs and missing sats ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1496:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "1092"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Returns sufficient token utxos for a single token tx.Support functions selectUtxos Returns sufficient token utxos for a single token tx ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1558:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns sufficient token utxos for a complicated token tx.Support functions selectUtxos Returns sufficient token utxos for a complicated token tx ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1633:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns sufficient token utxos for a complicated token tx if gasless.Support functions selectUtxos Returns sufficient token utxos for a complicated token tx if gasless ======
AssertionError: expected { success: true, …(4) } to deeply equal { success: true, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:1713:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "546"
         "requiresTxChain": false
      -  "satsStrategy": "NO_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns detailed summary of missing token inputs for a gasless tx.Support functions selectUtxos Returns detailed summary of missing token inputs for a gasless tx ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1786:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "1092"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "NO_SATS"
         "success": false
       }
====== selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1847:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "0"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Returns expected utxos for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected utxos for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:1889:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty >1 SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty >1 SLP_TOKEN_TYPE_NFT1_GROUP input ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1927:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "0"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input and also sats.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input and also sats ======
AssertionError: expected { success: false, …(5) } to deeply equal { success: false, …(4) }
    at Context.<anonymous> (src/wallet.test.ts:1961:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

         ]
         "missingSats": "100000000"
         "missingTokens": "[object Map]"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: An NFT mint fails with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input but insufficient sats.Support functions selectUtxos An NFT mint fails with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input but insufficient sats ======
AssertionError: expected { success: false, …(4) } to deeply equal { success: false, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:2004:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

           "Insufficient sats to complete tx. Need 99999454 additional satoshis to complete this Action."
         ]
         "missingSats": "99999454"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": false
       }
====== selectUtxos: An NFT mint succeeds with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input, insufficient sats, but select a strategy not requiring sats.Support functions selectUtxos An NFT mint succeeds with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input, insufficient sats, but select a strategy not requiring sats ======
AssertionError: expected { success: true, …(4) } to deeply equal { success: true, …(3) }
    at Context.<anonymous> (src/wallet.test.ts:2050:23)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "100000000"
         "requiresTxChain": false
      -  "satsStrategy": "NO_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns utxos with requiresTxChain: false for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have exact atoms.Support functions selectUtxos Returns utxos with requiresTxChain: false for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have exact atoms ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:2082:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": false
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000
====== selectUtxos: Returns utxos with requiresTxChain: true for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have enough atoms but not exact atoms.Support functions selectUtxos Returns utxos with requiresTxChain: true for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have enough atoms but not exact atoms ======
AssertionError: expected { success: true, …(4) } to deeply equal { Object (success, missingSats, ...) }
    at Context.<anonymous> (src/wallet.test.ts:2117:65)
    at process.processImmediate (node:internal/timers:485:21)

      + expected - actual

       {
         "missingSats": "0"
         "requiresTxChain": true
      -  "satsStrategy": "REQUIRE_SATS"
         "success": true
         "utxos": [
           {
             "blockHeight": 800000

Each failure log is accessible here:
selectUtxos: Return success false and missing sats for a non-token tx with insufficient sats.Support functions selectUtxos Return success false and missing sats for a non-token tx with insufficient sats
selectUtxos: Return success true for a non-token tx with insufficient sats if NO_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with insufficient sats if NO_SATS strategy
selectUtxos: Return success true for a non-token tx with insufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with insufficient sats if ATTEMPT_SATS strategy
selectUtxos: Return success true for a non-token tx with sufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a non-token tx with sufficient sats if ATTEMPT_SATS strategy
selectUtxos: Return success true for a token tx with sufficient tokens but insufficient sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a token tx with sufficient tokens but insufficient sats if ATTEMPT_SATS strategy
selectUtxos: Return failure for a token tx with missing tokens even if ATTEMPT_SATS strategy.Support functions selectUtxos Return failure for a token tx with missing tokens even if ATTEMPT_SATS strategy
selectUtxos: Return success true for a token tx with sufficient tokens and sats if ATTEMPT_SATS strategy.Support functions selectUtxos Return success true for a token tx with sufficient tokens and sats if ATTEMPT_SATS strategy
selectUtxos: For an XEC-only tx, returns non-token utxos with sufficient sats.Support functions selectUtxos For an XEC-only tx, returns non-token utxos with sufficient sats
selectUtxos: Will return when accumulative selection has identified utxos that exactly equal the total output sats.Support functions selectUtxos Will return when accumulative selection has identified utxos that exactly equal the total output sats
selectUtxos: Returns expected object if we have insufficient token utxos.Support functions selectUtxos Returns expected object if we have insufficient token utxos
selectUtxos: Returns detailed summary of missing token inputs.Support functions selectUtxos Returns detailed summary of missing token inputs
selectUtxos: Returns detailed summary of missing token inputs and missing sats.Support functions selectUtxos Returns detailed summary of missing token inputs and missing sats
selectUtxos: Returns sufficient token utxos for a single token tx.Support functions selectUtxos Returns sufficient token utxos for a single token tx
selectUtxos: Returns sufficient token utxos for a complicated token tx.Support functions selectUtxos Returns sufficient token utxos for a complicated token tx
selectUtxos: Returns sufficient token utxos for a complicated token tx if gasless.Support functions selectUtxos Returns sufficient token utxos for a complicated token tx if gasless
selectUtxos: Returns detailed summary of missing token inputs for a gasless tx.Support functions selectUtxos Returns detailed summary of missing token inputs for a gasless tx
selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input
selectUtxos: Returns expected utxos for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected utxos for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input
selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty >1 SLP_TOKEN_TYPE_NFT1_GROUP input.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we have a qty >1 SLP_TOKEN_TYPE_NFT1_GROUP input
selectUtxos: Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input and also sats.Support functions selectUtxos Returns expected error for an SLP_TOKEN_TYPE_NFT1_CHILD GENESIS tx if we are missing the SLP_TOKEN_TYPE_NFT1_GROUP input and also sats
selectUtxos: An NFT mint fails with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input but insufficient sats.Support functions selectUtxos An NFT mint fails with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input but insufficient sats
selectUtxos: An NFT mint succeeds with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input, insufficient sats, but select a strategy not requiring sats.Support functions selectUtxos An NFT mint succeeds with insufficient sats if we have a qty-1 SLP_TOKEN_TYPE_NFT1_GROUP input, insufficient sats, but select a strategy not requiring sats
selectUtxos: Returns utxos with requiresTxChain: false for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have exact atoms.Support functions selectUtxos Returns utxos with requiresTxChain: false for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have exact atoms
selectUtxos: Returns utxos with requiresTxChain: true for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have enough atoms but not exact atoms.Support functions selectUtxos Returns utxos with requiresTxChain: true for an SLP_TOKEN_TYPE_FUNGIBLE burn tx if we have enough atoms but not exact atoms

Failed tests logs:

====== Postage mechanism for eCash transactions: We can create a postage transaction with SIGHASH_ANYONECANPAY and add fuel inputs.Postage mechanism for eCash transactions We can create a postage transaction with SIGHASH_ANYONECANPAY and add fuel inputs ======
Error: Failed getting /tx/undefined: 400: Not a txid: undefined
    at FailoverProxy.ensureResponseErrorThrown (/work/modules/chronik-client/src/failoverProxy.ts:218:23)
    at FailoverProxy._callAxios (/work/modules/chronik-client/src/failoverProxy.ts:196:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async FailoverProxy._request (/work/modules/chronik-client/src/failoverProxy.ts:121:34)
    at async ChronikClient.tx (/work/modules/chronik-client/src/ChronikClient.ts:273:22)
    at async Context.<anonymous> (test/postage.test.ts:179:20)

Each failure log is accessible here:
Postage mechanism for eCash transactions: We can create a postage transaction with SIGHASH_ANYONECANPAY and add fuel inputs.Postage mechanism for eCash transactions We can create a postage transaction with SIGHASH_ANYONECANPAY and add fuel inputs

update method and test to reflect expected real world use

bytesofman retitled this revision from [ecash-lib, ecash-wallet] Properly construct postage tx to support serialization and server deser to [ecash-wallet] Properly construct postage tx to support serialization and server deser.Mon, Oct 27, 21:10
bytesofman edited the summary of this revision. (Show Details)

add prePostageInputSats as a param so the postage payer can know how many inputs need to be added

remove debug logs, rebase, explain why we do not update utxo set pre-broadcast for postage

Failed tests logs:

====== AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable ======
Error: Insufficient input sats (2333): Can only pay for 695 fees, but 1186 required
    at TxBuilder.sign (/work/modules/ecash-lib/src/txBuilder.ts:204:23)
    at AgoraOffer.take (src/agora.ts:295:34)
    at takeSlpOffer (tests/partial-helper-slp.ts:144:48)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Context.<anonymous> (tests/partial.slp.test.ts:500:32)

Each failure log is accessible here:
AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable

Failed tests logs:

====== AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable ======
Error: Insufficient input sats (2333): Can only pay for 695 fees, but 1186 required
    at TxBuilder.sign (/work/modules/ecash-lib/src/txBuilder.ts:204:23)
    at AgoraOffer.take (src/agora.ts:295:34)
    at takeSlpOffer (tests/partial-helper-slp.ts:144:48)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Context.<anonymous> (tests/partial.slp.test.ts:500:32)

Each failure log is accessible here:
AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable

Failed tests logs:

====== AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable ======
Error: Insufficient input sats (2333): Can only pay for 695 fees, but 1186 required
    at TxBuilder.sign (/work/modules/ecash-lib/src/txBuilder.ts:204:23)
    at AgoraOffer.take (src/agora.ts:295:34)
    at takeSlpOffer (tests/partial-helper-slp.ts:144:48)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Context.<anonymous> (tests/partial.slp.test.ts:500:32)

Each failure log is accessible here:
AgoraPartial SLP: AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable.AgoraPartial SLP AgoraPartial SLP 1000 for 1sat/token, dust accept, must allowUnspendable

CI failure is (I think) not related to this diff. Not able to repeat locally. Checking it out in this patch D18845

resolved: D18846

Fabien requested changes to this revision.Thu, Oct 30, 09:08
Fabien added a subscriber: Fabien.
Fabien added inline comments.
modules/ecash-wallet/src/wallet.ts
903 ↗(On Diff #56352)
919 ↗(On Diff #56352)

Actually it's not the missing stats but rather the opposite

980 ↗(On Diff #56352)

Does this include the change output already ?

This revision now requires changes to proceed.Thu, Oct 30, 09:08
bytesofman added inline comments.
modules/ecash-wallet/src/wallet.ts
919 ↗(On Diff #56352)

improved the explanation here

980 ↗(On Diff #56352)

These outputs are calculated by partiallySignedTx here:

/**
         * Validate outputs AND add token-required generated outputs
         * i.e. token change or burn-adjusted token change
         */
        const { txOutputs } = finalizeOutputs(
            this.action,
            selectedUtxos,
            this._wallet.script,
            dustSats,
        );

This function calculates and adds any token change outputs. It DOES NOT add any XEC change outputs. ecash-wallet adds these in the build() method, but NOT in the buildPostage method.

So, for token txs, we get a token change output. However we do not include an XEC change output. This is okay because for postage txs, at least for how they are implemented here, the creator has no XEC and expects no XEC change.

To really get into it, i.e. to build txs with exactly 1 sat/byte fees and perfect change, we would have to size and calculate this change output (for the postage payer) before handing it off to the postage payer. This could be possible. For example, the creator of the pre-postage-tx could know that the postage payer has utxos of a fixed size. So he could then know how many of these inputs will be needed to send the tx, and add a change output for the postage payer. But there are a lot of assumptions involved in this (i.e. the creator of the pre-postage-tx would also need to know the change output script of the postage payer).

A more robust approach is to include no XEC change. The postage payer must size his utxos appropriately so that that every nth utxo added does not overpay. In practice, an effective solution is the postage payer has only 1000-sat utxos. One is enough for simple token sends. Two is enough for slightly more complex token sends. Any fee overpayment is trivial.

This could be further optimized by more explicit utxo sizing and selection by the postage payer.

The core issue is that the outputs cannot be altered once the pre-postage-creator signs the tx. Since there is a problem in communicating relevant change info to thoe pre-postage-creator, the most robust solution simply requires none of this info, and passes the problem off to the postage-tx-payer.

bytesofman marked 2 inline comments as done.

rebase, better comments, spacing

This revision is now accepted and ready to land.Thu, Oct 30, 11:28