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

feat(SPV-789): extend PIKE capability #91

Merged
merged 6 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion brfc_definintions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ const (
BRFCVerifyPublicKeyOwner = "a9f510c16bde" // more info: http://bsvalias.org/05-verify-public-key-owner.html
BRFCBeefTransaction = "5c55a7fdb7bb" // more info: https://bsv.brc.dev/payments/0070

BRFCPike = "8c4ed5ef8ace" // more info: TODO BUX-665
BRFCTemporaryPike = "8c4ed5ef8ace" // Temporary BRFC ID for PIKE

BRFCPike = "935478af7bf2"
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
BRFCPikeInvite = "invite"
BRFCPikeOutputs = "outputs"
)

// BRFCKnownSpecifications is a running list of all known BRFC specifications
Expand Down
9 changes: 9 additions & 0 deletions examples/server/run_server/demo_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,12 @@ func (d *demoServiceProvider) AddContact(
) error {
return nil
}

func (d *demoServiceProvider) CreatePikeDestinationResponse(
ctx context.Context,
alias, domain string,
satoshis uint64,
metaData *server.RequestMetadata,
) (*paymail.PikePaymentOutputsResponse, error) {
return nil, nil
}
3 changes: 2 additions & 1 deletion examples/server/run_server/run_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func main() {

sl := server.PaymailServiceLocator{}
sl.RegisterPaymailService(new(demoServiceProvider))
sl.RegisterPikeService(new(demoServiceProvider))
sl.RegisterPikeContactService(new(demoServiceProvider))
sl.RegisterPikePaymentService(new(demoServiceProvider))

// Custom server with lots of customizable goodies
config, err := server.NewConfig(
Expand Down
26 changes: 26 additions & 0 deletions pike.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,43 @@ import (
"fmt"
"net/http"
"strings"
"time"
)

// PikeContactRequestResponse is PIKE wrapper for StandardResponse
type PikeContactRequestResponse struct {
StandardResponse
}

// PikeContactRequestPayload is a payload used to request a contact
type PikeContactRequestPayload struct {
FullName string `json:"fullName"`
Paymail string `json:"paymail"`
}

// PikePaymentOutputsPayload is a payload needed to get payment outputs
// TODO: check if everything is needed after whole PIKE implementation
type PikePaymentOutputsPayload struct {
SenderName string `json:"senderName"`
SenderPaymail string `json:"senderPaymail"`
Amount uint64 `json:"amount"`
Dt time.Time `json:"dt"`
Reference string `json:"reference"`
Signature string `json:"signature"`
}

// PikePaymentOutputsResponse is a response which contain output templates
type PikePaymentOutputsResponse struct {
Outputs []PikePaymentOutput `json:"outputs"`
Reference string `json:"reference"`
}

// PikePaymentOutput is a single output template with satoshis
type PikePaymentOutput struct {
Script string `json:"script"`
Satoshis int `json:"satoshis"`
}

func (c *Client) AddContactRequest(url, alias, domain string, request *PikeContactRequestPayload) (*PikeContactRequestResponse, error) {

if err := c.validateUrlWithPaymail(url, alias, domain); err != nil {
Expand Down
52 changes: 48 additions & 4 deletions server/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package server

import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strings"

"github.com/gin-gonic/gin"

"github.com/bitcoin-sv/go-paymail"
)

Expand All @@ -16,6 +15,7 @@ type CallableCapability struct {
Handler gin.HandlerFunc
}

type NestedCapabilitiesMap map[string]CallableCapabilitiesMap
type CallableCapabilitiesMap map[string]CallableCapability
type StaticCapabilitiesMap map[string]any

Expand Down Expand Up @@ -80,16 +80,41 @@ func (c *Configuration) SetBeefCapabilities() {
)
}

func (c *Configuration) SetPikeCapabilities() {
func (c *Configuration) SetPikeContactCapabilities() {
_addCapabilities(c.callableCapabilities,
CallableCapabilitiesMap{
paymail.BRFCPike: CallableCapability{
paymail.BRFCTemporaryPike: CallableCapability{
Path: fmt.Sprintf("/pike/%s", PaymailAddressTemplate),
Method: http.MethodPost,
Handler: c.pikeNewContact,
},
},
)
_addNestedCapabilities(c.nestedCapabilities,
NestedCapabilitiesMap{
paymail.BRFCPike: CallableCapabilitiesMap{
paymail.BRFCPikeInvite: CallableCapability{
Path: fmt.Sprintf("/contact/invite/%s", PaymailAddressTemplate),
Method: http.MethodPost,
Handler: c.pikeNewContact,
},
},
},
)
}

func (c *Configuration) SetPikePaymentCapabilities() {
_addNestedCapabilities(c.nestedCapabilities,
NestedCapabilitiesMap{
paymail.BRFCPike: CallableCapabilitiesMap{
paymail.BRFCPikeOutputs: CallableCapability{
Path: fmt.Sprintf("/pike/outputs/%s", PaymailAddressTemplate),
Method: http.MethodPost,
Handler: c.pikeGetPaymentDestinations,
},
},
},
)
}

func _addCapabilities[T any](base map[string]T, newCaps map[string]T) {
Expand All @@ -98,6 +123,15 @@ func _addCapabilities[T any](base map[string]T, newCaps map[string]T) {
}
}

func _addNestedCapabilities(base NestedCapabilitiesMap, newCaps NestedCapabilitiesMap) {
for key, val := range newCaps {
if _, ok := base[key]; !ok {
base[key] = make(CallableCapabilitiesMap)
}
_addCapabilities(base[key], val)
}
}

// showCapabilities will return the service discovery results for the server
// and list all active capabilities of the Paymail server
//
Expand Down Expand Up @@ -142,6 +176,16 @@ func (c *Configuration) EnrichCapabilities(host string) (*paymail.CapabilitiesPa
for key, cap := range c.callableCapabilities {
payload.Capabilities[key] = serviceUrl + string(cap.Path)
}
for key, cap := range c.nestedCapabilities {
payload.Capabilities[key] = make(map[string]interface{})
for nestedKey, nestedCap := range cap {
nestedObj, ok := payload.Capabilities[key].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("failed to cast nested capabilities")
}
nestedObj[nestedKey] = serviceUrl + nestedCap.Path
}
}
return payload, nil
}

Expand Down
22 changes: 15 additions & 7 deletions server/config.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package server

import (
"github.com/rs/zerolog"
"slices"
"strings"
"time"

"github.com/rs/zerolog"

"github.com/bitcoin-sv/go-paymail"
)

Expand All @@ -23,14 +22,17 @@ type Configuration struct {
GenericCapabilitiesEnabled bool `json:"generic_capabilities_enabled"`
P2PCapabilitiesEnabled bool `json:"p2p_capabilities_enabled"`
BeefCapabilitiesEnabled bool `json:"beef_capabilities_enabled"`
PikeCapabilitiesEnabled bool `json:"pike_capabilities_enabled"`
PikeContactCapabilitiesEnabled bool `json:"pike_contact_capabilities_enabled"`
PikePaymentCapabilitiesEnabled bool `json:"pike_payment_capabilities_enabled"`
ServiceName string `json:"service_name"`
Timeout time.Duration `json:"timeout"`
Logger *zerolog.Logger `json:"logger"`

// private
actions PaymailServiceProvider
pikeActions PikeServiceProvider
pikeContactActions PikeContactServiceProvider
pikePaymentActions PikePaymentServiceProvider
nestedCapabilities NestedCapabilitiesMap
callableCapabilities CallableCapabilitiesMap
staticCapabilities StaticCapabilitiesMap
}
Expand Down Expand Up @@ -139,9 +141,15 @@ func NewConfig(serviceProvider *PaymailServiceLocator, opts ...ConfigOps) (*Conf
if config.BeefCapabilitiesEnabled {
config.SetBeefCapabilities()
}
if config.PikeCapabilitiesEnabled {
config.SetPikeCapabilities()
config.pikeActions = serviceProvider.GetPikeService()

if config.PikeContactCapabilitiesEnabled {
config.SetPikeContactCapabilities()
config.pikeContactActions = serviceProvider.GetPikeContactService()
}

if config.PikePaymentCapabilitiesEnabled {
config.SetPikePaymentCapabilities()
config.pikePaymentActions = serviceProvider.GetPikePaymentService()
}

// Validate the configuration
Expand Down
17 changes: 13 additions & 4 deletions server/config_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ func defaultConfigOptions() *Configuration {
GenericCapabilitiesEnabled: true,
P2PCapabilitiesEnabled: false,
BeefCapabilitiesEnabled: false,
PikeCapabilitiesEnabled: false,
PikeContactCapabilitiesEnabled: false,
PikePaymentCapabilitiesEnabled: false,
ServiceName: paymail.DefaultServiceName,
Timeout: DefaultTimeout,
Logger: logging.GetDefaultLogger(),
nestedCapabilities: make(NestedCapabilitiesMap),
callableCapabilities: make(CallableCapabilitiesMap),
staticCapabilities: make(StaticCapabilitiesMap),
}
Expand Down Expand Up @@ -59,10 +61,17 @@ func WithBeefCapabilities() ConfigOps {
}
}

// WithPikeCapabilities will load the PIKE capabilities
func WithPikeCapabilities() ConfigOps {
// WithPikeContactCapabilities will load the PIKE capabilities
func WithPikeContactCapabilities() ConfigOps {
return func(c *Configuration) {
c.PikeCapabilitiesEnabled = true
c.PikeContactCapabilitiesEnabled = true
}
}

// WithPikePaymentCapabilities will load the PIKE capabilities
func WithPikePaymentCapabilities() ConfigOps {
return func(c *Configuration) {
c.PikePaymentCapabilitiesEnabled = true
}
}

Expand Down
71 changes: 65 additions & 6 deletions server/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
func testConfig(t *testing.T, domain string) *Configuration {
sl := PaymailServiceLocator{}
sl.RegisterPaymailService(new(mockServiceProvider))
sl.RegisterPikeService(new(mockServiceProvider))
sl.RegisterPikeContactService(new(mockServiceProvider))
sl.RegisterPikePaymentService(new(mockServiceProvider))

c, err := NewConfig(
&sl,
Expand Down Expand Up @@ -476,23 +477,60 @@ func TestNewConfig(t *testing.T) {
assert.Equal(t, true, c.PaymailDomainsValidationDisabled)
})

t.Run("with pike capabilities", func(t *testing.T) {
t.Run("with pike contact capabilities", func(t *testing.T) {
sl := &PaymailServiceLocator{}
sl.RegisterPaymailService(new(mockServiceProvider))
sl.RegisterPikeService(new(mockServiceProvider))
sl.RegisterPikeContactService(new(mockServiceProvider))

c, err := NewConfig(
sl,
WithDomain("test.com"),
WithP2PCapabilities(),
WithPikeCapabilities(),
WithPikeContactCapabilities(),
)
require.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, 7, len(c.callableCapabilities))
assert.Equal(t, 1, len(c.nestedCapabilities))
})

t.Run("with pike capabilities - pike service is not registered -> should panic", func(t *testing.T) {
t.Run("with pike payment capabilities", func(t *testing.T) {
sl := &PaymailServiceLocator{}
sl.RegisterPaymailService(new(mockServiceProvider))
sl.RegisterPikePaymentService(new(mockServiceProvider))

c, err := NewConfig(
sl,
WithDomain("test.com"),
WithP2PCapabilities(),
WithPikePaymentCapabilities(),
)
require.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, 6, len(c.callableCapabilities))
assert.Equal(t, 1, len(c.nestedCapabilities))
})

t.Run("with both pike capabilities", func(t *testing.T) {
sl := &PaymailServiceLocator{}
sl.RegisterPaymailService(new(mockServiceProvider))
sl.RegisterPikeContactService(new(mockServiceProvider))
sl.RegisterPikePaymentService(new(mockServiceProvider))

c, err := NewConfig(
sl,
WithDomain("test.com"),
WithP2PCapabilities(),
WithPikeContactCapabilities(),
WithPikePaymentCapabilities(),
)
require.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, 7, len(c.callableCapabilities))
assert.Equal(t, 1, len(c.nestedCapabilities))
})

t.Run("with pike contact capabilities - pike contact service is not registered -> should panic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
Expand All @@ -506,10 +544,31 @@ func TestNewConfig(t *testing.T) {
sl,
WithDomain("test.com"),
WithP2PCapabilities(),
WithPikeCapabilities(),
WithPikeContactCapabilities(),
)
require.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, 7, len(c.callableCapabilities))
})

t.Run("with pike payment capabilities - pike payment service is not registered -> should panic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()

sl := &PaymailServiceLocator{}
sl.RegisterPaymailService(new(mockServiceProvider))

c, err := NewConfig(
sl,
WithDomain("test.com"),
WithP2PCapabilities(),
WithPikePaymentCapabilities(),
)
require.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, 6, len(c.callableCapabilities))
})
}
Loading
Loading