Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions payjoin-ffi/csharp/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,16 @@ private static InputPair[] GetInputs(RpcClient rpc)
var amountBtc = utxo.GetProperty("amount").GetDouble();
var valueSat = (ulong)Math.Round(amountBtc * 100_000_000.0);

var txin = new TxIn(
new OutPoint(txid, vout),
Array.Empty<byte>(),
0,
Array.Empty<byte[]>());

var txout = new TxOut(valueSat, Convert.FromHexString(scriptPubKeyHex));
var psbtIn = new PsbtInput(txout, null, null);

inputs.Add(new InputPair(txin, psbtIn, null));
// OP_1 OP_PUSHBYTES_32 <key> is a P2TR output
if (scriptPubKeyHex.StartsWith("5120"))
{
inputs.Add(InputPair.NewP2trKeyspend(txout, new OutPoint(txid, vout)));
}
else
{
inputs.Add(InputPair.NewP2wpkh(txout, new OutPoint(txid, vout)));
}
}

return inputs.ToArray();
Expand Down Expand Up @@ -687,13 +687,16 @@ public void TestFfiValidation()
/// restart because every state transition is saved to a persister first.
/// </summary>
[Theory]
[InlineData(TransitionMode.Callback)]
[InlineData(TransitionMode.Nonblocking)]
public async Task TestIntegrationV2ToV2(TransitionMode mode)
[InlineData(TransitionMode.Callback, false)]
[InlineData(TransitionMode.Nonblocking, false)]
[InlineData(TransitionMode.Callback, true)]
public async Task TestIntegrationV2ToV2(TransitionMode mode, bool taproot)
{
var cancellationToken = TestContext.Current.CancellationToken;

using var env = PayjoinMethods.InitBitcoindSenderReceiver();
using var env = taproot
? PayjoinMethods.InitBitcoindSenderReceiverTaproot()
: PayjoinMethods.InitBitcoindSenderReceiver();
using var bitcoind = env.GetBitcoind();
using var receiver = env.GetReceiver();
using var sender = env.GetSender();
Expand Down
1 change: 1 addition & 0 deletions payjoin-ffi/dart/lib/test_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export "payjoin.dart"
RpcClient,
TestServices,
initBitcoindSenderReceiver,
initBitcoindSenderReceiverTaproot,
originalPsbt;
42 changes: 25 additions & 17 deletions payjoin-ffi/dart/test/test_payjoin_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -209,24 +209,19 @@ List<payjoin.InputPair> get_inputs(payjoin.RpcClient rpc_connection) {
final amountBtc = utxo["amount"] as num;
final amountSat = (amountBtc * 100000000).round();

final txin = payjoin.TxIn(
previousOutput: payjoin.OutPoint(txid: txid, vout: vout),
scriptSig: Uint8List(0),
sequence: 0,
witness: <Uint8List>[],
);
final witnessUtxo = payjoin.TxOut(
final txout = payjoin.TxOut(
valueSat: amountSat,
scriptPubkey: scriptPubKey,
);
final psbt_in = payjoin.PsbtInput(
witnessUtxo: witnessUtxo,
redeemScript: null,
witnessScript: null,
);
inputs.add(
payjoin.InputPair(txin: txin, psbtin: psbt_in, expectedWeight: null),
);
final outpoint = payjoin.OutPoint(txid: txid, vout: vout);
// OP_1 OP_PUSHBYTES_32 <key> is a P2TR output
if ((utxo["scriptPubKey"] as String).startsWith("5120")) {
inputs.add(
payjoin.InputPair.newP2trKeyspend(txout: txout, outpoint: outpoint),
);
} else {
inputs.add(payjoin.InputPair.newP2wpkh(txout: txout, outpoint: outpoint));
}
}

return inputs;
Expand Down Expand Up @@ -496,8 +491,12 @@ Future<payjoin.ReceiveSession?> process_receiver_proposal(
throw Exception("Unknown receiver state: $receiver");
}

Future<void> run_integration_v2_to_v2(TransitionMode mode) async {
env = test_utils.initBitcoindSenderReceiver();
Future<void> run_integration_v2_to_v2(
TransitionMode mode, {
test_utils.BitcoindEnv Function() initEnv =
test_utils.initBitcoindSenderReceiver,
}) async {
env = initEnv();
bitcoind = env.getBitcoind();
receiver = env.getReceiver();
sender = env.getSender();
Expand Down Expand Up @@ -777,5 +776,14 @@ void main() {
() async => run_integration_v2_to_v2(TransitionMode.nonblocking),
timeout: const Timeout(Duration(minutes: 5)),
);

test(
'Test integration v2 to v2 (taproot)',
() async => run_integration_v2_to_v2(
TransitionMode.callback,
initEnv: test_utils.initBitcoindSenderReceiverTaproot,
),
timeout: const Timeout(Duration(minutes: 5)),
);
});
}
1 change: 1 addition & 0 deletions payjoin-ffi/javascript/test-utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,6 @@ export const {
RpcClient,
TestServices,
initBitcoindSenderReceiver,
initBitcoindSenderReceiverTaproot,
originalPsbt,
} = nativeBinding;
17 changes: 17 additions & 0 deletions payjoin-ffi/javascript/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,20 @@ pub fn init_bitcoind_sender_receiver() -> Result<BitcoindEnv> {
sender: RpcClient { inner: Arc::new(sender) },
})
}

