github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/litrpc/chancmds.go (about) 1 package litrpc 2 3 import ( 4 "fmt" 5 6 "github.com/mit-dci/lit/logging" 7 8 "github.com/mit-dci/lit/btcutil" 9 "github.com/mit-dci/lit/consts" 10 "github.com/mit-dci/lit/portxo" 11 "github.com/mit-dci/lit/qln" 12 ) 13 14 type ChannelInfo struct { 15 OutPoint string 16 CoinType uint32 17 Closed bool 18 Failed bool 19 Capacity int64 20 MyBalance int64 21 Height int32 // block height of channel fund confirmation 22 StateNum uint64 // Most recent commit number 23 PeerIdx, CIdx uint32 24 PeerID string 25 Data [32]byte 26 Pkh [20]byte 27 HTLCs []HTLCInfo 28 LastUpdate uint64 29 } 30 type ChannelListReply struct { 31 Channels []ChannelInfo 32 } 33 type HTLCInfo struct { 34 Idx uint32 35 Incoming bool 36 Amt int64 37 RHash [32]byte 38 Locktime uint32 39 R [16]byte 40 Cleared bool 41 Clearing bool 42 ClearedOnChain bool 43 InProg bool 44 } 45 46 // ChannelList sends back a list of every (open?) channel with some 47 // info for each. 48 func (r *LitRPC) ChannelList(args ChanArgs, reply *ChannelListReply) error { 49 var err error 50 var qcs []*qln.Qchan 51 52 if args.ChanIdx == 0 { 53 qcs, err = r.Node.GetAllQchans() 54 if err != nil { 55 return err 56 } 57 } else { 58 qc, err := r.Node.GetQchanByIdx(args.ChanIdx) 59 if err != nil { 60 return err 61 } 62 qcs = append(qcs, qc) 63 } 64 65 reply.Channels = make([]ChannelInfo, len(qcs)) 66 67 for i, q := range qcs { 68 reply.Channels[i].OutPoint = q.Op.String() 69 reply.Channels[i].CoinType = q.Coin() 70 reply.Channels[i].Closed = q.CloseData.Closed 71 reply.Channels[i].Failed = q.State.Failed 72 reply.Channels[i].Capacity = q.Value 73 reply.Channels[i].MyBalance = q.State.MyAmt 74 reply.Channels[i].Height = q.Height 75 reply.Channels[i].StateNum = q.State.StateIdx 76 reply.Channels[i].PeerIdx = q.KeyGen.Step[3] & 0x7fffffff 77 reply.Channels[i].CIdx = q.KeyGen.Step[4] & 0x7fffffff 78 reply.Channels[i].Data = q.State.Data 79 reply.Channels[i].Pkh = q.WatchRefundAdr 80 for _, h := range q.State.HTLCs { 81 hi := HTLCInfo{ 82 h.Idx, 83 h.Incoming, 84 h.Amt, 85 h.RHash, 86 h.Locktime, 87 h.R, 88 h.Cleared, 89 h.Clearing, 90 h.ClearedOnChain, 91 false, 92 } 93 94 reply.Channels[i].HTLCs = append(reply.Channels[i].HTLCs, hi) 95 } 96 97 if q.State.InProgHTLC != nil { 98 h := q.State.InProgHTLC 99 hi := HTLCInfo{ 100 h.Idx, 101 h.Incoming, 102 h.Amt, 103 h.RHash, 104 h.Locktime, 105 h.R, 106 h.Cleared, 107 h.Clearing, 108 h.ClearedOnChain, 109 true, 110 } 111 reply.Channels[i].HTLCs = append(reply.Channels[i].HTLCs, hi) 112 } 113 114 if q.State.CollidingHTLC != nil { 115 h := q.State.CollidingHTLC 116 hi := HTLCInfo{ 117 h.Idx, 118 h.Incoming, 119 h.Amt, 120 h.RHash, 121 h.Locktime, 122 h.R, 123 h.Cleared, 124 h.Clearing, 125 h.ClearedOnChain, 126 true, 127 } 128 reply.Channels[i].HTLCs = append(reply.Channels[i].HTLCs, hi) 129 } 130 reply.Channels[i].LastUpdate = q.LastUpdate 131 } 132 return nil 133 } 134 135 // ------------------------- fund 136 type FundArgs struct { 137 Peer uint32 // who to make the channel with 138 CoinType uint32 // what coin to use 139 Capacity int64 // later can be minimum capacity 140 Roundup int64 // ignore for now; can be used to round-up capacity 141 InitialSend int64 // Initial send of -1 means "ALL" 142 Data [32]byte 143 } 144 145 type FundReply struct { 146 Status string 147 ChanIdx uint32 148 FundHeight int32 149 } 150 151 func (r *LitRPC) FundChannel(args FundArgs, reply *FundReply) error { 152 var err error 153 if r.Node.InProg != nil && r.Node.InProg.PeerIdx != 0 { 154 return fmt.Errorf("channel with peer %d not done yet", r.Node.InProg.PeerIdx) 155 } 156 157 if args.InitialSend < 0 || args.Capacity < 0 { 158 return fmt.Errorf("Can't have negative send or capacity") 159 } 160 if args.Capacity < consts.MinChanCapacity { // limit for now 161 return fmt.Errorf("Min channel capacity 1M sat") 162 } 163 if args.InitialSend > args.Capacity { 164 return fmt.Errorf("Can't send %d in %d capacity channel", 165 args.InitialSend, args.Capacity) 166 } 167 168 wal := r.Node.SubWallet[args.CoinType] 169 if wal == nil { 170 return fmt.Errorf("No wallet of cointype %d linked", args.CoinType) 171 } 172 173 nowHeight := wal.CurrentHeight() 174 175 // see if we have enough money before calling the funding function. Not 176 // strictly required but it's better to fail here instead of after net traffic. 177 // also assume a fee of like 50K sat just to be safe 178 var allPorTxos portxo.TxoSliceByAmt 179 allPorTxos, err = wal.UtxoDump() 180 if err != nil { 181 return err 182 } 183 184 spendable := allPorTxos.SumWitness(nowHeight) 185 186 if args.Capacity > spendable-wal.Fee()*consts.JusticeTxBump { 187 return fmt.Errorf("Wanted %d but %d available for channel creation", 188 args.Capacity, spendable-wal.Fee()*consts.JusticeTxBump) 189 } 190 191 idx, err := r.Node.FundChannel( 192 args.Peer, args.CoinType, args.Capacity, args.InitialSend, args.Data) 193 if err != nil { 194 return err 195 } 196 197 reply.Status = fmt.Sprintf("funded channel %d", idx) 198 reply.ChanIdx = idx 199 reply.FundHeight = nowHeight 200 201 return nil 202 } 203 204 // ------------------------- dual fund 205 type DualFundArgs struct { 206 Peer uint32 // who to make the channel with 207 CoinType uint32 // what coin to use 208 OurAmount int64 // what amount we will fund 209 TheirAmount int64 // what amount we request them to fund 210 } 211 212 func (r *LitRPC) DualFundChannel(args DualFundArgs, reply *StatusReply) error { 213 var err error 214 if r.Node.InProgDual != nil && r.Node.InProgDual.PeerIdx != 0 { 215 return fmt.Errorf("channel with peer %d not done yet", r.Node.InProgDual.PeerIdx) 216 } 217 218 if args.OurAmount <= 0 || args.TheirAmount <= 0 { 219 return fmt.Errorf("Need both our and their amount to be more than zero") 220 } 221 if args.OurAmount+args.TheirAmount < 1000000 { // limit for now 222 return fmt.Errorf("Min channel capacity 1M sat") 223 } 224 225 wal := r.Node.SubWallet[args.CoinType] 226 if wal == nil { 227 return fmt.Errorf("No wallet of cointype %d linked", args.CoinType) 228 } 229 230 nowHeight := wal.CurrentHeight() 231 232 // see if we have enough money before calling the funding function. Not 233 // strictly required but it's better to fail here instead of after net traffic. 234 // also assume a fee of like 50K sat just to be safe 235 var allPorTxos portxo.TxoSliceByAmt 236 allPorTxos, err = wal.UtxoDump() 237 if err != nil { 238 return err 239 } 240 241 spendable := allPorTxos.SumWitness(nowHeight) 242 243 if args.OurAmount > spendable-50000 { 244 return fmt.Errorf("Our amount to fund is %d but only %d available for channel creation", 245 args.OurAmount, spendable-50000) 246 } 247 248 result, err := r.Node.DualFundChannel( 249 args.Peer, args.CoinType, args.OurAmount, args.TheirAmount) 250 if err != nil { 251 252 return err 253 } 254 255 if !result.Accepted { 256 return fmt.Errorf("Peer declined the funding request for reason %d", result.DeclineReason) 257 } 258 259 reply.Status = fmt.Sprintf("funded channel %d", result.ChannelId) 260 261 return nil 262 } 263 264 type DualFundRespondArgs struct { 265 // True for accept, false for decline 266 AcceptOrDecline bool 267 } 268 269 func (r *LitRPC) DualFundRespond(args DualFundRespondArgs, reply *StatusReply) error { 270 peerIdx := r.Node.InProgDual.PeerIdx 271 272 if peerIdx == 0 || r.Node.InProgDual.InitiatedByUs { 273 return fmt.Errorf("There is no pending request to reject") 274 } 275 276 if args.AcceptOrDecline { 277 r.Node.DualFundAccept() 278 reply.Status = fmt.Sprintf("Successfully accepted funding request from peer %d", peerIdx) 279 } else { 280 r.Node.DualFundDecline(0x01) 281 reply.Status = fmt.Sprintf("Successfully declined funding request from peer %d", peerIdx) 282 } 283 284 return nil 285 } 286 287 type PendingDualFundRequestsArgs struct { 288 // none 289 } 290 291 type PendingDualFundReply struct { 292 Pending bool 293 PeerIdx uint32 294 CoinType uint32 295 TheirAmount int64 296 RequestedAmount int64 297 } 298 299 func (r *LitRPC) PendingDualFund(args PendingDualFundRequestsArgs, reply *PendingDualFundReply) error { 300 301 if r.Node.InProgDual.PeerIdx != 0 && !r.Node.InProgDual.InitiatedByUs { 302 reply.Pending = true 303 reply.TheirAmount = r.Node.InProgDual.TheirAmount 304 reply.RequestedAmount = r.Node.InProgDual.OurAmount 305 reply.PeerIdx = r.Node.InProgDual.PeerIdx 306 reply.CoinType = r.Node.InProgDual.CoinType 307 } 308 309 return nil 310 } 311 312 // ------------------------- statedump 313 type StateDumpArgs struct { 314 // none 315 } 316 317 type StateDumpReply struct { 318 Txs []qln.JusticeTx 319 } 320 321 // StateDump dumps all of the meta data for the state commitments of a channel 322 func (r *LitRPC) StateDump(args StateDumpArgs, reply *StateDumpReply) error { 323 var err error 324 reply.Txs, err = r.Node.DumpJusticeDB() 325 if err != nil { 326 return err 327 } 328 329 return nil 330 } 331 332 // ------------------------- push 333 type PushArgs struct { 334 ChanIdx uint32 335 Amt int64 336 Data [32]byte 337 } 338 type PushReply struct { 339 StateIndex uint64 340 } 341 342 // Push is the command to push money to the other side of the channel. 343 // Currently waits for the process to complete before returning. 344 // Will change to .. tries to send, but may not complete. 345 346 func (r *LitRPC) Push(args PushArgs, reply *PushReply) error { 347 if args.Amt > consts.MaxChanCapacity || args.Amt < 1 { 348 return fmt.Errorf( 349 "can't push %d max is 1 coin (100000000), min is 1", args.Amt) 350 } 351 352 logging.Infof("push %d to chan %d with data %x\n", args.Amt, args.ChanIdx, args.Data) 353 354 // load the whole channel from disk just to see who the peer is 355 // (pretty inefficient) 356 dummyqc, err := r.Node.GetQchanByIdx(args.ChanIdx) 357 if err != nil { 358 return err 359 } 360 // see if channel is closed and error early 361 if dummyqc.CloseData.Closed { 362 return fmt.Errorf("Can't push; channel %d closed", args.ChanIdx) 363 } 364 365 // but we want to reference the qc that's already in ram 366 // first see if we're connected to that peer 367 368 // map read, need mutex...? 369 r.Node.RemoteMtx.Lock() 370 peer, ok := r.Node.RemoteCons[dummyqc.Peer()] 371 r.Node.RemoteMtx.Unlock() 372 if !ok { 373 return fmt.Errorf("not connected to peer %d for channel %d", 374 dummyqc.Peer(), dummyqc.Idx()) 375 } 376 qc, ok := peer.QCs[dummyqc.Idx()] 377 if !ok { 378 return fmt.Errorf("peer %d doesn't have channel %d", 379 dummyqc.Peer(), dummyqc.Idx()) 380 } 381 382 logging.Infof("channel %s\n", qc.Op.String()) 383 384 if qc.CloseData.Closed { 385 return fmt.Errorf("Channel %d already closed by tx %s", 386 args.ChanIdx, qc.CloseData.CloseTxid.String()) 387 } 388 389 // TODO this is a bad place to put it -- litRPC should be a thin layer 390 // to the Node.Func() calls. For now though, set the height here... 391 qc.Height = dummyqc.Height 392 393 err = r.Node.PushChannel(qc, uint32(args.Amt), args.Data) 394 if err != nil { 395 logging.Errorf("Push error: %s\n", err.Error()) 396 return err 397 } 398 399 reply.StateIndex = qc.State.StateIdx 400 return nil 401 } 402 403 // ------------------------- cclose 404 type ChanArgs struct { 405 ChanIdx uint32 406 } 407 408 // reply with status string 409 // CloseChannel is a cooperative closing of a channel to a specified address. 410 func (r *LitRPC) CloseChannel(args ChanArgs, reply *StatusReply) error { 411 412 qc, err := r.Node.GetQchanByIdx(args.ChanIdx) 413 if err != nil { 414 return err 415 } 416 417 err = r.Node.CoopClose(qc) 418 if err != nil { 419 return err 420 } 421 reply.Status = "OK closed" 422 423 return nil 424 } 425 426 // ------------------------- break 427 func (r *LitRPC) BreakChannel(args ChanArgs, reply *StatusReply) error { 428 429 qc, err := r.Node.GetQchanByIdx(args.ChanIdx) 430 if err != nil { 431 return err 432 } 433 return r.Node.BreakChannel(qc) 434 } 435 436 // ------------------------- dumpPriv 437 type PrivInfo struct { 438 OutPoint string 439 Amt int64 440 Height int32 441 Delay int32 442 CoinType string 443 Witty bool 444 PairKey string 445 446 WIF string 447 } 448 449 type DumpReply struct { 450 Privs []PrivInfo 451 } 452 453 // DumpPrivs returns WIF private keys for every utxo and channel 454 func (r *LitRPC) DumpPrivs(args NoArgs, reply *DumpReply) error { 455 // get wifs for all channels 456 qcs, err := r.Node.GetAllQchans() 457 if err != nil { 458 return err 459 } 460 461 for _, qc := range qcs { 462 wal, ok := r.Node.SubWallet[qc.Coin()] 463 if !ok { 464 logging.Errorf( 465 "Channel %s error - coin %d not connected; can't show keys", 466 qc.Op.String(), qc.Coin()) 467 continue 468 } 469 470 var thisTxo PrivInfo 471 thisTxo.OutPoint = qc.Op.String() 472 thisTxo.Amt = qc.Value 473 thisTxo.Height = qc.Height 474 thisTxo.CoinType = wal.Params().Name 475 thisTxo.Witty = true 476 thisTxo.PairKey = fmt.Sprintf("%x", qc.TheirPub) 477 478 priv, err := wal.GetPriv(qc.KeyGen) 479 if err != nil { 480 return err 481 } 482 wif := btcutil.WIF{PrivKey: priv, CompressPubKey: true, NetID: wal.Params().PrivateKeyID} 483 thisTxo.WIF = wif.String() 484 485 reply.Privs = append(reply.Privs, thisTxo) 486 } 487 488 // get WIFs for all utxos in the wallets 489 for _, wal := range r.Node.SubWallet { 490 walTxos, err := wal.UtxoDump() 491 if err != nil { 492 return err 493 } 494 495 syncHeight := wal.CurrentHeight() 496 497 theseTxos := make([]PrivInfo, len(walTxos)) 498 for i, u := range walTxos { 499 theseTxos[i].OutPoint = u.Op.String() 500 theseTxos[i].Amt = u.Value 501 theseTxos[i].Height = u.Height 502 theseTxos[i].CoinType = wal.Params().Name 503 // show delay before utxo can be spent 504 if u.Seq != 0 { 505 theseTxos[i].Delay = u.Height + int32(u.Seq) - syncHeight 506 } 507 theseTxos[i].Witty = u.Mode&portxo.FlagTxoWitness != 0 508 priv, err := wal.GetPriv(u.KeyGen) 509 if err != nil { 510 return err 511 } 512 wif := btcutil.WIF{PrivKey: priv, CompressPubKey: true, NetID: wal.Params().PrivateKeyID} 513 514 theseTxos[i].WIF = wif.String() 515 } 516 517 reply.Privs = append(reply.Privs, theseTxos...) 518 } 519 520 return nil 521 } 522 523 // ------------------------- HTLCs 524 type AddHTLCArgs struct { 525 ChanIdx uint32 526 Amt int64 527 LockTime uint32 528 RHash [32]byte 529 Data [32]byte 530 } 531 type AddHTLCReply struct { 532 StateIndex uint64 533 HTLCIndex uint32 534 } 535 536 func (r *LitRPC) AddHTLC(args AddHTLCArgs, reply *AddHTLCReply) error { 537 if args.Amt > consts.MaxChanCapacity || args.Amt < consts.MinOutput { 538 return fmt.Errorf( 539 "can't add HTLC %d max is 1 coin (100000000), min is %d", args.Amt, consts.MinOutput) 540 } 541 542 logging.Infof("add HTLC %d to chan %d with data %x and RHash %x\n", args.Amt, args.ChanIdx, args.Data, args.RHash) 543 544 // load the whole channel from disk just to see who the peer is 545 // (pretty inefficient) 546 dummyqc, err := r.Node.GetQchanByIdx(args.ChanIdx) 547 if err != nil { 548 return err 549 } 550 // see if channel is closed and error early 551 if dummyqc.CloseData.Closed { 552 return fmt.Errorf("Can't push; channel %d closed", args.ChanIdx) 553 } 554 555 // but we want to reference the qc that's already in ram 556 // first see if we're connected to that peer 557 558 // map read, need mutex...? 559 r.Node.RemoteMtx.Lock() 560 peer, ok := r.Node.RemoteCons[dummyqc.Peer()] 561 r.Node.RemoteMtx.Unlock() 562 if !ok { 563 return fmt.Errorf("not connected to peer %d for channel %d", 564 dummyqc.Peer(), dummyqc.Idx()) 565 } 566 qc, ok := peer.QCs[dummyqc.Idx()] 567 if !ok { 568 return fmt.Errorf("peer %d doesn't have channel %d", 569 dummyqc.Peer(), dummyqc.Idx()) 570 } 571 572 logging.Infof("channel %s\n", qc.Op.String()) 573 574 if qc.CloseData.Closed { 575 return fmt.Errorf("Channel %d already closed by tx %s", 576 args.ChanIdx, qc.CloseData.CloseTxid.String()) 577 } 578 579 // TODO this is a bad place to put it -- litRPC should be a thin layer 580 // to the Node.Func() calls. For now though, set the height here... 581 qc.Height = dummyqc.Height 582 curHeight := uint32(r.Node.SubWallet[qc.Coin()].CurrentHeight()) 583 curHeight += args.LockTime 584 585 err = r.Node.OfferHTLC(qc, uint32(args.Amt), args.RHash, curHeight, args.Data) 586 if err != nil { 587 return err 588 } 589 590 reply.StateIndex = qc.State.StateIdx 591 reply.HTLCIndex = qc.State.HTLCIdx - 1 592 return nil 593 } 594 595 type ClearHTLCArgs struct { 596 ChanIdx uint32 597 HTLCIdx uint32 598 R [16]byte 599 Data [32]byte 600 } 601 type ClearHTLCReply struct { 602 StateIndex uint64 603 } 604 605 func (r *LitRPC) ClearHTLC(args ClearHTLCArgs, reply *ClearHTLCReply) error { 606 logging.Infof("clear HTLC %d from chan %d with data %x and preimage %x\n", args.HTLCIdx, args.ChanIdx, args.Data, args.R) 607 608 // load the whole channel from disk just to see who the peer is 609 // (pretty inefficient) 610 dummyqc, err := r.Node.GetQchanByIdx(args.ChanIdx) 611 if err != nil { 612 return err 613 } 614 // see if channel is closed and error early 615 if dummyqc.CloseData.Closed { 616 return fmt.Errorf("Can't clear; channel %d closed", args.ChanIdx) 617 } 618 619 // but we want to reference the qc that's already in ram 620 // first see if we're connected to that peer 621 622 // map read, need mutex...? 623 r.Node.RemoteMtx.Lock() 624 peer, ok := r.Node.RemoteCons[dummyqc.Peer()] 625 r.Node.RemoteMtx.Unlock() 626 if !ok { 627 return fmt.Errorf("not connected to peer %d for channel %d", 628 dummyqc.Peer(), dummyqc.Idx()) 629 } 630 qc, ok := peer.QCs[dummyqc.Idx()] 631 if !ok { 632 return fmt.Errorf("peer %d doesn't have channel %d", 633 dummyqc.Peer(), dummyqc.Idx()) 634 } 635 636 logging.Infof("channel %s\n", qc.Op.String()) 637 638 if qc.CloseData.Closed { 639 return fmt.Errorf("Channel %d already closed by tx %s", 640 args.ChanIdx, qc.CloseData.CloseTxid.String()) 641 } 642 643 // TODO this is a bad place to put it -- litRPC should be a thin layer 644 // to the Node.Func() calls. For now though, set the height here... 645 qc.Height = dummyqc.Height 646 647 err = r.Node.ClearHTLC(qc, args.R, args.HTLCIdx, args.Data) 648 if err != nil { 649 return err 650 } 651 652 reply.StateIndex = qc.State.StateIdx 653 return nil 654 } 655 656 type PayMultihopArgs struct { 657 DestLNAdr string 658 DestCoinType uint32 659 OriginCoinType uint32 660 Amt int64 661 } 662 663 // PayMultihop tries to find a multi-hop path to send the payment along 664 func (r *LitRPC) PayMultihop(args PayMultihopArgs, reply *StatusReply) error { 665 _, err := r.Node.PayMultihop(args.DestLNAdr, args.OriginCoinType, args.DestCoinType, args.Amt) 666 return err 667 }