Skip to content

Commit

Permalink
Staker undelegation (#65)
Browse files Browse the repository at this point in the history
- Add handler to enable fast unbonding.
  • Loading branch information
KonradStaniec authored Sep 1, 2023
1 parent 5a92ef6 commit c7b3ca6
Show file tree
Hide file tree
Showing 21 changed files with 2,102 additions and 380 deletions.
78 changes: 49 additions & 29 deletions btcstaking/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (int64, erro
return result, nil
}

//End of copied methods

// StakingScriptData is a struct that holds data parsed from staking script
type StakingScriptData struct {
StakerKey *btcec.PublicKey
Expand Down Expand Up @@ -589,33 +587,55 @@ func BuildSlashingTxFromStakingTxStrict(
return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee)
}

// CheckSlashingTx perform basic checks on slashing transaction:
// - slashing transaction is not nil
// - slashing transaction has exactly one input
// - slashing transaction is not replacable
// - slashing transaction has exactly one output
// - slashing transaction locktime is 0
// - slashing transaction output is simple pay to address script paying to provided slashing address
func CheckSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error {
// Transfer transaction is a transaction which:
// - has exactly one input
// - has exactly one output
func IsTransferTx(tx *wire.MsgTx) error {
if tx == nil {
return fmt.Errorf("transfer transaction must have cannot be nil")
}

if slashingTx == nil {
return fmt.Errorf("provided slashing transaction must not be nil")
if len(tx.TxIn) != 1 {
return fmt.Errorf("transfer transaction must have exactly one input")
}

if len(slashingTx.TxIn) != 1 {
return fmt.Errorf("slashing transaction must have exactly one input")
if len(tx.TxOut) != 1 {
return fmt.Errorf("transfer transaction must have exactly one output")
}

if slashingTx.TxIn[0].Sequence != wire.MaxTxInSequenceNum {
return fmt.Errorf("slashing transaction must be not replacable")
return nil
}

// Simple transfer transaction is a transaction which:
// - has exactly one input
// - has exactly one output
// - is not replacable
// - does not have any locktime
func IsSimpleTransfer(tx *wire.MsgTx) error {
if err := IsTransferTx(tx); err != nil {
return fmt.Errorf("invalid simple tansfer tx: %w", err)
}

if len(slashingTx.TxOut) != 1 {
return fmt.Errorf("slashing transaction must have exactly one output")
if tx.TxIn[0].Sequence != wire.MaxTxInSequenceNum {
return fmt.Errorf("simple transfer tx must not be replacable")
}

if slashingTx.LockTime != 0 {
return fmt.Errorf("slashing transaction locktime must be 0")
if tx.LockTime != 0 {
return fmt.Errorf("simple transfer tx must not have locktime")
}
return nil
}

// IsSlashingTx perform basic checks on slashing transaction:
// - slashing transaction is not nil
// - slashing transaction has exactly one input
// - slashing transaction is not replacable
// - slashing transaction has exactly one output
// - slashing transaction locktime is 0
// - slashing transaction output is simple pay to address script paying to provided slashing address
func IsSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error {
if err := IsSimpleTransfer(slashingTx); err != nil {
return fmt.Errorf("invalid slashing tx: %w", err)
}

pkScript, err := txscript.PayToAddrScript(slashingAddress)
Expand Down Expand Up @@ -666,16 +686,16 @@ func GetIdxOutputCommitingToScript(
return comittingOutputIdx, nil
}

// CheckTransactions validates all relevant data of slashing and staking transaction:
// CheckTransactions validates all relevant data of slashing and funding transaction.
// - slashing transaction is valid
// - staking transaction script is valid
// - staking transaction has output committing to the provided script
// - slashing transaction input is pointing to staking transaction staking output
// - funding transaction script is valid
// - funding transaction has output committing to the provided script
// - slashing transaction input is pointing to funding transaction output commiting to the script
// - that min fee for slashing tx is preserved
// In case of success, it returns data extracted from valid staking script and staking amount.
func CheckTransactions(
slashingTx *wire.MsgTx,
stakingTx *wire.MsgTx,
fundingTransaction *wire.MsgTx,
slashingTxMinFee int64,
slashingAddress btcutil.Address,
script []byte,
Expand All @@ -686,7 +706,7 @@ func CheckTransactions(
}

//1. Check slashing tx
if err := CheckSlashingTx(slashingTx, slashingAddress); err != nil {
if err := IsSlashingTx(slashingTx, slashingAddress); err != nil {
return nil, err
}

Expand All @@ -698,14 +718,14 @@ func CheckTransactions(
}

//3. Check that staking transaction has output committing to the provided script
stakingOutputIdx, err := GetIdxOutputCommitingToScript(stakingTx, script, net)
stakingOutputIdx, err := GetIdxOutputCommitingToScript(fundingTransaction, script, net)

if err != nil {
return nil, err
}

//4. Check that slashing transaction input is pointing to staking transaction
stakingTxHash := stakingTx.TxHash()
stakingTxHash := fundingTransaction.TxHash()
if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) {
return nil, fmt.Errorf("slashing transaction must spend staking output")
}
Expand All @@ -715,7 +735,7 @@ func CheckTransactions(
return nil, fmt.Errorf("slashing transaction input must spend staking output")
}

stakingOutput := stakingTx.TxOut[stakingOutputIdx]
stakingOutput := fundingTransaction.TxOut[stakingOutputIdx]

//6. Check fees
if slashingTx.TxOut[0].Value <= 0 || stakingOutput.Value <= 0 {
Expand Down
48 changes: 42 additions & 6 deletions proto/babylon/btcstaking/v1/btcstaking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ message BTCDelegation {
// quantified in satoshi
uint64 total_sat = 7;
// staking_tx is the staking tx
StakingTx staking_tx = 8;
BabylonBTCTaprootTx staking_tx = 8;
// slashing_tx is the slashing tx
// It is partially signed by SK corresponding to btc_pk, but not signed by
// validator or jury yet.
Expand All @@ -89,6 +89,41 @@ message BTCDelegation {
// by the jury (i.e., SK corresponding to jury_pk in params)
// It will be a part of the witness for the staking tx output.
bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];

// if this object is present it menans that staker requested undelegation, and whole
// delegation is being undelegated.
// TODO: Consider whether it would be better to store it in separate store, and not
// directly in delegation object
BTCUndelegation btc_undelegation = 12;
}

// BTCUndelegation signalizes that the delegation is being undelegated
message BTCUndelegation {
// unbonding_tx is the transaction which will transfer the funds from staking
// output to unbonding output. Unbonding output will usually have lower timelock
// than staking output.
BabylonBTCTaprootTx unbonding_tx = 1;
// slashing_tx is the slashing tx for unbodning transactions
// It is partially signed by SK corresponding to btc_pk, but not signed by
// validator or jury yet.
bytes slashing_tx = 2 [ (gogoproto.customtype) = "BTCSlashingTx" ];
// delegator_slashing_sig is the signature on the slashing tx
// by the delegator (i.e., SK corresponding to btc_pk).
// It will be a part of the witness for the unbodning tx output.
bytes delegator_slashing_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
// jury_slashing_sig is the signature on the slashing tx
// by the jury (i.e., SK corresponding to jury_pk in params)
// It must be provided after processing undelagate message by Babylon
bytes jury_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
// jury_unbonding_sig is the signature on the unbonding tx
// by the jury (i.e., SK corresponding to jury_pk in params)
// It must be provided after processing undelagate message by Babylon and after
// validator sig will be provided by validator
bytes jury_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
// validator_unbonding_sig is the signature on the unbonding tx
// by the validator (i.e., SK corresponding to jury_pk in params)
// It must be provided after processing undelagate message by Babylon
bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
}

// BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator.
Expand All @@ -107,12 +142,13 @@ message ProofOfPossession {
bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
}

// StakingTx is the tx for delegating BTC
message StakingTx {
// tx is the staking tx in bytes
// BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and
// transaction which commits to the provided script.
message BabylonBTCTaprootTx {
// tx is the transaction bytes
bytes tx = 1;
// staking_script is the script for the staking tx's output
bytes staking_script = 2;
// script is the script recognized by Babylon
bytes script = 2;
}

// BTCDelegationStatus is the status of a delegation.
Expand Down
28 changes: 23 additions & 5 deletions proto/babylon/btcstaking/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ service Msg {
rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse);
// CreateBTCDelegation creates a new BTC delegation
rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse);
// BtcUndelegate undelegates funds from exsitng btc delegation
rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse);
// AddJurySig handles a signature from jury
rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse);
// UpdateParams updates the btcstaking module parameters.
Expand Down Expand Up @@ -53,14 +55,14 @@ message MsgCreateBTCDelegation {
cosmos.crypto.secp256k1.PubKey babylon_pk = 2;
// pop is the proof of possession of babylon_pk and btc_pk
ProofOfPossession pop = 3;
// staking_tx is the staking tx
StakingTx staking_tx = 4;
// staking_tx is the staking tx
BabylonBTCTaprootTx staking_tx = 4;
// staking_tx_info is the tx info of the staking tx, including the Merkle proof
babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 5;
// slashing_tx is the slashing tx
// Note that the tx itself does not contain signatures, which are off-chain.
bytes slashing_tx = 6 [ (gogoproto.customtype) = "BTCSlashingTx" ];
// delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk).
// delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk).
// It will be a part of the witness for the staking tx output.
// The staking tx output further needs signatures from jury and validator in
// order to be spendable.
Expand All @@ -69,6 +71,23 @@ message MsgCreateBTCDelegation {
// MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation
message MsgCreateBTCDelegationResponse {}


// MsgBTCUndelegate is the message undelegating existing and active delegation
message MsgBTCUndelegate {
string signer = 1;
// unbonding_tx is bitcoin unbonding transaction i.e transaction that spends
// staking output and sends it to the unbonding output
BabylonBTCTaprootTx unbonding_tx = 2;
// slashing_tx is the slashing tx which slash unbonding contract
// Note that the tx itself does not contain signatures, which are off-chain.
bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ];
// delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk).
bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ];
}

// MsgBtcUndelegateResponse is the response for MsgBtcUndelegate
message MsgBTCUndelegateResponse {}

// MsgAddJurySig is the message for handling a signature from jury
message MsgAddJurySig {
string signer = 1;
Expand All @@ -78,7 +97,7 @@ message MsgAddJurySig {
// del_pk is the Bitcoin secp256k1 PK of the BTC delegation
// the PK follows encoding in BIP-340 spec
bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ];
// staking_tx_hash is the hash of the staking tx.
// staking_tx_hash is the hash of the staking tx.
// (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation
string staking_tx_hash = 4;
// sig is the signature of the jury
Expand Down Expand Up @@ -107,4 +126,3 @@ message MsgUpdateParams {
// MsgUpdateParamsResponse is the response to the MsgUpdateParams message.
message MsgUpdateParamsResponse {}


Loading

0 comments on commit c7b3ca6

Please sign in to comment.