Skip to content

Commit

Permalink
Refactor contracts, transfers, agreements, and storage.
Browse files Browse the repository at this point in the history
This does the following:

- Move ContractNegotiations to their own package
- Do the same to TransferRequests
- Modify all the code to reflect the move
- Add locking to the badger storage layer
- Clean up code where it made sense
  • Loading branch information
ainmosni committed Dec 3, 2024
1 parent 40ab062 commit 785be5e
Show file tree
Hide file tree
Showing 39 changed files with 1,672 additions and 2,075 deletions.
3 changes: 2 additions & 1 deletion dsp/common_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"net/http"
"net/url"

"github.com/go-dataspace/run-dsp/dsp/persistence"
"github.com/go-dataspace/run-dsp/dsp/shared"
"github.com/go-dataspace/run-dsp/dsp/statemachine"
"github.com/go-dataspace/run-dsp/internal/constants"
Expand All @@ -29,7 +30,7 @@ import (
)

type dspHandlers struct {
store statemachine.Archiver
store persistence.StorageProvider
provider providerv1.ProviderServiceClient
reconciler *statemachine.Reconciler
selfURL *url.URL
Expand Down
23 changes: 23 additions & 0 deletions dsp/constants/roles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2024 go-dataspace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package constants

// DataspaceRole signifies what role in a dataspace exchange this is.
type DataspaceRole uint8

const (
DataspaceConsumer DataspaceRole = iota
DataspaceProvider
)
16 changes: 16 additions & 0 deletions dsp/contract/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 go-dataspace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package contract contains the Contract type and all related code.
package contract
241 changes: 241 additions & 0 deletions dsp/contract/negotiation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// Copyright 2024 go-dataspace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package contract

import (
"bytes"
"encoding/gob"
"fmt"
"net/url"
"slices"
"strconv"

"github.com/go-dataspace/run-dsp/dsp/constants"
"github.com/go-dataspace/run-dsp/dsp/shared"
"github.com/go-dataspace/run-dsp/odrl"
"github.com/google/uuid"
)

var validTransitions = map[State][]State{
States.INITIAL: {
States.OFFERED,
States.REQUESTED,
States.TERMINATED,
},
States.REQUESTED: {
States.OFFERED,
States.AGREED,
States.TERMINATED,
},
States.OFFERED: {
States.REQUESTED,
States.ACCEPTED,
States.TERMINATED,
},
States.ACCEPTED: {
States.AGREED,
States.TERMINATED,
},
States.AGREED: {
States.VERIFIED,
States.TERMINATED,
},
States.VERIFIED: {
States.FINALIZED,
States.TERMINATED,
},
States.FINALIZED: {},
States.TERMINATED: {},
}

// Negotiation represents a contract negotiation.
type Negotiation struct {
providerPID uuid.UUID
consumerPID uuid.UUID
state State
offer odrl.Offer
agreement *odrl.Agreement
callback *url.URL
self *url.URL
role constants.DataspaceRole

initial bool
ro bool
modified bool
}

type storableNegotiation struct {
ProviderPID uuid.UUID
ConsumerPID uuid.UUID
State State
Offer odrl.Offer
Agreement *odrl.Agreement
Callback *url.URL
Self *url.URL
Role constants.DataspaceRole
}

func New(
providerPID, consumerPID uuid.UUID,
state State,
offer odrl.Offer,
callback, self *url.URL,
role constants.DataspaceRole,
) *Negotiation {
return &Negotiation{
providerPID: providerPID,
consumerPID: consumerPID,
state: state,
offer: offer,
callback: callback,
self: self,
role: role,
modified: true,
}
}

func FromBytes(b []byte) (*Negotiation, error) {
var sn storableNegotiation
r := bytes.NewReader(b)
dec := gob.NewDecoder(r)
if err := dec.Decode(&sn); err != nil {
return nil, fmt.Errorf("Could not decode bytes into storableNegotiation: %w", err)
}
return &Negotiation{
providerPID: sn.ProviderPID,
consumerPID: sn.ConsumerPID,
state: sn.State,
offer: sn.Offer,
agreement: sn.Agreement,
callback: sn.Callback,
self: sn.Self,
role: sn.Role,
}, nil
}

// GenerateKey generates a key for a contract negotiation.
func GenerateKey(id uuid.UUID, role constants.DataspaceRole) []byte {
return []byte("negotiation-" + id.String() + "-" + strconv.Itoa(int(role)))
}

// Negotiation getters.
func (cn *Negotiation) GetProviderPID() uuid.UUID { return cn.providerPID }
func (cn *Negotiation) GetConsumerPID() uuid.UUID { return cn.consumerPID }
func (cn *Negotiation) GetState() State { return cn.state }
func (cn *Negotiation) GetOffer() odrl.Offer { return cn.offer }
func (cn *Negotiation) GetAgreement() *odrl.Agreement { return cn.agreement }
func (cn *Negotiation) GetRole() constants.DataspaceRole { return cn.role }
func (cn *Negotiation) GetCallback() *url.URL { return cn.callback }
func (cn *Negotiation) GetSelf() *url.URL { return cn.self }
func (cn *Negotiation) GetContract() *Negotiation { return cn }

// Negotiation setters, these will panic when the negotiation is RO.
func (cn *Negotiation) SetProviderPID(u uuid.UUID) {
cn.panicRO()
cn.providerPID = u
cn.modify()
}

func (cn *Negotiation) SetConsumerPID(u uuid.UUID) {
cn.panicRO()
cn.providerPID = u
cn.modify()
}

func (cn *Negotiation) SetAgreement(a *odrl.Agreement) {
cn.panicRO()
cn.agreement = a
cn.modify()
}

func (cn *Negotiation) SetState(state State) error {
cn.panicRO()
if !slices.Contains(validTransitions[cn.state], state) {
return fmt.Errorf("can't transition from %s to %s", cn.state, state)
}
cn.state = state
cn.modify()
return nil
}

// SetCallback sets the remote callback root.
func (cn *Negotiation) SetCallback(u string) error {
nu, err := url.Parse(u)
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
cn.callback = nu
cn.modify()
return nil
}

// Properties that decisions are based on.
func (cn *Negotiation) ReadOnly() bool { return cn.ro }
func (cn *Negotiation) Initial() bool { return cn.initial }
func (cn *Negotiation) Modified() bool { return cn.modified }
func (cn *Negotiation) StorageKey() []byte {
id := cn.consumerPID
if cn.role == constants.DataspaceProvider {
id = cn.providerPID
}
return GenerateKey(id, cn.role)
}

// Property setters.
func (cn *Negotiation) SetReadOnly() { cn.ro = true }
func (cn *Negotiation) SetInitial() { cn.initial = true }
func (cn *Negotiation) UnsetInitial() { cn.initial = false }

// ToBytes returns a binary representation of the negotiation, one that is compatible with the FromBytes
// function.
func (cn *Negotiation) ToBytes() ([]byte, error) {
s := storableNegotiation{
ProviderPID: cn.providerPID,
ConsumerPID: cn.consumerPID,
State: cn.state,
Offer: cn.offer,
Agreement: cn.agreement,
Callback: cn.callback,
Self: cn.self,
Role: cn.role,
}
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s); err != nil {
return nil, fmt.Errorf("could not encode negotiation: %w", err)
}
return buf.Bytes(), nil
}

