channel+cnct: remove preimage from channel and resolution
Now that the success resolver preimage field is always populated by the incoming contest resolver, preimage lookups earlier in the process (channel and channel arbitrator) can mostly be removed.
This commit is contained in:
parent
6886a0117f
commit
d55a8b7b29
@ -1844,14 +1844,12 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
|||||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||||
}
|
}
|
||||||
|
|
||||||
pCache := newMockPreimageCache()
|
|
||||||
|
|
||||||
aliceSigner := &mockSigner{aliceKeyPriv}
|
aliceSigner := &mockSigner{aliceKeyPriv}
|
||||||
bobSigner := &mockSigner{bobKeyPriv}
|
bobSigner := &mockSigner{bobKeyPriv}
|
||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
aliceSigner, pCache, aliceChannelState, alicePool,
|
aliceSigner, aliceChannelState, alicePool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
@ -1860,7 +1858,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
|||||||
|
|
||||||
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
||||||
channelBob, err := lnwallet.NewLightningChannel(
|
channelBob, err := lnwallet.NewLightningChannel(
|
||||||
bobSigner, pCache, bobChannelState, bobPool,
|
bobSigner, bobChannelState, bobPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
@ -259,7 +259,7 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
|
|||||||
// Finally, we'll force close the channel completing
|
// Finally, we'll force close the channel completing
|
||||||
// the force close workflow.
|
// the force close workflow.
|
||||||
chanMachine, err := lnwallet.NewLightningChannel(
|
chanMachine, err := lnwallet.NewLightningChannel(
|
||||||
c.cfg.Signer, c.cfg.PreimageDB, channel, nil,
|
c.cfg.Signer, channel, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -375,7 +375,6 @@ func (c *ChainArbitrator) Start() error {
|
|||||||
chainWatcherConfig{
|
chainWatcherConfig{
|
||||||
chanState: channel,
|
chanState: channel,
|
||||||
notifier: c.cfg.Notifier,
|
notifier: c.cfg.Notifier,
|
||||||
pCache: c.cfg.PreimageDB,
|
|
||||||
signer: c.cfg.Signer,
|
signer: c.cfg.Signer,
|
||||||
isOurAddr: c.cfg.IsOurAddress,
|
isOurAddr: c.cfg.IsOurAddress,
|
||||||
contractBreach: func(retInfo *lnwallet.BreachRetribution) error {
|
contractBreach: func(retInfo *lnwallet.BreachRetribution) error {
|
||||||
@ -709,7 +708,6 @@ func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error
|
|||||||
chainWatcherConfig{
|
chainWatcherConfig{
|
||||||
chanState: newChan,
|
chanState: newChan,
|
||||||
notifier: c.cfg.Notifier,
|
notifier: c.cfg.Notifier,
|
||||||
pCache: c.cfg.PreimageDB,
|
|
||||||
signer: c.cfg.Signer,
|
signer: c.cfg.Signer,
|
||||||
isOurAddr: c.cfg.IsOurAddress,
|
isOurAddr: c.cfg.IsOurAddress,
|
||||||
contractBreach: func(retInfo *lnwallet.BreachRetribution) error {
|
contractBreach: func(retInfo *lnwallet.BreachRetribution) error {
|
||||||
|
@ -88,11 +88,6 @@ type chainWatcherConfig struct {
|
|||||||
// notified of output spends and when transactions are confirmed.
|
// notified of output spends and when transactions are confirmed.
|
||||||
notifier chainntnfs.ChainNotifier
|
notifier chainntnfs.ChainNotifier
|
||||||
|
|
||||||
// pCache is a reference to the shared preimage cache. We'll use this
|
|
||||||
// to see if we can settle any incoming HTLC's during a remote
|
|
||||||
// commitment close event.
|
|
||||||
pCache WitnessBeacon
|
|
||||||
|
|
||||||
// signer is the main signer instances that will be responsible for
|
// signer is the main signer instances that will be responsible for
|
||||||
// signing any HTLC and commitment transaction generated by the state
|
// signing any HTLC and commitment transaction generated by the state
|
||||||
// machine.
|
// machine.
|
||||||
@ -702,7 +697,7 @@ func (c *chainWatcher) dispatchLocalForceClose(
|
|||||||
"detected", c.cfg.chanState.FundingOutpoint)
|
"detected", c.cfg.chanState.FundingOutpoint)
|
||||||
|
|
||||||
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
forceClose, err := lnwallet.NewLocalForceCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer, c.cfg.pCache,
|
c.cfg.chanState, c.cfg.signer,
|
||||||
commitSpend.SpendingTx, localCommit,
|
commitSpend.SpendingTx, localCommit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -795,7 +790,7 @@ func (c *chainWatcher) dispatchRemoteForceClose(
|
|||||||
// materials required to let each subscriber sweep the funds in the
|
// materials required to let each subscriber sweep the funds in the
|
||||||
// channel on-chain.
|
// channel on-chain.
|
||||||
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
||||||
c.cfg.chanState, c.cfg.signer, c.cfg.pCache, commitSpend,
|
c.cfg.chanState, c.cfg.signer, commitSpend,
|
||||||
remoteCommit, commitPoint,
|
remoteCommit, commitPoint,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1712,7 +1712,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
|
|||||||
// Go on adding the channel to the channel graph, and crafting
|
// Go on adding the channel to the channel graph, and crafting
|
||||||
// channel announcements.
|
// channel announcements.
|
||||||
lnChannel, err := lnwallet.NewLightningChannel(
|
lnChannel, err := lnwallet.NewLightningChannel(
|
||||||
nil, nil, completeChan, nil,
|
nil, completeChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fndgLog.Errorf("failed creating lnChannel: %v", err)
|
fndgLog.Errorf("failed creating lnChannel: %v", err)
|
||||||
@ -2005,7 +2005,7 @@ func (f *fundingManager) handleFundingConfirmation(peer lnpeer.Peer,
|
|||||||
|
|
||||||
// We create the state-machine object which wraps the database state.
|
// We create the state-machine object which wraps the database state.
|
||||||
lnChannel, err := lnwallet.NewLightningChannel(
|
lnChannel, err := lnwallet.NewLightningChannel(
|
||||||
nil, nil, completeChan, nil,
|
nil, completeChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -367,11 +367,9 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
|||||||
aliceSigner := &mockSigner{aliceKeyPriv}
|
aliceSigner := &mockSigner{aliceKeyPriv}
|
||||||
bobSigner := &mockSigner{bobKeyPriv}
|
bobSigner := &mockSigner{bobKeyPriv}
|
||||||
|
|
||||||
pCache := newMockPreimageCache()
|
|
||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
|
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
aliceSigner, pCache, aliceChannelState, alicePool,
|
aliceSigner, aliceChannelState, alicePool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
@ -380,7 +378,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
|||||||
|
|
||||||
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
|
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
|
||||||
channelBob, err := lnwallet.NewLightningChannel(
|
channelBob, err := lnwallet.NewLightningChannel(
|
||||||
bobSigner, pCache, bobChannelState, bobPool,
|
bobSigner, bobChannelState, bobPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
@ -441,7 +439,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
newAliceChannel, err := lnwallet.NewLightningChannel(
|
newAliceChannel, err := lnwallet.NewLightningChannel(
|
||||||
aliceSigner, nil, aliceStoredChannel, alicePool,
|
aliceSigner, aliceStoredChannel, alicePool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Errorf("unable to create new channel: %v",
|
return nil, nil, errors.Errorf("unable to create new channel: %v",
|
||||||
@ -481,7 +479,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
newBobChannel, err := lnwallet.NewLightningChannel(
|
newBobChannel, err := lnwallet.NewLightningChannel(
|
||||||
bobSigner, nil, bobStoredChannel, bobPool,
|
bobSigner, bobStoredChannel, bobPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Errorf("unable to create new channel: %v",
|
return nil, nil, errors.Errorf("unable to create new channel: %v",
|
||||||
|
@ -1309,13 +1309,6 @@ type LightningChannel struct {
|
|||||||
// signatures, of which there may be hundreds.
|
// signatures, of which there may be hundreds.
|
||||||
sigPool *SigPool
|
sigPool *SigPool
|
||||||
|
|
||||||
// pCache is the global preimage cache shared across all other
|
|
||||||
// LightningChannel instance. We'll use this cache either when we force
|
|
||||||
// close, or we detect that the remote party has force closed. If the
|
|
||||||
// preimage for an incoming HTLC is found in the cache, then we'll try
|
|
||||||
// to claim it on chain.
|
|
||||||
pCache PreimageCache
|
|
||||||
|
|
||||||
// Capacity is the total capacity of this channel.
|
// Capacity is the total capacity of this channel.
|
||||||
Capacity btcutil.Amount
|
Capacity btcutil.Amount
|
||||||
|
|
||||||
@ -1368,7 +1361,7 @@ type LightningChannel struct {
|
|||||||
// settled channel state. Throughout state transitions, then channel will
|
// settled channel state. Throughout state transitions, then channel will
|
||||||
// automatically persist pertinent state to the database in an efficient
|
// automatically persist pertinent state to the database in an efficient
|
||||||
// manner.
|
// manner.
|
||||||
func NewLightningChannel(signer input.Signer, pCache PreimageCache,
|
func NewLightningChannel(signer input.Signer,
|
||||||
state *channeldb.OpenChannel,
|
state *channeldb.OpenChannel,
|
||||||
sigPool *SigPool) (*LightningChannel, error) {
|
sigPool *SigPool) (*LightningChannel, error) {
|
||||||
|
|
||||||
@ -1387,7 +1380,6 @@ func NewLightningChannel(signer input.Signer, pCache PreimageCache,
|
|||||||
lc := &LightningChannel{
|
lc := &LightningChannel{
|
||||||
Signer: signer,
|
Signer: signer,
|
||||||
sigPool: sigPool,
|
sigPool: sigPool,
|
||||||
pCache: pCache,
|
|
||||||
currentHeight: localCommit.CommitHeight,
|
currentHeight: localCommit.CommitHeight,
|
||||||
remoteCommitChain: newCommitmentChain(),
|
remoteCommitChain: newCommitmentChain(),
|
||||||
localCommitChain: newCommitmentChain(),
|
localCommitChain: newCommitmentChain(),
|
||||||
@ -5096,7 +5088,7 @@ type UnilateralCloseSummary struct {
|
|||||||
// which case we will attempt to sweep the non-HTLC output using the passed
|
// which case we will attempt to sweep the non-HTLC output using the passed
|
||||||
// commitPoint.
|
// commitPoint.
|
||||||
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer,
|
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer,
|
||||||
pCache PreimageCache, commitSpend *chainntnfs.SpendDetail,
|
commitSpend *chainntnfs.SpendDetail,
|
||||||
remoteCommit channeldb.ChannelCommitment,
|
remoteCommit channeldb.ChannelCommitment,
|
||||||
commitPoint *btcec.PublicKey) (*UnilateralCloseSummary, error) {
|
commitPoint *btcec.PublicKey) (*UnilateralCloseSummary, error) {
|
||||||
|
|
||||||
@ -5113,7 +5105,6 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
|||||||
SatPerKWeight(remoteCommit.FeePerKw), false, signer,
|
SatPerKWeight(remoteCommit.FeePerKw), false, signer,
|
||||||
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
||||||
&chanState.RemoteChanCfg, *commitSpend.SpenderTxHash,
|
&chanState.RemoteChanCfg, *commitSpend.SpenderTxHash,
|
||||||
pCache,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create htlc "+
|
return nil, fmt.Errorf("unable to create htlc "+
|
||||||
@ -5211,11 +5202,11 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
|||||||
// using this struct if we need to go on-chain for any reason, or if we detect
|
// using this struct if we need to go on-chain for any reason, or if we detect
|
||||||
// that the remote party broadcasts their commitment transaction.
|
// that the remote party broadcasts their commitment transaction.
|
||||||
type IncomingHtlcResolution struct {
|
type IncomingHtlcResolution struct {
|
||||||
// Preimage is the preimage that will be used to satisfy the contract
|
// Preimage is the preimage that will be used to satisfy the contract of
|
||||||
// of the HTLC.
|
// the HTLC.
|
||||||
//
|
//
|
||||||
// NOTE: This field will only be populated if we know the preimage at
|
// NOTE: This field will only be populated in the incoming contest
|
||||||
// the time a unilateral or force close occurs.
|
// resolver.
|
||||||
Preimage [32]byte
|
Preimage [32]byte
|
||||||
|
|
||||||
// SignedSuccessTx is the fully signed HTLC success transaction. This
|
// SignedSuccessTx is the fully signed HTLC success transaction. This
|
||||||
@ -5448,7 +5439,7 @@ func newOutgoingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
|||||||
func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig,
|
func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig,
|
||||||
commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
|
commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||||
feePerKw SatPerKWeight, dustLimit btcutil.Amount, csvDelay uint32,
|
feePerKw SatPerKWeight, dustLimit btcutil.Amount, csvDelay uint32,
|
||||||
localCommit bool, preimage [32]byte) (*IncomingHtlcResolution, error) {
|
localCommit bool) (*IncomingHtlcResolution, error) {
|
||||||
|
|
||||||
op := wire.OutPoint{
|
op := wire.OutPoint{
|
||||||
Hash: commitHash,
|
Hash: commitHash,
|
||||||
@ -5475,7 +5466,6 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
|||||||
// With the script generated, we can completely populated the
|
// With the script generated, we can completely populated the
|
||||||
// SignDescriptor needed to sweep the output.
|
// SignDescriptor needed to sweep the output.
|
||||||
return &IncomingHtlcResolution{
|
return &IncomingHtlcResolution{
|
||||||
Preimage: preimage,
|
|
||||||
ClaimOutpoint: op,
|
ClaimOutpoint: op,
|
||||||
CsvDelay: csvDelay,
|
CsvDelay: csvDelay,
|
||||||
SweepSignDesc: input.SignDescriptor{
|
SweepSignDesc: input.SignDescriptor{
|
||||||
@ -5526,10 +5516,12 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
|||||||
InputIndex: 0,
|
InputIndex: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we'll construct the full witness needed to satisfy the input
|
// Next, we'll construct the full witness needed to satisfy the input of
|
||||||
// of the success transaction.
|
// the success transaction. Don't specify the preimage yet. The preimage
|
||||||
|
// will be supplied by the contract resolver, either directly or when it
|
||||||
|
// becomes known.
|
||||||
successWitness, err := input.ReceiverHtlcSpendRedeem(
|
successWitness, err := input.ReceiverHtlcSpendRedeem(
|
||||||
htlc.Signature, preimage[:], signer, &successSignDesc, successTx,
|
htlc.Signature, nil, signer, &successSignDesc, successTx,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -5554,7 +5546,6 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
|||||||
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
|
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
|
||||||
)
|
)
|
||||||
return &IncomingHtlcResolution{
|
return &IncomingHtlcResolution{
|
||||||
Preimage: preimage,
|
|
||||||
SignedSuccessTx: successTx,
|
SignedSuccessTx: successTx,
|
||||||
CsvDelay: csvDelay,
|
CsvDelay: csvDelay,
|
||||||
ClaimOutpoint: wire.OutPoint{
|
ClaimOutpoint: wire.OutPoint{
|
||||||
@ -5604,7 +5595,7 @@ func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint {
|
|||||||
func extractHtlcResolutions(feePerKw SatPerKWeight, ourCommit bool,
|
func extractHtlcResolutions(feePerKw SatPerKWeight, ourCommit bool,
|
||||||
signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
|
signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||||
commitHash chainhash.Hash, pCache PreimageCache) (*HtlcResolutions, error) {
|
commitHash chainhash.Hash) (*HtlcResolutions, error) {
|
||||||
|
|
||||||
// TODO(roasbeef): don't need to swap csv delay?
|
// TODO(roasbeef): don't need to swap csv delay?
|
||||||
dustLimit := remoteChanCfg.DustLimit
|
dustLimit := remoteChanCfg.DustLimit
|
||||||
@ -5628,19 +5619,11 @@ func extractHtlcResolutions(feePerKw SatPerKWeight, ourCommit bool,
|
|||||||
// If the HTLC is incoming, then we'll attempt to see if we
|
// If the HTLC is incoming, then we'll attempt to see if we
|
||||||
// know the pre-image to the HTLC.
|
// know the pre-image to the HTLC.
|
||||||
if htlc.Incoming {
|
if htlc.Incoming {
|
||||||
// We'll now query the preimage cache for the preimage
|
|
||||||
// for this HTLC. If it's present then we can fully
|
|
||||||
// populate this resolution.
|
|
||||||
preimage, _ := pCache.LookupPreimage(htlc.RHash)
|
|
||||||
|
|
||||||
// Otherwise, we'll create an incoming HTLC resolution
|
// Otherwise, we'll create an incoming HTLC resolution
|
||||||
// as we can satisfy the contract.
|
// as we can satisfy the contract.
|
||||||
var pre [32]byte
|
|
||||||
copy(pre[:], preimage[:])
|
|
||||||
ihr, err := newIncomingHtlcResolution(
|
ihr, err := newIncomingHtlcResolution(
|
||||||
signer, localChanCfg, commitHash, &htlc, keyRing,
|
signer, localChanCfg, commitHash, &htlc, keyRing,
|
||||||
feePerKw, dustLimit, uint32(csvDelay), ourCommit,
|
feePerKw, dustLimit, uint32(csvDelay), ourCommit,
|
||||||
pre,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -5730,7 +5713,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
|
|||||||
|
|
||||||
localCommitment := lc.channelState.LocalCommitment
|
localCommitment := lc.channelState.LocalCommitment
|
||||||
summary, err := NewLocalForceCloseSummary(
|
summary, err := NewLocalForceCloseSummary(
|
||||||
lc.channelState, lc.Signer, lc.pCache, commitTx,
|
lc.channelState, lc.Signer, commitTx,
|
||||||
localCommitment,
|
localCommitment,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -5748,8 +5731,8 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
|
|||||||
// channel state. The passed commitTx must be a fully signed commitment
|
// channel state. The passed commitTx must be a fully signed commitment
|
||||||
// transaction corresponding to localCommit.
|
// transaction corresponding to localCommit.
|
||||||
func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer,
|
func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer,
|
||||||
pCache PreimageCache, commitTx *wire.MsgTx,
|
commitTx *wire.MsgTx, localCommit channeldb.ChannelCommitment) (
|
||||||
localCommit channeldb.ChannelCommitment) (*LocalForceCloseSummary, error) {
|
*LocalForceCloseSummary, error) {
|
||||||
|
|
||||||
// Re-derive the original pkScript for to-self output within the
|
// Re-derive the original pkScript for to-self output within the
|
||||||
// commitment transaction. We'll need this to find the corresponding
|
// commitment transaction. We'll need this to find the corresponding
|
||||||
@ -5830,7 +5813,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
|||||||
htlcResolutions, err := extractHtlcResolutions(
|
htlcResolutions, err := extractHtlcResolutions(
|
||||||
SatPerKWeight(localCommit.FeePerKw), true, signer,
|
SatPerKWeight(localCommit.FeePerKw), true, signer,
|
||||||
localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
||||||
&chanState.RemoteChanCfg, txHash, pCache)
|
&chanState.RemoteChanCfg, txHash,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -544,10 +543,6 @@ func TestForceClose(t *testing.T) {
|
|||||||
t.Fatalf("Can't update the channel state: %v", err)
|
t.Fatalf("Can't update the channel state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before we force close Alice's channel, we'll add the pre-image of
|
|
||||||
// Bob's HTLC to her preimage cache.
|
|
||||||
aliceChannel.pCache.AddPreimages(lntypes.Preimage(preimageBob))
|
|
||||||
|
|
||||||
// With the cache populated, we'll now attempt the force close
|
// With the cache populated, we'll now attempt the force close
|
||||||
// initiated by Alice.
|
// initiated by Alice.
|
||||||
closeSummary, err := aliceChannel.ForceClose()
|
closeSummary, err := aliceChannel.ForceClose()
|
||||||
@ -681,8 +676,11 @@ func TestForceClose(t *testing.T) {
|
|||||||
receiverHtlcScript := closeSummary.CloseTx.TxOut[inHtlcIndex].PkScript
|
receiverHtlcScript := closeSummary.CloseTx.TxOut[inHtlcIndex].PkScript
|
||||||
|
|
||||||
// With the original pkscript located, we'll now verify that the second
|
// With the original pkscript located, we'll now verify that the second
|
||||||
// level transaction can spend from the multi-sig out.
|
// level transaction can spend from the multi-sig out. Supply the
|
||||||
|
// preimage manually. This is usually done by the contract resolver
|
||||||
|
// before publication.
|
||||||
successTx := inHtlcResolution.SignedSuccessTx
|
successTx := inHtlcResolution.SignedSuccessTx
|
||||||
|
successTx.TxIn[0].Witness[3] = preimageBob[:]
|
||||||
vm, err = txscript.NewEngine(receiverHtlcScript,
|
vm, err = txscript.NewEngine(receiverHtlcScript,
|
||||||
successTx, 0, txscript.StandardVerifyFlags, nil,
|
successTx, 0, txscript.StandardVerifyFlags, nil,
|
||||||
nil, int64(htlcAmount.ToSatoshis()))
|
nil, int64(htlcAmount.ToSatoshis()))
|
||||||
@ -778,10 +776,6 @@ func TestForceClose(t *testing.T) {
|
|||||||
"htlcs, got %v htlcs",
|
"htlcs, got %v htlcs",
|
||||||
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
||||||
}
|
}
|
||||||
var zeroHash [32]byte
|
|
||||||
if closeSummary.HtlcResolutions.IncomingHTLCs[0].Preimage != zeroHash {
|
|
||||||
t.Fatalf("bob shouldn't know preimage but does")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestForceCloseDustOutput tests that if either side force closes with an
|
// TestForceCloseDustOutput tests that if either side force closes with an
|
||||||
@ -1459,16 +1453,14 @@ func TestStateUpdatePersistence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
aliceChannelNew, err := NewLightningChannel(
|
aliceChannelNew, err := NewLightningChannel(
|
||||||
aliceChannel.Signer, nil, aliceChannels[0],
|
aliceChannel.Signer, aliceChannels[0], aliceChannel.sigPool,
|
||||||
aliceChannel.sigPool,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bobChannelNew, err := NewLightningChannel(
|
bobChannelNew, err := NewLightningChannel(
|
||||||
bobChannel.Signer, nil, bobChannels[0],
|
bobChannel.Signer, bobChannels[0], bobChannel.sigPool,
|
||||||
bobChannel.sigPool,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
@ -2645,13 +2637,13 @@ func TestChanSyncFullySynced(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
aliceChannelNew, err := NewLightningChannel(
|
aliceChannelNew, err := NewLightningChannel(
|
||||||
aliceChannel.Signer, nil, aliceChannels[0], aliceChannel.sigPool,
|
aliceChannel.Signer, aliceChannels[0], aliceChannel.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
}
|
}
|
||||||
bobChannelNew, err := NewLightningChannel(
|
bobChannelNew, err := NewLightningChannel(
|
||||||
bobChannel.Signer, nil, bobChannels[0], bobChannel.sigPool,
|
bobChannel.Signer, bobChannels[0], bobChannel.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
@ -2673,7 +2665,7 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channelNew, err := NewLightningChannel(
|
channelNew, err := NewLightningChannel(
|
||||||
channelOld.Signer, channelOld.pCache, nodeChannels[0],
|
channelOld.Signer, nodeChannels[0],
|
||||||
channelOld.sigPool,
|
channelOld.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -4943,11 +4935,6 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
t.Fatalf("unable to close: %v", err)
|
t.Fatalf("unable to close: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that Bob has force closed, we'll modify Alice's pre image cache
|
|
||||||
// such that she now gains the ability to also settle the incoming HTLC
|
|
||||||
// from Bob.
|
|
||||||
aliceChannel.pCache.AddPreimages(lntypes.Preimage(preimageBob))
|
|
||||||
|
|
||||||
// We'll then use Bob's transaction to trigger a spend notification for
|
// We'll then use Bob's transaction to trigger a spend notification for
|
||||||
// Alice.
|
// Alice.
|
||||||
closeTx := bobForceClose.CloseTx
|
closeTx := bobForceClose.CloseTx
|
||||||
@ -4958,7 +4945,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
}
|
}
|
||||||
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
||||||
aliceChannel.channelState, aliceChannel.Signer,
|
aliceChannel.channelState, aliceChannel.Signer,
|
||||||
aliceChannel.pCache, spendDetail,
|
spendDetail,
|
||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
)
|
)
|
||||||
@ -5108,7 +5095,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
// our output wasn't picked up.
|
// our output wasn't picked up.
|
||||||
aliceWrongCloseSummary, err := NewUnilateralCloseSummary(
|
aliceWrongCloseSummary, err := NewUnilateralCloseSummary(
|
||||||
aliceChannel.channelState, aliceChannel.Signer,
|
aliceChannel.channelState, aliceChannel.Signer,
|
||||||
aliceChannel.pCache, spendDetail,
|
spendDetail,
|
||||||
aliceChannel.channelState.RemoteCommitment,
|
aliceChannel.channelState.RemoteCommitment,
|
||||||
aliceChannel.channelState.RemoteCurrentRevocation,
|
aliceChannel.channelState.RemoteCurrentRevocation,
|
||||||
)
|
)
|
||||||
@ -5129,7 +5116,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
||||||
aliceChannel.channelState, aliceChannel.Signer,
|
aliceChannel.channelState, aliceChannel.Signer,
|
||||||
aliceChannel.pCache, spendDetail,
|
spendDetail,
|
||||||
aliceRemoteChainTip.Commitment,
|
aliceRemoteChainTip.Commitment,
|
||||||
aliceChannel.channelState.RemoteNextRevocation,
|
aliceChannel.channelState.RemoteNextRevocation,
|
||||||
)
|
)
|
||||||
@ -5926,7 +5913,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) {
|
|||||||
// We now re-create the channels, mimicking a restart. This should sync
|
// We now re-create the channels, mimicking a restart. This should sync
|
||||||
// the update logs up to the correct state set up above.
|
// the update logs up to the correct state set up above.
|
||||||
newAliceChannel, err := NewLightningChannel(
|
newAliceChannel, err := NewLightningChannel(
|
||||||
aliceChannel.Signer, nil, aliceChannel.channelState,
|
aliceChannel.Signer, aliceChannel.channelState,
|
||||||
aliceChannel.sigPool,
|
aliceChannel.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -5934,7 +5921,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newBobChannel, err := NewLightningChannel(
|
newBobChannel, err := NewLightningChannel(
|
||||||
bobChannel.Signer, nil, bobChannel.channelState,
|
bobChannel.Signer, bobChannel.channelState,
|
||||||
bobChannel.sigPool,
|
bobChannel.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -6008,7 +5995,7 @@ func restoreAndAssert(t *testing.T, channel *LightningChannel, numAddsLocal,
|
|||||||
numFailsLocal, numAddsRemote, numFailsRemote int) {
|
numFailsLocal, numAddsRemote, numFailsRemote int) {
|
||||||
|
|
||||||
newChannel, err := NewLightningChannel(
|
newChannel, err := NewLightningChannel(
|
||||||
channel.Signer, nil, channel.channelState,
|
channel.Signer, channel.channelState,
|
||||||
channel.sigPool,
|
channel.sigPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -6318,8 +6305,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) {
|
|||||||
expLocal, expRemote uint64) *LightningChannel {
|
expLocal, expRemote uint64) *LightningChannel {
|
||||||
|
|
||||||
newChannel, err := NewLightningChannel(
|
newChannel, err := NewLightningChannel(
|
||||||
channel.Signer, nil, channel.channelState,
|
channel.Signer, channel.channelState, channel.sigPool,
|
||||||
channel.sigPool,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddressType is an enum-like type which denotes the possible address types
|
// AddressType is an enum-like type which denotes the possible address types
|
||||||
@ -294,21 +293,6 @@ type MessageSigner interface {
|
|||||||
SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error)
|
SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreimageCache is an interface that represents a global cache for preimages.
|
|
||||||
// We'll utilize this cache to communicate the discovery of new preimages
|
|
||||||
// across sub-systems.
|
|
||||||
type PreimageCache interface {
|
|
||||||
// LookupPreimage attempts to look up a preimage according to its hash.
|
|
||||||
// If found, the preimage is returned along with true for the second
|
|
||||||
// argument. Otherwise, it'll return false.
|
|
||||||
LookupPreimage(hash lntypes.Hash) (lntypes.Preimage, bool)
|
|
||||||
|
|
||||||
// AddPreimages adds a batch of newly discovered preimages to the global
|
|
||||||
// cache, and also signals any subscribers of the newly discovered
|
|
||||||
// witness.
|
|
||||||
AddPreimages(preimages ...lntypes.Preimage) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// WalletDriver represents a "driver" for a particular concrete
|
// WalletDriver represents a "driver" for a particular concrete
|
||||||
// WalletController implementation. A driver is identified by a globally unique
|
// WalletController implementation. A driver is identified by a globally unique
|
||||||
// string identifier along with a 'New()' method which is responsible for
|
// string identifier along with a 'New()' method which is responsible for
|
||||||
|
@ -301,13 +301,11 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
aliceSigner := &input.MockSigner{Privkeys: aliceKeys}
|
aliceSigner := &input.MockSigner{Privkeys: aliceKeys}
|
||||||
bobSigner := &input.MockSigner{Privkeys: bobKeys}
|
bobSigner := &input.MockSigner{Privkeys: bobKeys}
|
||||||
|
|
||||||
pCache := newMockPreimageCache()
|
|
||||||
|
|
||||||
// TODO(roasbeef): make mock version of pre-image store
|
// TODO(roasbeef): make mock version of pre-image store
|
||||||
|
|
||||||
alicePool := NewSigPool(1, aliceSigner)
|
alicePool := NewSigPool(1, aliceSigner)
|
||||||
channelAlice, err := NewLightningChannel(
|
channelAlice, err := NewLightningChannel(
|
||||||
aliceSigner, pCache, aliceChannelState, alicePool,
|
aliceSigner, aliceChannelState, alicePool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
@ -316,7 +314,7 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
|||||||
|
|
||||||
bobPool := NewSigPool(1, bobSigner)
|
bobPool := NewSigPool(1, bobSigner)
|
||||||
channelBob, err := NewLightningChannel(
|
channelBob, err := NewLightningChannel(
|
||||||
bobSigner, pCache, bobChannelState, bobPool,
|
bobSigner, bobChannelState, bobPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
@ -780,8 +780,6 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pCache := newMockPreimageCache()
|
|
||||||
|
|
||||||
for i, test := range testCases {
|
for i, test := range testCases {
|
||||||
expectedCommitmentTx, err := txFromHex(test.expectedCommitmentTxHex)
|
expectedCommitmentTx, err := txFromHex(test.expectedCommitmentTxHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -848,7 +846,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
|
|||||||
htlcResolutions, err := extractHtlcResolutions(
|
htlcResolutions, err := extractHtlcResolutions(
|
||||||
SatPerKWeight(test.commitment.FeePerKw), true, signer,
|
SatPerKWeight(test.commitment.FeePerKw), true, signer,
|
||||||
htlcs, keys, channel.localChanCfg, channel.remoteChanCfg,
|
htlcs, keys, channel.localChanCfg, channel.remoteChanCfg,
|
||||||
commitTx.TxHash(), pCache,
|
commitTx.TxHash(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err)
|
t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err)
|
||||||
|
6
peer.go
6
peer.go
@ -429,8 +429,7 @@ func (p *peer) QuitSignal() <-chan struct{} {
|
|||||||
func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
|
func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
|
||||||
for _, dbChan := range chans {
|
for _, dbChan := range chans {
|
||||||
lnChan, err := lnwallet.NewLightningChannel(
|
lnChan, err := lnwallet.NewLightningChannel(
|
||||||
p.server.cc.signer, p.server.witnessBeacon, dbChan,
|
p.server.cc.signer, dbChan, p.server.sigPool,
|
||||||
p.server.sigPool,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1789,8 +1788,7 @@ out:
|
|||||||
// set of active channels, so we can look it up later
|
// set of active channels, so we can look it up later
|
||||||
// easily according to its channel ID.
|
// easily according to its channel ID.
|
||||||
lnChan, err := lnwallet.NewLightningChannel(
|
lnChan, err := lnwallet.NewLightningChannel(
|
||||||
p.server.cc.signer, p.server.witnessBeacon,
|
p.server.cc.signer, newChan, p.server.sigPool,
|
||||||
newChan, p.server.sigPool,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.activeChanMtx.Unlock()
|
p.activeChanMtx.Unlock()
|
||||||
|
@ -1917,7 +1917,7 @@ func (r *rpcServer) fetchActiveChannel(chanPoint wire.OutPoint) (
|
|||||||
// we create a fully populated channel state machine which
|
// we create a fully populated channel state machine which
|
||||||
// uses the db channel as backing storage.
|
// uses the db channel as backing storage.
|
||||||
return lnwallet.NewLightningChannel(
|
return lnwallet.NewLightningChannel(
|
||||||
r.server.cc.wallet.Cfg.Signer, nil, dbChan, nil,
|
r.server.cc.wallet.Cfg.Signer, dbChan, nil,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +303,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
|||||||
|
|
||||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||||
channelAlice, err := lnwallet.NewLightningChannel(
|
channelAlice, err := lnwallet.NewLightningChannel(
|
||||||
aliceSigner, nil, aliceChannelState, alicePool,
|
aliceSigner, aliceChannelState, alicePool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
@ -312,7 +312,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
|||||||
|
|
||||||
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
||||||
channelBob, err := lnwallet.NewLightningChannel(
|
channelBob, err := lnwallet.NewLightningChannel(
|
||||||
bobSigner, nil, bobChannelState, bobPool,
|
bobSigner, bobChannelState, bobPool,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// preimageSubscriber reprints an active subscription to be notified once the
|
// preimageSubscriber reprints an active subscription to be notified once the
|
||||||
@ -144,4 +143,3 @@ func (p *preimageBeacon) AddPreimages(preimages ...lntypes.Preimage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ contractcourt.WitnessBeacon = (*preimageBeacon)(nil)
|
var _ contractcourt.WitnessBeacon = (*preimageBeacon)(nil)
|
||||||
var _ lnwallet.PreimageCache = (*preimageBeacon)(nil)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user