-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return tx instead of psbt #44
Changes from 17 commits
7d225f6
6c1b56f
b0cdefa
7e8869a
0858d15
b8d7ebd
fa98387
df6f3b8
f451c77
8197e0d
ff2f11c
c5be034
5513b98
fa54029
fec82d5
f8f3753
417b59f
c8a578d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { networks, Transaction } from "bitcoinjs-lib"; | ||
import { networks, Psbt, Transaction } from "bitcoinjs-lib"; | ||
import { StakingParams } from "../types/params"; | ||
import { UTXO } from "../types/UTXO"; | ||
import { StakingScriptData, StakingScripts } from "./stakingScript"; | ||
|
@@ -21,8 +21,9 @@ import { | |
validateStakingTimelock, | ||
validateStakingTxInputData, | ||
} from "../utils/staking"; | ||
import { PsbtTransactionResult } from "../types/transaction"; | ||
import { PsbtResult, TransactionResult } from "../types/transaction"; | ||
import { toBuffers } from "../utils/staking"; | ||
import { stakingPsbt, unbondingPsbt } from "./psbt"; | ||
export * from "./stakingScript"; | ||
|
||
export interface StakerInfo { | ||
|
@@ -117,13 +118,15 @@ export class Staking { | |
* @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking | ||
* transaction. | ||
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @returns {TransactionResult} - An object containing the unsigned transaction | ||
* and fee | ||
* @throws {StakingError} - If the transaction cannot be built | ||
*/ | ||
public createStakingTransaction( | ||
stakingAmountSat: number, | ||
inputUTXOs: UTXO[], | ||
feeRate: number, | ||
): PsbtTransactionResult { | ||
): TransactionResult { | ||
validateStakingTxInputData( | ||
stakingAmountSat, | ||
this.stakingTimelock, | ||
|
@@ -135,15 +138,28 @@ export class Staking { | |
const scripts = this.buildScripts(); | ||
|
||
try { | ||
return stakingTransaction( | ||
const { transaction, fee } = stakingTransaction( | ||
scripts, | ||
stakingAmountSat, | ||
this.stakerInfo.address, | ||
inputUTXOs, | ||
this.network, | ||
feeRate, | ||
isTaproot(this.stakerInfo.address, this.network) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined, | ||
); | ||
// Do a dry run of stakingPsbt to ensure the transaction can be converted to PSBT | ||
jrwbabylonlab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// with all the required properties before returning it | ||
stakingPsbt( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when this would fail ? Asking as tbh this weirds my out a bit. Imagine you are the caller of the function When you call it you do not have even idea what is this PSBT thingy. In my mind, the library code should build staking transaction and return it and all necessary data to the caller, and then it is totally up to the caller to convert this data into psbt when the caller want to do the signing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the concern, but I have a different perspective on this.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what I am getting at is that our library should always output valid bitcoin transactions, and every valid bitcoin transaction is convertible into PSBT. Therefore it goes that this check is a bit redundant, as if we create valid btc transactions it will also be psbt complainant. The only case it would not happen is if we were have some pretty bad bug. It is not blocking for me (a bit of redundancy never hurts) but having psbt's here pollutes a bit core of the library (imo). One note:
Imo this is idealist view of the world 😅 . In reality users of the library will use bits and pieces of the library which are most interesting to them. And hack around the rest. The only way around it is to make library api surface as small as possible and as type controlled as possible, so that core operations happens on library custom types, and all conversions to other types happens on caller side (or in separate helpers provider by the library in some helper packages) |
||
transaction, | ||
this.network, | ||
inputUTXOs, | ||
isTaproot( | ||
this.stakerInfo.address, this.network | ||
) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined, | ||
); | ||
return { | ||
transaction, | ||
fee, | ||
}; | ||
} catch (error: unknown) { | ||
throw StakingError.fromUnknown( | ||
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE, | ||
|
@@ -152,16 +168,40 @@ export class Staking { | |
} | ||
}; | ||
|
||
/** | ||
* Create a staking psbt based on the existing staking transaction. | ||
* | ||
* @param {Transaction} stakingTx - The staking transaction. | ||
* @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking | ||
* transaction. The UTXOs that were used to create the staking transaction should | ||
* be included in this array. | ||
* @returns {Psbt} - The psbt. | ||
*/ | ||
public toStakingPsbt( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just to understand how to interaction would look like api wise, it would be something like:
If this understanding is correct, then two things makes a bit nervous:
What do you think about:
This would make it clear to the caller how to use the library at type system level. (this comment probably applies to other types of psbts) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, this is a wrong statement. We don't use the transaction data output from FE when submitting to BTC. The transaction is a transactionHex which we receive from API (coming from BBN event).
Could you help double check what else we could add in terms of validation for the @KonradStaniec u actually reminds me a security question. Staker now need to sign tx that coming from the backend service. What's the worst thing could happen if say API being hijacked and returned purposely made tx outs by hacker? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
what do you mean by hijacked ? Is this the case in which there is some kind of man in the middle attack in which front end is receiving bad data from backend ? If this is possible then funds can be stolen i.e if front end receives staking transaction which points to staker utxos, but output in staking transaction point to attacker address. Imo this should be impossible, as if this is possible then no amount of validation will help. In my mind the chain of trust in out systems goes like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two comments here:
Note that this is different than the trust the client side has to the API for receiving parameters. These are parameters used to construct a staking transaction. Getting transactions from the API and signing them without additional verification is a big no-go. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. But I think it worth us adding such validation by reconstructing the transaction and psbt from scratch to compare the output script/address shall match with what we would expect. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that i would not add those validation in this PR, they shall be handled at later stage and likely gonna be after testnet. But agree this is a must have for mainnet. right now, just don't have the bandwidth for it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update the PR description with the list of things that should follow this PR and whether they will happen before testnet? It's super important to not lose track of these, as I'm starting to worry we are offloading a lot of things after the testnet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do
Yep. Unfortunately with limited time, that's the only approach we can take without delaying the testnet. |
||
stakingTx: Transaction, | ||
inputUTXOs: UTXO[], | ||
): Psbt { | ||
return stakingPsbt( | ||
stakingTx, | ||
this.network, | ||
inputUTXOs, | ||
isTaproot( | ||
this.stakerInfo.address, this.network | ||
) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : undefined, | ||
); | ||
} | ||
|
||
/** | ||
* Create an unbonding transaction for staking. | ||
* | ||
* @param {Transaction} stakingTx - The staking transaction to unbond. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @returns {TransactionResult} - An object containing the unsigned transaction | ||
* and fee | ||
* @throws {StakingError} - If the transaction cannot be built | ||
*/ | ||
public createUnbondingTransaction( | ||
stakingTx: Transaction, | ||
) : PsbtTransactionResult { | ||
) : TransactionResult { | ||
// Build scripts | ||
const scripts = this.buildScripts(); | ||
|
||
|
@@ -173,14 +213,25 @@ export class Staking { | |
) | ||
// Create the unbonding transaction | ||
try { | ||
const { psbt } = unbondingTransaction( | ||
const { transaction } = unbondingTransaction( | ||
scripts, | ||
stakingTx, | ||
this.params.unbondingFeeSat, | ||
this.network, | ||
stakingOutputIndex, | ||
); | ||
return { psbt, fee: this.params.unbondingFeeSat }; | ||
// Do a dry run of unbondingPsbt to ensure the transaction can be converted to PSBT | ||
// with all the required properties before returning it | ||
unbondingPsbt( | ||
scripts, | ||
transaction, | ||
stakingTx, | ||
this.network, | ||
); | ||
return { | ||
transaction, | ||
fee: this.params.unbondingFeeSat, | ||
}; | ||
} catch (error) { | ||
throw StakingError.fromUnknown( | ||
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE, | ||
|
@@ -190,25 +241,51 @@ export class Staking { | |
} | ||
|
||
/** | ||
* Create a withdrawal transaction that spends an unbonding transaction for observable staking. | ||
* Create an unbonding psbt based on the existing unbonding transaction and | ||
* staking transaction. | ||
* | ||
* @param {Transaction} unbondingTx - The unbonding transaction to withdraw from. | ||
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built | ||
* @param {Transaction} unbondingTx - The unbonding transaction. | ||
* @param {Transaction} stakingTx - The staking transaction. | ||
* | ||
* @returns {Psbt} - The psbt. | ||
*/ | ||
public createWithdrawEarlyUnbondedTransaction ( | ||
public toUnbondingPsbt( | ||
unbondingTx: Transaction, | ||
stakingTx: Transaction, | ||
): Psbt { | ||
return unbondingPsbt( | ||
this.buildScripts(), | ||
unbondingTx, | ||
stakingTx, | ||
this.network, | ||
); | ||
} | ||
|
||
/** | ||
* Creates a withdrawal transaction that spends from an unbonding or slashing | ||
* transaction. The timelock on the input transaction must have expired before | ||
* this withdrawal can be valid. | ||
* | ||
* @param {Transaction} earlyUnbondedTx - The unbonding or slashing | ||
* transaction to withdraw from | ||
* @param {number} feeRate - Fee rate in satoshis per byte for the withdrawal | ||
* transaction | ||
* @returns {PsbtResult} - Contains the unsigned PSBT and fee amount | ||
* @throws {StakingError} - If the input transaction is invalid or withdrawal | ||
* transaction cannot be built | ||
*/ | ||
public createWithdrawEarlyUnbondedTransaction ( | ||
earlyUnbondedTx: Transaction, | ||
feeRate: number, | ||
): PsbtTransactionResult { | ||
): PsbtResult { | ||
// Build scripts | ||
const scripts = this.buildScripts(); | ||
|
||
// Create the withdraw early unbonded transaction | ||
try { | ||
return withdrawEarlyUnbondedTransaction( | ||
scripts, | ||
unbondingTx, | ||
earlyUnbondedTx, | ||
this.stakerInfo.address, | ||
this.network, | ||
feeRate, | ||
|
@@ -227,13 +304,13 @@ export class Staking { | |
* | ||
* @param {Transaction} stakingTx - The staking transaction to withdraw from. | ||
* @param {number} feeRate - The fee rate for the transaction in satoshis per byte. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @returns {PsbtResult} - An object containing the unsigned psbt and fee | ||
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built | ||
*/ | ||
public createWithdrawTimelockUnbondedTransaction( | ||
public createWithdrawStakingExpiredTransaction( | ||
stakingTx: Transaction, | ||
feeRate: number, | ||
): PsbtTransactionResult { | ||
): PsbtResult { | ||
// Build scripts | ||
const scripts = this.buildScripts(); | ||
|
||
|
@@ -266,12 +343,12 @@ export class Staking { | |
* Create a slashing transaction spending from the staking output. | ||
* | ||
* @param {Transaction} stakingTx - The staking transaction to slash. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @returns {PsbtResult} - An object containing the unsigned psbt and fee | ||
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built | ||
*/ | ||
public createStakingOutputSlashingTransaction( | ||
stakingTx: Transaction, | ||
) : PsbtTransactionResult { | ||
) : PsbtResult { | ||
if (!this.params.slashing) { | ||
throw new StakingError( | ||
StakingErrorCode.INVALID_PARAMS, | ||
|
@@ -292,7 +369,10 @@ export class Staking { | |
this.params.slashing.minSlashingTxFeeSat, | ||
this.network, | ||
); | ||
return { psbt, fee: this.params.slashing.minSlashingTxFeeSat }; | ||
return { | ||
psbt, | ||
fee: this.params.slashing.minSlashingTxFeeSat, | ||
}; | ||
} catch (error) { | ||
throw StakingError.fromUnknown( | ||
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE, | ||
|
@@ -305,12 +385,12 @@ export class Staking { | |
* Create a slashing transaction for an unbonding output. | ||
* | ||
* @param {Transaction} unbondingTx - The unbonding transaction to slash. | ||
* @returns {PsbtTransactionResult} - An object containing the unsigned psbt and fee | ||
* @returns {PsbtResult} - An object containing the unsigned psbt and fee | ||
* @throws {StakingError} - If the delegation is invalid or the transaction cannot be built | ||
*/ | ||
public createUnbondingOutputSlashingTransaction( | ||
unbondingTx: Transaction, | ||
): PsbtTransactionResult { | ||
): PsbtResult { | ||
if (!this.params.slashing) { | ||
throw new StakingError( | ||
StakingErrorCode.INVALID_PARAMS, | ||
|
@@ -330,7 +410,10 @@ export class Staking { | |
this.params.slashing.minSlashingTxFeeSat, | ||
this.network, | ||
); | ||
return { psbt, fee: this.params.slashing.minSlashingTxFeeSat }; | ||
return { | ||
psbt, | ||
fee: this.params.slashing.minSlashingTxFeeSat, | ||
}; | ||
} catch (error) { | ||
throw StakingError.fromUnknown( | ||
error, StakingErrorCode.BUILD_TRANSACTION_FAILURE, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file requires formatting