Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix for issue 139 #197

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/Angor/Client/Pages/Create.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
@inject IRelayService _RelayService
@inject ILogger<Create> _Logger;
@inject IHtmlStripperService HtmlStripperService;
@inject IFounderKeyService FounderKeyService


@inject IFounderTransactionActions _founderTransactionActions
<NotificationComponent @ref="notificationComponent"/>
Expand Down Expand Up @@ -847,6 +849,21 @@

try
{
// Check if the FounderKey is in use and get the total keys used
var keyCheckResult = await FounderKeyService.CheckFounderKeyAsync(project.ProjectInfo.FounderKey);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting this in the CreatProjectTransaction is probably not the right thing to do, we must know much before this point if the key was already used

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do rescan before creating,
But there's a scenario this scenario.

  1. Logged on browser A, B.
  2. start create browser A.
  3. Start create browser B.
  4. Create browser B.
  5. Create Browser A.

unless you put a rescan when creating, you won't know that there's another project created on the same key.
but also now that i think of it we will need to refresh the data in the create a project. (almost everything except project info no?)

I know that this is an edge case that is not expected (especially when we suspect that per user there will be probably only 1 project), but still this should be addressed in some way because we cannot have any loophole of over riding a project.

Copy link
Collaborator

@DavidGershony DavidGershony Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to have a meeting about trusting the relays for knowing if the project was created or only look at the indexer to see if a transaciton exists
Bare in mind that we keep the nostr event id on the transaction so it is not possible to show false data there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then i'll wait with this PR until we do that,
We should align on how much we trust the relays, and on which instances we can trust it.

Also, according to that we should check if there are other places in angor that trust the relay instead of the indexer?


if (keyCheckResult.IsKeyInUse)
{
notificationComponent.ShowErrorMessage("The FounderKey is already in use. Please use a different key.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we need to "fix" the data in the local storage because the user can't select a founder key to use, perhaps add a message to rescan or wipe the storage and recover the wallet again.

return;
}

if (keyCheckResult.TotalKeysInUse >= 14)
{
notificationComponent.ShowErrorMessage("You cannot create more than 15 projects.");
return;
}

var words = await passwordComponent.GetWalletAsync();
var accountInfo = storage.GetAccountInfo(network.Name);

Expand All @@ -863,7 +880,6 @@
}
catch (Exception e)
{
_Logger.LogError(e, e.Message);
notificationComponent.ShowErrorMessage(e.Message,e);
}
finally
Expand Down
80 changes: 80 additions & 0 deletions src/Angor/Client/Services/FounderKeyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Angor.Shared.Models;
using Angor.Shared.Services;
using Nostr.Client.Messages;
using Angor.Client.Storage;

public class FounderKeyService : IFounderKeyService
{
private readonly IWalletStorage _walletStorage;
private readonly IRelayService _relayService;
private readonly ISerializer _serializer;




public FounderKeyService(
IWalletStorage walletStorage,
IIndexerService indexerService,
IRelayService relayService,
IClientStorage storage,
ISerializer serializer)
{
_walletStorage = walletStorage;
_relayService = relayService;
_serializer = serializer;
}

public async Task<bool> IsFounderKeyInUseAsync(string founderKey)
{
var keys = _walletStorage.GetFounderKeys();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the relay is the right place to check, we need to check with the indexer if the transaction exists.

var inUseKeys = new HashSet<string>();

await _relayService.RequestProjectCreateEventsByPubKeyAsync(
keys.Keys.Select(k => k.NostrPubKey).ToArray(),
eventMessage =>
{
if (eventMessage.Kind == NostrKind.ApplicationSpecificData)
{
var projectInfo = _serializer.Deserialize<ProjectInfo>(eventMessage.Content);
inUseKeys.Add(projectInfo.FounderKey);
dangershony marked this conversation as resolved.
Show resolved Hide resolved
}
},
null);

return inUseKeys.Contains(founderKey);
}

public async Task<FounderKeyCheckResult> CheckFounderKeyAsync(string founderKey)
{
var keys = _walletStorage.GetFounderKeys();
var usedKeys = new HashSet<string>();

// Perform the relay scan to get project data
await _relayService.RequestProjectCreateEventsByPubKeyAsync(
keys.Keys.Select(k => k.NostrPubKey).ToArray(),
eventMessage =>
{
if (eventMessage.Kind == NostrKind.ApplicationSpecificData)
{
var projectInfo = _serializer.Deserialize<ProjectInfo>(eventMessage.Content);
usedKeys.Add(projectInfo.FounderKey);
}
},
null);

return new FounderKeyCheckResult
{
IsKeyInUse = usedKeys.Contains(founderKey),
TotalKeysInUse = usedKeys.Count
};
}


}


public class FounderKeyCheckResult
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class should be in the models folder

{
public bool IsKeyInUse { get; set; }
public int TotalKeysInUse { get; set; }
}
7 changes: 7 additions & 0 deletions src/Angor/Client/Services/IFounderKeyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public interface IFounderKeyService
{
Task<bool> IsFounderKeyInUseAsync(string founderKey);

Task<FounderKeyCheckResult> CheckFounderKeyAsync(string founderKey);

}
2 changes: 2 additions & 0 deletions src/Angor/Shared/Services/IRelayService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public interface IRelayService

string SendDirectMessagesForPubKeyAsync(string senderNosterPrivateKey, string nostrPubKey, string encryptedMessage,
Action<NostrOkResponse> onResponseAction);
Task RequestProjectCreateEventsByPubKeyAsync(string[] nPubs, Action<NostrEvent> onResponseAction, Action? onEoseAction);

}
52 changes: 52 additions & 0 deletions src/Angor/Shared/Services/RelayService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,58 @@ public void RequestProjectCreateEventsByPubKey(Action<NostrEvent> onResponseActi
Kinds = [NostrKind.ApplicationSpecificData, NostrKind.Metadata],
}));
}