/// Like `init_bitcoind_sender_receiver`, but both wallets are funded with
/// Taproot (P2TR) coins.
#[napi]
pub fn init_bitcoind_sender_receiver_taproot() -> Result<BitcoindEnv> {
let (bitcoind, receiver, sender) = payjoin_test_utils::init_bitcoind_sender_receiver(
Some(AddressType::Bech32m),
Some(AddressType::Bech32m),
)
.map_err(|e| Error::from_reason(format!("Failed to initialize bitcoind: {}", e)))?;

Ok(BitcoindEnv {
bitcoind: BitcoindInstance { _inner: Arc::new(bitcoind) },
receiver: RpcClient { inner: Arc::new(receiver) },
sender: RpcClient { inner: Arc::new(sender) },
})
}
36 changes: 21 additions & 15 deletions payjoin-ffi/javascript/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,26 +226,21 @@ function getInputs(
const utxos: Utxo[] = JSON.parse(rpcConnection.call("listunspent", []));
const inputs: PJ<"InputPair">[] = [];
for (const utxo of utxos) {
const txin = payjoin.TxIn.create({
previousOutput: payjoin.OutPoint.create({
txid: utxo.txid,
vout: utxo.vout,
}),
scriptSig: new Uint8Array([]).buffer,
sequence: 0,
witness: [],
});
const txOut = payjoin.TxOut.create({
valueSat: BigInt(Math.round(utxo.amount * 100_000_000)),
// @ts-ignore
scriptPubkey: Buffer.from(utxo.scriptPubKey, "hex"),
});
const psbtIn = payjoin.PsbtInput.create({
witnessUtxo: txOut,
redeemScript: undefined,
witnessScript: undefined,
const outpoint = payjoin.OutPoint.create({
txid: utxo.txid,
vout: utxo.vout,
});
inputs.push(new payjoin.InputPair(txin, psbtIn, undefined));
// OP_1 OP_PUSHBYTES_32 <key> is a P2TR output
if (utxo.scriptPubKey.startsWith("5120")) {
inputs.push(payjoin.InputPair.newP2trKeyspend(txOut, outpoint));
} else {
inputs.push(payjoin.InputPair.newP2wpkh(txOut, outpoint));
}
}
return inputs;
}
Expand Down Expand Up @@ -617,8 +612,9 @@ function testFfiValidation(payjoin: PayjoinModule): void {
async function testIntegrationV2ToV2(
payjoin: PayjoinModule,
mode: TransitionMode,
initEnv: () => testUtils.BitcoindEnv = testUtils.initBitcoindSenderReceiver,
): Promise<void> {
const env = testUtils.initBitcoindSenderReceiver();
const env = initEnv();
const receiver = env.getReceiver();
const sender = env.getSender();

Expand Down Expand Up @@ -773,11 +769,21 @@ async function runTests(): Promise<void> {
testFfiValidation(nodejsPayjoin);
await testIntegrationV2ToV2(nodejsPayjoin, "callback");
await testIntegrationV2ToV2(nodejsPayjoin, "nonblocking");
await testIntegrationV2ToV2(
nodejsPayjoin,
"callback",
testUtils.initBitcoindSenderReceiverTaproot,
);

await webUniffiInitAsync();
testFfiValidation(webPayjoin);
await testIntegrationV2ToV2(webPayjoin, "callback");
await testIntegrationV2ToV2(webPayjoin, "nonblocking");
await testIntegrationV2ToV2(
webPayjoin,
"callback",
testUtils.initBitcoindSenderReceiverTaproot,
);
}

runTests().catch((error: unknown) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ class IntegrationTests {
@Test
fun v2ToV2PayjoinNonblocking() = runScenario(mode = TransitionMode.Nonblocking)

@Test
fun v2ToV2TaprootPayjoin() = runScenario(initEnv = ::initBitcoindSenderReceiverTaproot)

private fun runScenario(
mode: TransitionMode = TransitionMode.Callback,
initEnv: () -> BitcoindEnv = ::initBitcoindSenderReceiver,
cancelReceiver: ((PayjoinProposal, InMemoryReceiverPersister) -> Unit)? = null,
) {
initTracing()
Expand All @@ -92,7 +96,7 @@ class IntegrationTests {
val relay = services.ohttpRelayUrl()
services.fetchOhttpKeys().use { ohttpKeys ->
TestHttp(services).use { http ->
initBitcoindSenderReceiver().use { env ->
initEnv().use { env ->
env.getSender().use { senderRpc ->
env.getReceiver().use { receiverRpc ->
runV2ToV2(
Expand Down Expand Up @@ -477,18 +481,13 @@ private fun getInputs(rpcConnection: RpcClient): List<InputPair> {
val vout = obj.getValue("vout").jsonPrimitive.double.toInt().toUInt()
val scriptPubkey = HexFormat.of().parseHex(obj.getValue("scriptPubKey").jsonPrimitive.content)
val amountSat = kotlin.math.round(obj.getValue("amount").jsonPrimitive.double * 100_000_000.0).toULong()
val txin = TxIn(
previousOutput = OutPoint(txid, vout),
scriptSig = ByteArray(0),
sequence = 0u,
witness = emptyList(),
)
val psbtIn = PsbtInput(
witnessUtxo = TxOut(amountSat, scriptPubkey),
redeemScript = null,
witnessScript = null,
)
InputPair(txin, psbtIn, null)
val txout = TxOut(amountSat, scriptPubkey)
// OP_1 OP_PUSHBYTES_32 <key> is a P2TR output
if (obj.getValue("scriptPubKey").jsonPrimitive.content.startsWith("5120")) {
InputPair.newP2trKeyspend(txout, OutPoint(txid, vout))
} else {
InputPair.newP2wpkh(txout, OutPoint(txid, vout))
}
}
}

Expand Down
24 changes: 13 additions & 11 deletions payjoin-ffi/python/test/test_payjoin_integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ async def test_integration_v2_to_v2_callback(self):
async def test_integration_v2_to_v2_nonblocking(self):
await self._run_integration_v2_to_v2("nonblocking")

async def test_integration_v2_to_v2_taproot(self):
self.env = init_bitcoind_sender_receiver_taproot()
self.receiver = self.env.get_receiver()
self.sender = self.env.get_sender()
await self._run_integration_v2_to_v2("callback")


def build_sweep_psbt(sender: RpcClient, pj_uri: PjUri) -> str:
outputs = {}
Expand Down Expand Up @@ -526,17 +532,13 @@ def get_inputs(rpc_connection: RpcClient) -> list[InputPair]:
script_pubkey = bytes.fromhex(utxo["scriptPubKey"])
amount_sat = round(utxo["amount"] * 100_000_000)

txin = TxIn(
previous_output=OutPoint(txid=txid, vout=vout),
script_sig=bytes(),
sequence=0,
witness=[],
)
witness_utxo = TxOut(value_sat=amount_sat, script_pubkey=script_pubkey)
psbt_in = PsbtInput(
witness_utxo=witness_utxo, redeem_script=None, witness_script=None
)
inputs.append(InputPair(txin=txin, psbtin=psbt_in, expected_weight=None))
txout = TxOut(value_sat=amount_sat, script_pubkey=script_pubkey)
outpoint = OutPoint(txid=txid, vout=vout)
# OP_1 OP_PUSHBYTES_32 <key> is a P2TR output
if utxo["scriptPubKey"].startswith("5120"):
inputs.append(InputPair.new_p2tr_keyspend(txout=txout, outpoint=outpoint))
else:
inputs.append(InputPair.new_p2wpkh(txout=txout, outpoint=outpoint))

return inputs

Expand Down
3 changes: 3 additions & 0 deletions payjoin-ffi/src/receive/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ pub enum InputPairError {
/// Input failed validation in the FFI layer.
#[error("Invalid input: {0}")]
FfiValidation(FfiValidationError),
/// Provided transaction could not be consensus decoded.
#[error("Invalid transaction: {0}")]
InvalidTransaction(String),
}

impl InputPairError {
Expand Down
Loading
Loading