mirror of
https://github.com/aljazceru/lnd-manageJ.git
synced 2026-01-20 22:44:31 +01:00
get available local balance
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package de.cotto.lndmanagej.controller;
|
||||
|
||||
import de.cotto.lndmanagej.model.Coins;
|
||||
import de.cotto.lndmanagej.service.BalanceService;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import de.cotto.lndmanagej.service.FeeService;
|
||||
import de.cotto.lndmanagej.service.NodeService;
|
||||
@@ -51,6 +52,9 @@ class LegacyControllerIT {
|
||||
@MockBean
|
||||
private FeeService feeService;
|
||||
|
||||
@MockBean
|
||||
private BalanceService balanceService;
|
||||
|
||||
@Test
|
||||
void getAlias() throws Exception {
|
||||
when(nodeService.getAlias(PUBKEY)).thenReturn(ALIAS);
|
||||
@@ -136,4 +140,12 @@ class LegacyControllerIT {
|
||||
mockMvc.perform(get(CHANNEL_BASE + "/outgoing-base-fee"))
|
||||
.andExpect(content().string(String.valueOf(BASE_FEE.milliSatoshis())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAvailableLocalBalance() throws Exception {
|
||||
Coins availableBalance = Coins.ofSatoshis(999);
|
||||
when(balanceService.getAvailableLocalBalance(CHANNEL_ID)).thenReturn(availableBalance);
|
||||
mockMvc.perform(get(CHANNEL_BASE + "/available-local-balance"))
|
||||
.andExpect(content().string(String.valueOf(availableBalance.satoshis())));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import de.cotto.lndmanagej.model.Channel;
|
||||
import de.cotto.lndmanagej.model.ChannelId;
|
||||
import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import de.cotto.lndmanagej.model.Pubkey;
|
||||
import de.cotto.lndmanagej.service.BalanceService;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import de.cotto.lndmanagej.service.FeeService;
|
||||
import de.cotto.lndmanagej.service.NodeService;
|
||||
@@ -25,17 +26,20 @@ public class LegacyController {
|
||||
private final ChannelService channelService;
|
||||
private final OwnNodeService ownNodeService;
|
||||
private final FeeService feeService;
|
||||
private final BalanceService balanceService;
|
||||
|
||||
public LegacyController(
|
||||
NodeService nodeService,
|
||||
ChannelService channelService,
|
||||
OwnNodeService ownNodeService,
|
||||
FeeService feeService
|
||||
FeeService feeService,
|
||||
BalanceService balanceService
|
||||
) {
|
||||
this.nodeService = nodeService;
|
||||
this.channelService = channelService;
|
||||
this.ownNodeService = ownNodeService;
|
||||
this.feeService = feeService;
|
||||
this.balanceService = balanceService;
|
||||
}
|
||||
|
||||
@GetMapping("/node/{pubkey}/alias")
|
||||
@@ -115,6 +119,11 @@ public class LegacyController {
|
||||
return feeService.getOutgoingBaseFee(channelId).milliSatoshis();
|
||||
}
|
||||
|
||||
@GetMapping("/channel/{channelId}/available-local-balance")
|
||||
public long getAvailableLocalBalance(ChannelId channelId) {
|
||||
return balanceService.getAvailableLocalBalance(channelId).satoshis();
|
||||
}
|
||||
|
||||
private Stream<ChannelId> getOpenChannelIdsSorted() {
|
||||
return channelService.getOpenChannels().stream()
|
||||
.map(Channel::getId)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.cotto.lndmanagej.service;
|
||||
|
||||
import de.cotto.lndmanagej.grpc.GrpcChannels;
|
||||
import de.cotto.lndmanagej.model.ChannelId;
|
||||
import de.cotto.lndmanagej.model.Coins;
|
||||
import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class BalanceService {
|
||||
private final GrpcChannels grpcChannels;
|
||||
|
||||
public BalanceService(GrpcChannels grpcChannels) {
|
||||
this.grpcChannels = grpcChannels;
|
||||
}
|
||||
|
||||
public Coins getLocalBalance(ChannelId channelId) {
|
||||
return grpcChannels.getChannel(channelId).map(LocalChannel::getLocalBalance).orElse(Coins.NONE);
|
||||
}
|
||||
|
||||
public Coins getLocalReserve(ChannelId channelId) {
|
||||
return grpcChannels.getChannel(channelId).map(LocalChannel::getLocalReserve).orElse(Coins.NONE);
|
||||
}
|
||||
|
||||
public Coins getAvailableLocalBalance(ChannelId channelId) {
|
||||
Coins available = grpcChannels.getChannel(channelId)
|
||||
.map(c -> c.getLocalBalance().subtract(c.getLocalReserve()))
|
||||
.orElse(Coins.NONE);
|
||||
if (available.isNegative()) {
|
||||
return Coins.NONE;
|
||||
}
|
||||
return available;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package de.cotto.lndmanagej.controller;
|
||||
|
||||
import de.cotto.lndmanagej.model.Channel;
|
||||
import de.cotto.lndmanagej.model.ChannelFixtures;
|
||||
import de.cotto.lndmanagej.model.Coins;
|
||||
import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import de.cotto.lndmanagej.service.BalanceService;
|
||||
import de.cotto.lndmanagej.service.ChannelService;
|
||||
import de.cotto.lndmanagej.service.FeeService;
|
||||
import de.cotto.lndmanagej.service.NodeService;
|
||||
@@ -53,6 +55,9 @@ class LegacyControllerTest {
|
||||
@Mock
|
||||
private FeeService feeService;
|
||||
|
||||
@Mock
|
||||
private BalanceService balanceService;
|
||||
|
||||
@Test
|
||||
void getAlias() {
|
||||
when(nodeService.getAlias(PUBKEY)).thenReturn(ALIAS);
|
||||
@@ -131,14 +136,16 @@ class LegacyControllerTest {
|
||||
|
||||
@Test
|
||||
void getPeerPubkeys() {
|
||||
LocalChannel channel2 = new LocalChannel(ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2), PUBKEY);
|
||||
Channel channel = ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2);
|
||||
LocalChannel channel2 = new LocalChannel(channel, PUBKEY, Coins.NONE, Coins.NONE);
|
||||
when(channelService.getOpenChannels()).thenReturn(Set.of(LOCAL_CHANNEL, channel2));
|
||||
assertThat(legacyController.getPeerPubkeys()).isEqualTo(PUBKEY_2 + "\n" + PUBKEY_3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPeerPubkeys_sorted() {
|
||||
LocalChannel channel2 = new LocalChannel(ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2), PUBKEY);
|
||||
Channel channel = ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2);
|
||||
LocalChannel channel2 = new LocalChannel(channel, PUBKEY, Coins.NONE, Coins.NONE);
|
||||
when(channelService.getOpenChannels()).thenReturn(Set.of(channel2, LOCAL_CHANNEL));
|
||||
assertThat(legacyController.getPeerPubkeys()).isEqualTo(PUBKEY_2 + "\n" + PUBKEY_3);
|
||||
}
|
||||
@@ -172,4 +179,10 @@ class LegacyControllerTest {
|
||||
when(feeService.getOutgoingBaseFee(CHANNEL_ID)).thenReturn(Coins.ofMilliSatoshis(10L));
|
||||
assertThat(legacyController.getOutgoingBaseFee(CHANNEL_ID)).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAvailableLocalBalance() {
|
||||
when(balanceService.getAvailableLocalBalance(CHANNEL_ID)).thenReturn(Coins.ofSatoshis(123L));
|
||||
assertThat(legacyController.getAvailableLocalBalance(CHANNEL_ID)).isEqualTo(123);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.cotto.lndmanagej.service;
|
||||
|
||||
import de.cotto.lndmanagej.grpc.GrpcChannels;
|
||||
import de.cotto.lndmanagej.model.Coins;
|
||||
import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.LOCAL_BALANCE;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.LOCAL_CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.RESERVE_LOCAL;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BalanceServiceTest {
|
||||
@InjectMocks
|
||||
private BalanceService balanceService;
|
||||
|
||||
@Mock
|
||||
private GrpcChannels grpcChannels;
|
||||
|
||||
@Test
|
||||
void getLocalBalance() {
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_CHANNEL));
|
||||
assertThat(balanceService.getLocalBalance(CHANNEL_ID)).isEqualTo(LOCAL_BALANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLocalBalance_no_channel() {
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.empty());
|
||||
assertThat(balanceService.getLocalBalance(CHANNEL_ID)).isEqualTo(Coins.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLocalReserve() {
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_CHANNEL));
|
||||
assertThat(balanceService.getLocalReserve(CHANNEL_ID)).isEqualTo(RESERVE_LOCAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAvailableLocalBalance() {
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(LOCAL_CHANNEL));
|
||||
assertThat(balanceService.getAvailableLocalBalance(CHANNEL_ID))
|
||||
.isEqualTo(LOCAL_BALANCE.subtract(RESERVE_LOCAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAvailableLocalBalance_negative() {
|
||||
LocalChannel localChannel = new LocalChannel(CHANNEL, PUBKEY, Coins.ofSatoshis(99), Coins.ofSatoshis(100));
|
||||
when(grpcChannels.getChannel(CHANNEL_ID)).thenReturn(Optional.of(localChannel));
|
||||
assertThat(balanceService.getAvailableLocalBalance(CHANNEL_ID)).isEqualTo(Coins.NONE);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package de.cotto.lndmanagej.service;
|
||||
|
||||
import de.cotto.lndmanagej.grpc.GrpcChannels;
|
||||
import de.cotto.lndmanagej.model.Channel;
|
||||
import de.cotto.lndmanagej.model.ChannelFixtures;
|
||||
import de.cotto.lndmanagej.model.Coins;
|
||||
import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -37,7 +39,8 @@ class ChannelServiceTest {
|
||||
|
||||
@Test
|
||||
void getOpenChannelsWith_ignores_channel_to_other_node() {
|
||||
LocalChannel localChannel2 = new LocalChannel(ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2), PUBKEY);
|
||||
Channel channel = ChannelFixtures.create(PUBKEY, PUBKEY_3, CHANNEL_ID_2);
|
||||
LocalChannel localChannel2 = new LocalChannel(channel, PUBKEY, Coins.NONE, Coins.NONE);
|
||||
when(grpcChannels.getChannels()).thenReturn(Set.of(LOCAL_CHANNEL, localChannel2, LOCAL_CHANNEL_3));
|
||||
assertThat(channelService.getOpenChannelsWith(PUBKEY_2))
|
||||
.containsExactlyInAnyOrder(LOCAL_CHANNEL, LOCAL_CHANNEL_3);
|
||||
|
||||
@@ -7,6 +7,7 @@ import de.cotto.lndmanagej.model.LocalChannel;
|
||||
import de.cotto.lndmanagej.model.Pubkey;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
@@ -31,6 +32,15 @@ public class GrpcChannels {
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
public Optional<LocalChannel> getChannel(ChannelId channelId) {
|
||||
Pubkey ownPubkey = grpcGetInfo.getPubkey();
|
||||
long expectedChannelId = channelId.shortChannelId();
|
||||
return grpcService.getChannels().stream()
|
||||
.filter(c -> c.getChanId() == expectedChannelId)
|
||||
.map(lndChannel -> toChannel(lndChannel, ownPubkey))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private LocalChannel toChannel(lnrpc.Channel lndChannel, Pubkey ownPubkey) {
|
||||
Channel channel = Channel.builder()
|
||||
.withChannelId(ChannelId.fromShortChannelId(lndChannel.getChanId()))
|
||||
@@ -38,7 +48,9 @@ public class GrpcChannels {
|
||||
.withNode1(ownPubkey)
|
||||
.withNode2(Pubkey.create(lndChannel.getRemotePubkey()))
|
||||
.build();
|
||||
return new LocalChannel(channel, ownPubkey);
|
||||
Coins localBalance = Coins.ofSatoshis(lndChannel.getLocalBalance());
|
||||
Coins localReserve = Coins.ofSatoshis(lndChannel.getLocalConstraints().getChanReserveSat());
|
||||
return new LocalChannel(channel, ownPubkey, localBalance, localReserve);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.cotto.lndmanagej.grpc;
|
||||
|
||||
import de.cotto.lndmanagej.model.ChannelId;
|
||||
import lnrpc.Channel;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -12,6 +13,7 @@ import java.util.List;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelFixtures.CAPACITY;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.LOCAL_CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_2;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
|
||||
@@ -35,19 +37,30 @@ class GrpcChannelsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void no_channels() {
|
||||
void getChannels_no_channels() {
|
||||
assertThat(grpcChannels.getChannels()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void one_channel() {
|
||||
when(grpcService.getChannels()).thenReturn(List.of(channel()));
|
||||
void getChannels_one_channel() {
|
||||
when(grpcService.getChannels()).thenReturn(List.of(channel(CHANNEL_ID)));
|
||||
assertThat(grpcChannels.getChannels()).containsExactly(LOCAL_CHANNEL);
|
||||
}
|
||||
|
||||
private Channel channel() {
|
||||
@Test
|
||||
void getChannel() {
|
||||
when(grpcService.getChannels()).thenReturn(List.of(channel(CHANNEL_ID_2), channel(CHANNEL_ID)));
|
||||
assertThat(grpcChannels.getChannel(CHANNEL_ID)).contains(LOCAL_CHANNEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getChannel_empty() {
|
||||
assertThat(grpcChannels.getChannel(CHANNEL_ID)).isEmpty();
|
||||
}
|
||||
|
||||
private Channel channel(ChannelId channelId) {
|
||||
return Channel.newBuilder()
|
||||
.setChanId(CHANNEL_ID.shortChannelId())
|
||||
.setChanId(channelId.shortChannelId())
|
||||
.setCapacity(CAPACITY.satoshis())
|
||||
.setRemotePubkey(NODE_2.pubkey().toString())
|
||||
.build();
|
||||
|
||||
@@ -4,9 +4,13 @@ import java.util.Set;
|
||||
|
||||
public class LocalChannel extends Channel {
|
||||
private final Pubkey remotePubkey;
|
||||
private final Coins localBalance;
|
||||
private final Coins localReserve;
|
||||
|
||||
public LocalChannel(Channel channel, Pubkey ownPubkey) {
|
||||
public LocalChannel(Channel channel, Pubkey ownPubkey, Coins localBalance, Coins localReserve) {
|
||||
super(channel.getId(), channel.getCapacity(), channel.getPubkeys());
|
||||
this.localBalance = localBalance;
|
||||
this.localReserve = localReserve;
|
||||
Set<Pubkey> pubkeys = channel.getPubkeys();
|
||||
remotePubkey = pubkeys.stream()
|
||||
.filter(pubkey -> !ownPubkey.equals(pubkey))
|
||||
@@ -20,4 +24,12 @@ public class LocalChannel extends Channel {
|
||||
public Pubkey getRemotePubkey() {
|
||||
return remotePubkey;
|
||||
}
|
||||
|
||||
public Coins getLocalBalance() {
|
||||
return localBalance;
|
||||
}
|
||||
|
||||
public Coins getLocalReserve() {
|
||||
return localReserve;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_2;
|
||||
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.LOCAL_BALANCE;
|
||||
import static de.cotto.lndmanagej.model.LocalChannelFixtures.LOCAL_CHANNEL;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_3;
|
||||
@@ -13,20 +15,27 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
class LocalChannelTest {
|
||||
@Test
|
||||
void getRemotePubkey() {
|
||||
LocalChannel localChannel = new LocalChannel(ChannelFixtures.create(PUBKEY_2, PUBKEY, CHANNEL_ID), PUBKEY);
|
||||
Channel channel = ChannelFixtures.create(PUBKEY_2, PUBKEY, CHANNEL_ID);
|
||||
LocalChannel localChannel = new LocalChannel(channel, PUBKEY, Coins.NONE, Coins.NONE);
|
||||
assertThat(localChannel.getRemotePubkey()).isEqualTo(PUBKEY_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRemotePubkey_swapped() {
|
||||
LocalChannel localChannel = new LocalChannel(ChannelFixtures.create(PUBKEY_3, PUBKEY_2, CHANNEL_ID), PUBKEY_3);
|
||||
Channel channel = ChannelFixtures.create(PUBKEY_3, PUBKEY_2, CHANNEL_ID);
|
||||
LocalChannel localChannel = new LocalChannel(channel, PUBKEY_3, Coins.NONE, Coins.NONE);
|
||||
assertThat(localChannel.getRemotePubkey()).isEqualTo(PUBKEY_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ownPubkey_not_in_pubkey_set() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new LocalChannel(CHANNEL_2, PUBKEY_3))
|
||||
.isThrownBy(() -> new LocalChannel(CHANNEL_2, PUBKEY_3, Coins.NONE, Coins.NONE))
|
||||
.withMessage("Channel must have given pubkey as peer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLocalBalance() {
|
||||
assertThat(LOCAL_CHANNEL.getLocalBalance()).isEqualTo(LOCAL_BALANCE);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,15 @@ import static de.cotto.lndmanagej.model.ChannelFixtures.CHANNEL_TO_NODE_3;
|
||||
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
|
||||
|
||||
public class LocalChannelFixtures {
|
||||
public static final LocalChannel LOCAL_CHANNEL = new LocalChannel(CHANNEL, PUBKEY);
|
||||
public static final LocalChannel LOCAL_CHANNEL_2 = new LocalChannel(CHANNEL_2, PUBKEY);
|
||||
public static final LocalChannel LOCAL_CHANNEL_3 = new LocalChannel(CHANNEL_3, PUBKEY);
|
||||
public static final LocalChannel LOCAL_CHANNEL_TO_NODE_3 = new LocalChannel(CHANNEL_TO_NODE_3, PUBKEY);
|
||||
public static final Coins LOCAL_BALANCE = Coins.ofSatoshis(1_000);
|
||||
public static final Coins RESERVE_LOCAL = Coins.ofSatoshis(100);
|
||||
|
||||
public static final LocalChannel LOCAL_CHANNEL =
|
||||
new LocalChannel(CHANNEL, PUBKEY, LOCAL_BALANCE, RESERVE_LOCAL);
|
||||
public static final LocalChannel LOCAL_CHANNEL_2 =
|
||||
new LocalChannel(CHANNEL_2, PUBKEY, LOCAL_BALANCE, RESERVE_LOCAL);
|
||||
public static final LocalChannel LOCAL_CHANNEL_3 =
|
||||
new LocalChannel(CHANNEL_3, PUBKEY, LOCAL_BALANCE, RESERVE_LOCAL);
|
||||
public static final LocalChannel LOCAL_CHANNEL_TO_NODE_3 =
|
||||
new LocalChannel(CHANNEL_TO_NODE_3, PUBKEY, LOCAL_BALANCE, RESERVE_LOCAL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user