// add the same as function ^^ as async
public async Task RequestProjectCreateEventsByPubKeyAsync(
string[] nPubs,
Action<NostrEvent> onResponseAction,
Action? onEoseAction)
{
var subscriptionKey = Guid.NewGuid().ToString().Replace("-", "");

var nostrClient = _communicationFactory.GetOrCreateClient(_networkService);

if (!_subscriptionsHandling.RelaySubscriptionAdded(subscriptionKey))
{
var subscription = nostrClient.Streams.EventStream
.Where(_ => _.Subscription == subscriptionKey)
.Where(_ => _.Event is not null)
.Select(_ => _.Event)
.Subscribe(onResponseAction!);

_subscriptionsHandling.TryAddRelaySubscription(subscriptionKey, subscription);
}

if (onEoseAction != null)
{
_subscriptionsHandling.TryAddEoseAction(subscriptionKey, onEoseAction);
}

var tcs = new TaskCompletionSource();

// Handle end-of-stream asynchronously
if (onEoseAction == null)
{
_subscriptionsHandling.TryAddEoseAction(subscriptionKey, () => tcs.SetResult());
}
else
{
_subscriptionsHandling.TryAddEoseAction(subscriptionKey, () =>
{
onEoseAction();
tcs.SetResult();
});
}

nostrClient.Send(new NostrRequest(subscriptionKey, new NostrFilter
{
Authors = nPubs,
Kinds = new[] { NostrKind.ApplicationSpecificData, NostrKind.Metadata },
}));

await tcs.Task; // Wait for the end of stream
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we awaiting the tcs and not just allowing the callback thread to perfome the action we need?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it because of this
https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern

why shouldnt we need TaskCompletionSource to wait for end of steam?

either way copilot removed it 😉

}


public Task LookupSignaturesDirectMessagesForPubKeyAsync(string nostrPubKey, DateTime? since, int? limit, Action<NostrEvent> onResponseAction)
{
Expand Down
Loading