mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 12:44:19 +01:00
Support forfeit with CHECKLOCKTIMEVERIFY (#389)
* explicit Timelock struct * support & test CLTV forfeit path * fix wasm pkg * fix wasm * fix liquid GetCurrentBlockTime * cleaning * move esplora URL check
This commit is contained in:
@@ -33,10 +33,10 @@ const marketHourDelta = 5 * time.Minute
|
||||
type covenantlessService struct {
|
||||
network common.Network
|
||||
pubkey *secp256k1.PublicKey
|
||||
roundLifetime int64
|
||||
roundLifetime common.Locktime
|
||||
roundInterval int64
|
||||
unilateralExitDelay int64
|
||||
boardingExitDelay int64
|
||||
unilateralExitDelay common.Locktime
|
||||
boardingExitDelay common.Locktime
|
||||
|
||||
nostrDefaultRelays []string
|
||||
|
||||
@@ -61,7 +61,8 @@ type covenantlessService struct {
|
||||
|
||||
func NewCovenantlessService(
|
||||
network common.Network,
|
||||
roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64,
|
||||
roundInterval int64,
|
||||
roundLifetime, unilateralExitDelay, boardingExitDelay common.Locktime,
|
||||
nostrDefaultRelays []string,
|
||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||
@@ -303,6 +304,35 @@ func (s *covenantlessService) SubmitRedeemTx(
|
||||
return "", fmt.Errorf("witness utxo value mismatch")
|
||||
}
|
||||
|
||||
// verify forfeit closure script
|
||||
closure, err := tree.DecodeClosure(tapscript.Script)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode forfeit closure: %s", err)
|
||||
}
|
||||
|
||||
switch c := closure.(type) {
|
||||
case *tree.CLTVMultisigClosure:
|
||||
blocktimestamp, err := s.wallet.GetCurrentBlockTime(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get current block time: %s", err)
|
||||
}
|
||||
|
||||
switch c.Locktime.Type {
|
||||
case common.LocktimeTypeBlock:
|
||||
if c.Locktime.Value > blocktimestamp.Height {
|
||||
return "", fmt.Errorf("forfeit closure is CLTV locked, %d > %d (block height)", c.Locktime.Value, blocktimestamp.Height)
|
||||
}
|
||||
case common.LocktimeTypeSecond:
|
||||
if c.Locktime.Value > uint32(blocktimestamp.Time) {
|
||||
return "", fmt.Errorf("forfeit closure is CLTV locked, %d > %d (block time)", c.Locktime.Value, blocktimestamp.Time)
|
||||
}
|
||||
}
|
||||
case *tree.MultisigClosure:
|
||||
// prevent failure in case of multisig closure
|
||||
default:
|
||||
return "", fmt.Errorf("invalid forfeit closure script")
|
||||
}
|
||||
|
||||
ctrlBlock, err := txscript.ParseControlBlock(tapscript.ControlBlock)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse control block: %s", err)
|
||||
@@ -349,7 +379,7 @@ func (s *covenantlessService) SubmitRedeemTx(
|
||||
}
|
||||
|
||||
// recompute redeem tx
|
||||
rebuiltRedeemTx, err := bitcointree.BuildRedeemTx(ins, outputs, fees)
|
||||
rebuiltRedeemTx, err := bitcointree.BuildRedeemTx(ins, outputs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to rebuild redeem tx: %s", err)
|
||||
}
|
||||
@@ -439,7 +469,7 @@ func (s *covenantlessService) SubmitRedeemTx(
|
||||
func (s *covenantlessService) GetBoardingAddress(
|
||||
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
||||
) (address string, scripts []string, err error) {
|
||||
vtxoScript := bitcointree.NewDefaultVtxoScript(s.pubkey, userPubkey, uint(s.boardingExitDelay))
|
||||
vtxoScript := bitcointree.NewDefaultVtxoScript(s.pubkey, userPubkey, s.boardingExitDelay)
|
||||
|
||||
tapKey, _, err := vtxoScript.TapTree()
|
||||
if err != nil {
|
||||
@@ -546,7 +576,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
|
||||
}
|
||||
|
||||
// if the exit path is available, forbid registering the boarding utxo
|
||||
if blocktime+int64(exitDelay) < now {
|
||||
if blocktime+exitDelay.Seconds() < now {
|
||||
return "", fmt.Errorf("tx %s expired", input.Txid)
|
||||
}
|
||||
|
||||
@@ -634,7 +664,7 @@ func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input)
|
||||
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||
}
|
||||
|
||||
if err := boardingScript.Validate(s.pubkey, uint(s.unilateralExitDelay)); err != nil {
|
||||
if err := boardingScript.Validate(s.pubkey, s.unilateralExitDelay); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -755,8 +785,8 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error)
|
||||
|
||||
return &ServiceInfo{
|
||||
PubKey: pubkey,
|
||||
RoundLifetime: s.roundLifetime,
|
||||
UnilateralExitDelay: s.unilateralExitDelay,
|
||||
RoundLifetime: int64(s.roundLifetime.Value),
|
||||
UnilateralExitDelay: int64(s.unilateralExitDelay.Value),
|
||||
RoundInterval: s.roundInterval,
|
||||
Network: s.network.Name,
|
||||
Dust: dust,
|
||||
@@ -1064,7 +1094,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
|
||||
sweepClosure := tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{s.pubkey}},
|
||||
Seconds: uint(s.roundLifetime),
|
||||
Locktime: s.roundLifetime,
|
||||
}
|
||||
|
||||
sweepScript, err := sweepClosure.Script()
|
||||
@@ -1561,7 +1591,7 @@ func (s *covenantlessService) scheduleSweepVtxosForRound(round *domain.Round) {
|
||||
return
|
||||
}
|
||||
|
||||
expirationTimestamp := s.sweeper.scheduler.AddNow(s.roundLifetime)
|
||||
expirationTimestamp := s.sweeper.scheduler.AddNow(int64(s.roundLifetime.Value))
|
||||
|
||||
if err := s.sweeper.schedule(expirationTimestamp, round.Txid, round.VtxoTree); err != nil {
|
||||
log.WithError(err).Warn("failed to schedule sweep tx")
|
||||
|
||||
Reference in New Issue
Block a user