mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-02 23:00:14 +02:00
nip13: parallel DoWork(), deprecate Generate()
This commit is contained in:
parent
2da58356b0
commit
83bd196bae
@ -1,9 +1,11 @@
|
|||||||
package nip13
|
package nip13
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -63,12 +65,7 @@ func Check(id string, minDifficulty int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate performs proof of work on the specified event until either the target
|
// Deprecated: use DoWork()
|
||||||
// difficulty is reached or the function runs for longer than the timeout.
|
|
||||||
// The latter case results in ErrGenerateTimeout.
|
|
||||||
//
|
|
||||||
// Upon success, the returned event always contains a "nonce" tag with the target difficulty
|
|
||||||
// commitment, and an updated event.CreatedAt.
|
|
||||||
func Generate(event *nostr.Event, targetDifficulty int, timeout time.Duration) (*nostr.Event, error) {
|
func Generate(event *nostr.Event, targetDifficulty int, timeout time.Duration) (*nostr.Event, error) {
|
||||||
if event.PubKey == "" {
|
if event.PubKey == "" {
|
||||||
return nil, ErrMissingPubKey
|
return nil, ErrMissingPubKey
|
||||||
@ -91,3 +88,53 @@ func Generate(event *nostr.Event, targetDifficulty int, timeout time.Duration) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoWork() performs work in multiple threads (given by runtime.NumCPU()) and returns the first
|
||||||
|
// nonce (as a nostr.Tag) that yields the required work.
|
||||||
|
// Returns an error if the context expires before that.
|
||||||
|
func DoWork(ctx context.Context, event nostr.Event, targetDifficulty int) (nostr.Tag, error) {
|
||||||
|
if event.PubKey == "" {
|
||||||
|
return nil, ErrMissingPubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
nthreads := runtime.NumCPU()
|
||||||
|
var nonceTag nostr.Tag
|
||||||
|
|
||||||
|
for i := 0; i < nthreads; i++ {
|
||||||
|
go func(event nostr.Event, nonce uint64) {
|
||||||
|
tag := nostr.Tag{"nonce", "", strconv.Itoa(targetDifficulty)}
|
||||||
|
event.Tags = append(event.Tags, tag)
|
||||||
|
for {
|
||||||
|
// try 10000 times (~30ms)
|
||||||
|
for n := 0; n < 10000; n++ {
|
||||||
|
tag[1] = strconv.FormatUint(nonce, 10)
|
||||||
|
|
||||||
|
if Difficulty(event.GetID()) >= targetDifficulty {
|
||||||
|
nonceTag = tag
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce += uint64(nthreads)
|
||||||
|
}
|
||||||
|
|
||||||
|
// then check if the context was canceled
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// otherwise keep trying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(event, uint64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
if nonceTag != nil {
|
||||||
|
return nonceTag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrGenerateTimeout
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package nip13
|
package nip13
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -52,20 +53,20 @@ func TestCommittedDifficulty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateShort(t *testing.T) {
|
func TestDoWorkShort(t *testing.T) {
|
||||||
event := &nostr.Event{
|
event := nostr.Event{
|
||||||
Kind: nostr.KindTextNote,
|
Kind: nostr.KindTextNote,
|
||||||
Content: "It's just me mining my own business",
|
Content: "It's just me mining my own business",
|
||||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||||
}
|
}
|
||||||
pow, err := Generate(event, 0, 3*time.Second)
|
pow, err := DoWork(context.Background(), event, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
testNonceTag(t, pow, 0)
|
testNonceTag(t, pow, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateLong(t *testing.T) {
|
func TestDoWorkLong(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too consuming for short mode")
|
t.Skip("too consuming for short mode")
|
||||||
}
|
}
|
||||||
@ -73,16 +74,18 @@ func TestGenerateLong(t *testing.T) {
|
|||||||
difficulty := difficulty
|
difficulty := difficulty
|
||||||
t.Run(fmt.Sprintf("%dbits", difficulty), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%dbits", difficulty), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
event := &nostr.Event{
|
event := nostr.Event{
|
||||||
Kind: nostr.KindTextNote,
|
Kind: nostr.KindTextNote,
|
||||||
Content: "It's just me mining my own business",
|
Content: "It's just me mining my own business",
|
||||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||||
}
|
}
|
||||||
pow, err := Generate(event, difficulty, time.Minute)
|
ctx, _ := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
pow, err := DoWork(ctx, event, difficulty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := Check(pow.GetID(), difficulty); err != nil {
|
event.Tags = append(event.Tags, pow)
|
||||||
|
if err := Check(event.GetID(), difficulty); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
testNonceTag(t, pow, difficulty)
|
testNonceTag(t, pow, difficulty)
|
||||||
@ -90,13 +93,8 @@ func TestGenerateLong(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNonceTag(t *testing.T, event *nostr.Event, commitment int) {
|
func testNonceTag(t *testing.T, tag nostr.Tag, commitment int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tagptr := event.Tags.GetFirst([]string{"nonce"})
|
|
||||||
if tagptr == nil {
|
|
||||||
t.Fatal("no nonce tag")
|
|
||||||
}
|
|
||||||
tag := *tagptr
|
|
||||||
if tag[0] != "nonce" {
|
if tag[0] != "nonce" {
|
||||||
t.Errorf("tag[0] = %q; want 'nonce'", tag[0])
|
t.Errorf("tag[0] = %q; want 'nonce'", tag[0])
|
||||||
}
|
}
|
||||||
@ -108,42 +106,24 @@ func testNonceTag(t *testing.T, event *nostr.Event, commitment int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateTimeout(t *testing.T) {
|
func TestDoWorkTimeout(t *testing.T) {
|
||||||
event := &nostr.Event{
|
event := nostr.Event{
|
||||||
Kind: nostr.KindTextNote,
|
Kind: nostr.KindTextNote,
|
||||||
Content: "It's just me mining my own business",
|
Content: "It's just me mining my own business",
|
||||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||||
}
|
}
|
||||||
done := make(chan error)
|
done := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := Generate(event, 256, time.Millisecond)
|
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond)
|
||||||
|
_, err := DoWork(ctx, event, 256)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Error("Generate took too long to timeout")
|
t.Error("DoWork took too long to timeout")
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if !errors.Is(err, ErrGenerateTimeout) {
|
if !errors.Is(err, ErrGenerateTimeout) {
|
||||||
t.Errorf("Generate returned %v; want ErrGenerateTimeout", err)
|
t.Errorf("DoWork returned %v; want ErrDoWorkTimeout", err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCheck(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
Check("000000000e9d97a1ab09fc381030b346cdd7a142ad57e6df0b46dc9bef6c7e2d", 36)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkGenerateOneIteration(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
event := &nostr.Event{
|
|
||||||
Kind: nostr.KindTextNote,
|
|
||||||
Content: "It's just me mining my own business",
|
|
||||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
|
||||||
}
|
|
||||||
if _, err := Generate(event, 0, time.Minute); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,3 +148,25 @@ func BenchmarkGenerate(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkDoWork(b *testing.B) {
|
||||||
|
if testing.Short() {
|
||||||
|
b.Skip("too consuming for short mode")
|
||||||
|
}
|
||||||
|
for _, difficulty := range []int{8, 16, 24} {
|
||||||
|
difficulty := difficulty
|
||||||
|
b.Run(fmt.Sprintf("%dbits", difficulty), func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: nostr.KindTextNote,
|
||||||
|
Content: "It's just me mining my own business",
|
||||||
|
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), time.Second*60)
|
||||||
|
if _, err := DoWork(ctx, *event, difficulty); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user