// GetContractNegotiation returns a ContractNegotion message.
func (cn *Negotiation) GetContractNegotiation() shared.ContractNegotiation {
return shared.ContractNegotiation{
Context: shared.GetDSPContext(),
Type: "dspace:ContractNegotiation",
ConsumerPID: cn.GetConsumerPID().URN(),
ProviderPID: cn.GetProviderPID().URN(),
State: cn.GetState().String(),
}
}

func (cn *Negotiation) panicRO() {
if cn.ro {
panic("Trying to write to a read-only negotiation, this is certainly a bug.")
}
}

func (cn *Negotiation) modify() {
cn.modified = true
}
20 changes: 10 additions & 10 deletions dsp/statemachine/contract_state.go → dsp/contract/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package statemachine
package contract

type contractState int
type state int

//go:generate goenums contract_state.go
const (
initial contractState = iota // INITIAL
requested // dspace:REQUESTED
offered // dspace:OFFERED
agreed // dspace:AGREED
accepted // dspace:ACCEPTED
verified // dspace:VERIFIED
finalized // dspace:FINALIZED
terminated // dspace:TERMINATED
initial state = iota // INITIAL
requested // dspace:REQUESTED
offered // dspace:OFFERED
agreed // dspace:AGREED
accepted // dspace:ACCEPTED
verified // dspace:VERIFIED
finalized // dspace:FINALIZED
terminated // dspace:TERMINATED
)
Loading

0 comments on commit 785be5e

Please sign in to comment.