mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-02 14:50:13 +02:00
nip13: parallel DoWork(), deprecate Generate()
This commit is contained in:
parent
2da58356b0
commit
83bd196bae
@ -1,9 +1,11 @@
|
||||
package nip13
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math/bits"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@ -63,12 +65,7 @@ func Check(id string, minDifficulty int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate performs proof of work on the specified event until either the target
|
||||
// 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.
|
||||
// Deprecated: use DoWork()
|
||||
func Generate(event *nostr.Event, targetDifficulty int, timeout time.Duration) (*nostr.Event, error) {
|
||||
if event.PubKey == "" {
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@ -52,20 +53,20 @@ func TestCommittedDifficulty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateShort(t *testing.T) {
|
||||
event := &nostr.Event{
|
||||
func TestDoWorkShort(t *testing.T) {
|
||||
event := nostr.Event{
|
||||
Kind: nostr.KindTextNote,
|
||||
Content: "It's just me mining my own business",
|
||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||
}
|
||||
pow, err := Generate(event, 0, 3*time.Second)
|
||||
pow, err := DoWork(context.Background(), event, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testNonceTag(t, pow, 0)
|
||||
}
|
||||
|
||||
func TestGenerateLong(t *testing.T) {
|
||||
func TestDoWorkLong(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too consuming for short mode")
|
||||
}
|
||||
@ -73,16 +74,18 @@ func TestGenerateLong(t *testing.T) {
|
||||
difficulty := difficulty
|
||||
t.Run(fmt.Sprintf("%dbits", difficulty), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
event := &nostr.Event{
|
||||
event := nostr.Event{
|
||||
Kind: nostr.KindTextNote,
|
||||
Content: "It's just me mining my own business",
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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()
|
||||
tagptr := event.Tags.GetFirst([]string{"nonce"})
|
||||
if tagptr == nil {
|
||||
t.Fatal("no nonce tag")
|
||||
}
|
||||
tag := *tagptr
|
||||
if tag[0] != "nonce" {
|
||||
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) {
|
||||
event := &nostr.Event{
|
||||
func TestDoWorkTimeout(t *testing.T) {
|
||||
event := nostr.Event{
|
||||
Kind: nostr.KindTextNote,
|
||||
Content: "It's just me mining my own business",
|
||||
PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243",
|
||||
}
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
_, err := Generate(event, 256, time.Millisecond)
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
_, err := DoWork(ctx, event, 256)
|
||||
done <- err
|
||||
}()
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
t.Error("Generate took too long to timeout")
|
||||
t.Error("DoWork took too long to timeout")
|
||||
case err := <-done:
|
||||
if !errors.Is(err, ErrGenerateTimeout) {
|
||||
t.Errorf("Generate returned %v; want ErrGenerateTimeout", 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)
|
||||
t.Errorf("DoWork returned %v; want ErrDoWorkTimeout", 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