Repository: threefoldtech/zos
Pull request #1337 (2021-07-13 18:12:12)

Zos3/contract
- validation of contract
- assigning of Public IPs


Patch:
diff --git a/cmds/modules/capacityd/twin.go b/cmds/modules/capacityd/twin.go
index 91fe0617..d4975821 100644
--- a/cmds/modules/capacityd/twin.go
+++ b/cmds/modules/capacityd/twin.go
@@ -14,7 +14,11 @@ func runMsgBus(ctx context.Context, twin uint32) error {
 	// todo: make it argument or parse from broker
 	const redis = "/var/run/redis.sock"
 	for {
-		cmd := exec.CommandContext(ctx, "msgbusd", fmt.Sprint(twin), redis)
+		cmd := exec.CommandContext(ctx,
+			"msgbusd",
+			"--twin", fmt.Sprint(twin),
+			"--redis", redis)
+
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
 
diff --git a/cmds/modules/provisiond/main.go b/cmds/modules/provisiond/main.go
index 1294ac63..c2d98e1b 100644
--- a/cmds/modules/provisiond/main.go
+++ b/cmds/modules/provisiond/main.go
@@ -2,6 +2,7 @@ package provisiond
 
 import (
 	"context"
+	"crypto/ed25519"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -21,7 +22,6 @@ import (
 	"github.com/threefoldtech/zos/pkg/provision/mbus"
 	"github.com/threefoldtech/zos/pkg/provision/storage"
 	"github.com/threefoldtech/zos/pkg/rmb"
-	"github.com/threefoldtech/zos/pkg/substrate"
 	"github.com/urfave/cli/v2"
 
 	"github.com/threefoldtech/zos/pkg/stubs"
@@ -105,7 +105,7 @@ func action(cli *cli.Context) error {
 	}
 
 	identity := stubs.NewIdentityManagerStub(cl)
-	nodeID := identity.NodeID(cli.Context)
+	sk := ed25519.PrivateKey(identity.PrivateKey(ctx))
 
 	// block until networkd is ready to serve request from zbus
 	// this is used to prevent uptime and online status to the explorer if the node is not in a fully ready
@@ -183,7 +183,6 @@ func action(cli *cli.Context) error {
 		cap,
 		current,
 		reserved,
-		nodeID.Identity(),
 		provisioners,
 	)
 
@@ -195,12 +194,16 @@ func action(cli *cli.Context) error {
 	// users := mw.NewUserMap()
 	// users.AddKeyFromHex(1, "95d1ba20e9f5cb6cfc6182fecfa904664fb1953eba520db454d5d5afaa82d791")
 
-	users, err := substrate.NewSubstrateTwins(env.SubstrateURL)
+	sub, err := env.GetSubstrate()
+	if err != nil {
+		return errors.Wrap(err, "failed to get connection to substrate")
+	}
+	users, err := provision.NewSubstrateTwins(sub)
 	if err != nil {
 		return errors.Wrap(err, "failed to create substrate users database")
 	}
 
-	admins, err := substrate.NewSubstrateAdmins(env.SubstrateURL, uint32(env.FarmerID))
+	admins, err := provision.NewSubstrateAdmins(sub, uint32(env.FarmerID))
 	if err != nil {
 		return errors.Wrap(err, "failed to create substrate admins database")
 	}
@@ -216,6 +219,7 @@ func action(cli *cli.Context) error {
 		queues,
 		provision.WithTwins(users),
 		provision.WithAdmins(admins),
+		provision.WithSubstrate(sk, sub),
 		// set priority to some reservation types on boot
 		// so we always need to make sure all volumes and networks
 		// comes first.
diff --git a/pkg/gridtypes/deployment.go b/pkg/gridtypes/deployment.go
index 2f07cb01..bf26e7a5 100644
--- a/pkg/gridtypes/deployment.go
+++ b/pkg/gridtypes/deployment.go
@@ -39,7 +39,7 @@ type KeyGetter interface {
 type Deployment struct {
 	Version              int                  `json:"version"`
 	TwinID               uint32               `json:"twin_id"`
-	DeploymentID         uint32               `json:"deployment_id"`
+	ContractID           uint32               `json:"contract_id"`
 	Metadata             string               `json:"metadata"`
 	Description          string               `json:"description"`
 	Expiration           Timestamp            `json:"expiration"`
@@ -47,6 +47,16 @@ type Deployment struct {
 	Workloads            []Workload           `json:"workloads"`
 }
 
+// SetError sets an error on ALL workloads. this is mostly
+// an error caused by validation AFTTER the deployment was initially accepted
+func (d *Deployment) SetError(err error) {
+	for i := range d.Workloads {
+		wl := &d.Workloads[i]
+		wl.Result.State = StateError
+		wl.Result.Error = err.Error()
+	}
+}
+
 // WorkloadWithID wrapper around workload type
 // that holds the global workload ID
 type WorkloadWithID struct {
@@ -127,9 +137,13 @@ func (d *Deployment) Challenge(w io.Writer) error {
 		return err
 	}
 
-	if _, err := fmt.Fprintf(w, "%d", d.DeploymentID); err != nil {
-		return err
-	}
+	// ContractID is intentionally removed from the hash calculations
+	// because the contract is created on the blockchain first (requires this hash)
+	// then set on the deployment before send to the node
+	//
+	// if _, err := fmt.Fprintf(w, "%d", d.ContractID); err != nil {
+	// 	return err
+	// }
 
 	if _, err := fmt.Fprintf(w, "%s", d.Metadata); err != nil {
 		return err
@@ -271,7 +285,7 @@ func (d *Deployment) Get(name Name) (*WorkloadWithID, error) {
 	for i := range d.Workloads {
 		wl := &d.Workloads[i]
 		if wl.Name == name {
-			id, _ := NewWorkloadID(d.TwinID, d.DeploymentID, name)
+			id, _ := NewWorkloadID(d.TwinID, d.ContractID, name)
 			return &WorkloadWithID{
 				Workload: wl,
 				ID:       id,
@@ -301,7 +315,7 @@ func (d *Deployment) ByType(typ WorkloadType) []*WorkloadWithID {
 	for i := range d.Workloads {
 		wl := &d.Workloads[i]
 		if wl.Type == typ {
-			id, err := NewWorkloadID(d.TwinID, d.DeploymentID, wl.Name)
+			id, err := NewWorkloadID(d.TwinID, d.ContractID, wl.Name)
 			if err != nil {
 				log.Warn().Err(err).Msg("deployment has invalid name. please run validation")
 				continue
@@ -321,13 +335,14 @@ func (d *Deployment) ByType(typ WorkloadType) []*WorkloadWithID {
 }
 
 // Upgrade validates n as an updated version of d, and return an Upgrade description
-// for the steps that the node needs to take.
+// for the steps that the node needs to take to move from d to n. unchanged workloads results
+// will be set on n as is
 func (d *Deployment) Upgrade(n *Deployment) (*Upgrade, error) {
 	if err := n.Valid(); err != nil {
 		return nil, errors.Wrap(err, "new deployment is invalid")
 	}
 
-	if d.TwinID != n.TwinID || d.DeploymentID != n.DeploymentID {
+	if d.TwinID != n.TwinID || d.ContractID != n.ContractID {
 		return nil, fmt.Errorf("cannot change deployment or twin id")
 	}
 
@@ -349,7 +364,7 @@ func (d *Deployment) Upgrade(n *Deployment) (*Upgrade, error) {
 
 	for i := range n.Workloads {
 		l := &n.Workloads[i]
-		id, err := NewWorkloadID(n.TwinID, n.DeploymentID, l.Name)
+		id, err := NewWorkloadID(n.TwinID, n.ContractID, l.Name)
 		if err != nil {
 			return nil, errors.Wrap(err, "failed to get build workload ID")
 		}
@@ -387,7 +402,7 @@ func (d *Deployment) Upgrade(n *Deployment) (*Upgrade, error) {
 	}
 
 	for _, wl := range current {
-		id, _ := NewWorkloadID(d.TwinID, d.DeploymentID, wl.Name)
+		id, _ := NewWorkloadID(d.TwinID, d.ContractID, wl.Name)
 
 		remove = append(remove, &WorkloadWithID{
 			Workload: wl,
diff --git a/pkg/gridtypes/workload.go b/pkg/gridtypes/workload.go
index 92fab600..1d7e9800 100644
--- a/pkg/gridtypes/workload.go
+++ b/pkg/gridtypes/workload.go
@@ -262,6 +262,11 @@ type Result struct {
 	Data json.RawMessage `json:"data"`
 }
 
+// Unmarshal a shortcut for json.Unmarshal
+func (r *Result) Unmarshal(v interface{}) error {
+	return json.Unmarshal(r.Data, v)
+}
+
 // IsNil checks if Result is the zero values
 func (r *Result) IsNil() bool {
 	// ideally this should be implemented like this
diff --git a/pkg/gridtypes/zos/ipv4.go b/pkg/gridtypes/zos/ipv4.go
index 6c2e1d25..d7b82905 100644
--- a/pkg/gridtypes/zos/ipv4.go
+++ b/pkg/gridtypes/zos/ipv4.go
@@ -1,7 +1,6 @@
 package zos
 
 import (
-	"fmt"
 	"io"
 	"net"
 
@@ -9,35 +8,16 @@ import (
 )
 
 // PublicIP structure
-type PublicIP struct {
-	// IP of the VM. The IP must be part of the subnet available in the network
-	// resource defined by the networkID on this node
-	IP gridtypes.IPNet `json:"ip"`
-
-	// Gateway: this fields is only here because we have no idea what is the
-	// gateway of that ip without consulting the farmer. Currently this
-	// component does not exist. hence as a temporaray solution the user must
-	// also provide
-	Gateway net.IP `json:"gateway"`
-}
+type PublicIP struct{}
 
 // Valid validate public ip input
 func (p PublicIP) Valid(getter gridtypes.WorkloadGetter) error {
-	if len(p.IP.IP) == 0 {
-		return fmt.Errorf("empty ip value")
-	}
-
-	if p.IP.IP.To4() == nil {
-		return fmt.Errorf("invalid ip format")
-	}
-
 	return nil
 }
 
 // Challenge implementation
 func (p PublicIP) Challenge(b io.Writer) error {
-	_, err := fmt.Fprintf(b, "%v", p.IP.String())
-	return err
+	return nil
 }
 
 // Capacity implementation
@@ -47,5 +27,13 @@ func (p PublicIP) Capacity() (gridtypes.Capacity, error) {
 
 // PublicIPResult result returned by publicIP reservation
 type PublicIPResult struct {
+	// IP of the VM. The IP must be part of the subnet available in the network
+	// resource defined by the networkID on this node
 	IP gridtypes.IPNet `json:"ip"`
+
+	// Gateway: this fields is only here because we have no idea what is the
+	// gateway of that ip without consulting the farmer. Currently this
+	// component does not exist. hence as a temporaray solution the user must
+	// also provide
+	Gateway net.IP `json:"gateway"`
 }
diff --git a/pkg/primitives/public_ip.go b/pkg/primitives/public_ip.go
index 37c200c3..85532ee0 100644
--- a/pkg/primitives/public_ip.go
+++ b/pkg/primitives/public_ip.go
@@ -21,6 +21,75 @@ func (p *Primitives) publicIPProvision(ctx context.Context, wl *gridtypes.Worklo
 	return p.publicIPProvisionImpl(ctx, wl)
 }
 
+func (p *Primitives) getAssignedPublicIP(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
+	//Okay, this implementation is tricky but will be simplified once we store the reserved IP
+	//on the contract.
+
+	// Okay, so first (and easiest path) is that the Ip was already
+	// assigned, hence we can simply use it again. this is usually
+	// the case if the node is rerunning the same workload deployment for
+	// some reason.
+	if wl.Result.IsNil() || wl.Result.State == gridtypes.StateOk {
+		if err := wl.Result.Unmarshal(&result); err != nil {
+			return result, errors.Wrap(err, "failed to load public ip result")
+		}
+
+		return result, nil
+	}
+	// otherwise we do the following:
+
+	// - We need to get the contract and the farm object this node belongs to
+	deployment := provision.GetDeployment(ctx)
+	contract := provision.GetContract(ctx)
+
+	// - now we find out ALL ips belonging to this contract
+	reserved := contract.PublicIPs
+
+	// make sure we have enough reserved IPs
+	ipWorkloads := deployment.ByType(zos.PublicIPType)
+	if len(ipWorkloads) > len(reserved) {
+		return result, fmt.Errorf("required %d ips while contract has %d ip reserved", len(ipWorkloads), len(reserved))
+	}
+
+	usedIPs := make(map[string]struct{})
+
+	for _, ipWl := range ipWorkloads {
+		if wl.Name == ipWl.Name {
+			// we don't need this.
+			continue
+		}
+
+		if ipWl.Result.IsNil() || ipWl.Result.State != gridtypes.StateOk {
+			continue
+		}
+		var used zos.PublicIPResult
+		if err := ipWl.Result.Unmarshal(&used); err != nil {
+			return result, errors.Wrap(err, "failed to ")
+		}
+
+		usedIPs[used.IP.String()] = struct{}{}
+	}
+
+	// otherwise we go over the list of IPs and take the first free one
+	for _, ip := range reserved {
+		if _, ok := usedIPs[ip.IP]; !ok {
+			// free ip. we can just take it
+			ipNet, err := gridtypes.ParseIPNet(ip.IP)
+			if err != nil {
+				return result, fmt.Errorf("found a mullformed ip address in farm object '%s'", ip.IP)
+			}
+			gw := net.ParseIP(ip.Gateway)
+			if gw == nil {
+				return result, fmt.Errorf("found a mullformed gateway address in farm object '%s'", ip.Gateway)
+			}
+
+			return zos.PublicIPResult{IP: ipNet, Gateway: gw}, nil
+		}
+	}
+
+	return result, fmt.Errorf("could not allocate public IP address to workload")
+}
+
 func (p *Primitives) publicIPProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
 	config := zos.PublicIP{}
 
@@ -48,8 +117,12 @@ func (p *Primitives) publicIPProvisionImpl(ctx context.Context, wl *gridtypes.Wo
 		return zos.PublicIPResult{}, errors.Wrap(err, "could not look up ipv6 prefix")
 	}
 
-	result.IP = config.IP
-	err = network.SetupPubIPFilter(ctx, fName, tapName, config.IP.IP.To4().String(), predictedIPv6, mac.String())
+	result, err = p.getAssignedPublicIP(ctx, wl)
+	if err != nil {
+		return zos.PublicIPResult{}, err
+	}
+
+	err = network.SetupPubIPFilter(ctx, fName, tapName, result.IP.IP.String(), predictedIPv6, mac.String())
 	return
 }
 
diff --git a/pkg/primitives/statistics.go b/pkg/primitives/statistics.go
index ea90df70..81b69d04 100644
--- a/pkg/primitives/statistics.go
+++ b/pkg/primitives/statistics.go
@@ -40,8 +40,6 @@ type Statistics struct {
 	counters Counters
 	reserved Counters
 	mem      gridtypes.Unit
-
-	nodeID string
 }
 
 // round the given value to the lowest gigabyte
@@ -51,7 +49,7 @@ func roundTotalMemory(t gridtypes.Unit) gridtypes.Unit {
 
 // NewStatistics creates a new statistics provisioner interceptor.
 // Statistics provisioner keeps track of used capacity and update explorer when it changes
-func NewStatistics(total, initial gridtypes.Capacity, reserved Counters, nodeID string, inner provision.Provisioner) *Statistics {
+func NewStatistics(total, initial gridtypes.Capacity, reserved Counters, inner provision.Provisioner) *Statistics {
 	vm, err := mem.VirtualMemory()
 	if err != nil {
 		panic(err)
@@ -69,7 +67,6 @@ func NewStatistics(total, initial gridtypes.Capacity, reserved Counters, nodeID
 		counters: counters,
 		reserved: reserved,
 		mem:      gridtypes.Unit(vm.Total),
-		nodeID:   nodeID,
 	}
 }
 
diff --git a/pkg/primitives/vm_utils.go b/pkg/primitives/vm_utils.go
index b7761009..3b0757f9 100644
--- a/pkg/primitives/vm_utils.go
+++ b/pkg/primitives/vm_utils.go
@@ -150,13 +150,6 @@ func (p *Primitives) newPubNetworkInterface(ctx context.Context, deployment grid
 
 // Get the public ip, and the gateway from the reservation ID
 func (p *Primitives) getPubIPConfig(wl *gridtypes.WorkloadWithID) (ip net.IPNet, gw net.IP, err error) {
-
-	//CRITICAL: TODO
-	// in this function we need to return the IP from the IP workload
-	// but we also need to get the Gateway IP from the farmer some how
-	// we used to get this from the explorer, but now we need another
-	// way to do this. for now the only option is to get it from the
-	// reservation itself. hence we added the gatway fields to ip data
 	if wl.Type != zos.PublicIPType {
 		return ip, gw, fmt.Errorf("workload for public IP is of wrong type")
 	}
@@ -164,16 +157,13 @@ func (p *Primitives) getPubIPConfig(wl *gridtypes.WorkloadWithID) (ip net.IPNet,
 	if wl.Result.State != gridtypes.StateOk {
 		return ip, gw, fmt.Errorf("public ip workload is not okay")
 	}
-	ipData, err := wl.WorkloadData()
-	if err != nil {
-		return
-	}
-	data, ok := ipData.(*zos.PublicIP)
-	if !ok {
-		return ip, gw, fmt.Errorf("invalid ip data in deployment got '%T'", ipData)
+
+	var result zos.PublicIPResult
+	if err := wl.Result.Unmarshal(&result); err != nil {
+		return ip, gw, errors.Wrap(err, "failed to load ip config")
 	}
 
-	return data.IP.IPNet, data.Gateway, nil
+	return result.IP.IPNet, result.Gateway, nil
 }
 
 func getFlistInfo(imagePath string) (FListInfo, error) {
diff --git a/pkg/provision/api/network.go b/pkg/provision/api/network.go
index 466f950a..a24f4566 100644
--- a/pkg/provision/api/network.go
+++ b/pkg/provision/api/network.go
@@ -89,14 +89,13 @@ func (n *Network) listPublicIps(request *http.Request) (interface{}, mw.Response
 					continue
 				}
 
-				data, err := workload.WorkloadData()
-				if err != nil {
+				var result zos.PublicIPResult
+				if err := workload.Result.Unmarshal(&result); err != nil {
 					return nil, mw.Error(err)
 				}
 
-				ip, _ := data.(*zos.PublicIP)
-				if ip.IP.IP != nil {
-					ips = append(ips, ip.IP.String())
+				if result.IP.IP != nil {
+					ips = append(ips, result.IP.String())
 				}
 			}
 		}
diff --git a/pkg/substrate/auth.go b/pkg/provision/auth.go
similarity index 80%
rename from pkg/substrate/auth.go
rename to pkg/provision/auth.go
index 2dcae1f8..7b4c923b 100644
--- a/pkg/substrate/auth.go
+++ b/pkg/provision/auth.go
@@ -1,4 +1,4 @@
-package substrate
+package provision
 
 import (
 	"crypto/ed25519"
@@ -6,21 +6,16 @@ import (
 
 	lru "github.com/hashicorp/golang-lru"
 	"github.com/pkg/errors"
-	"github.com/threefoldtech/zos/pkg/provision"
+	"github.com/threefoldtech/zos/pkg/substrate"
 )
 
 type substrateTwins struct {
-	sub *Substrate
+	sub *substrate.Substrate
 	mem *lru.Cache
 }
 
 // NewSubstrateTwins creates a substrate users db that implements the provision.Users interface.
-func NewSubstrateTwins(url string) (provision.Twins, error) {
-	sub, err := NewSubstrate(url)
-	if err != nil {
-		return nil, err
-	}
-
+func NewSubstrateTwins(sub *substrate.Substrate) (Twins, error) {
 	cache, err := lru.New(1024)
 	if err != nil {
 		return nil, err
@@ -49,19 +44,14 @@ func (s *substrateTwins) GetKey(id uint32) (ed25519.PublicKey, error) {
 }
 
 type substrateAdmins struct {
-	sub  *Substrate
+	sub  *substrate.Substrate
 	twin uint32
 	pk   ed25519.PublicKey
 }
 
 // NewSubstrateAdmins creates a substrate twins db that implements the provision.Users interface.
 // but it also make sure the user is an admin
-func NewSubstrateAdmins(url string, farmID uint32) (provision.Twins, error) {
-	sub, err := NewSubstrate(url)
-	if err != nil {
-		return nil, err
-	}
-
+func NewSubstrateAdmins(sub *substrate.Substrate, farmID uint32) (Twins, error) {
 	farm, err := sub.GetFarm(farmID)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to get farm")
diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go
index 0642ba6f..9b41f43d 100644
--- a/pkg/provision/engine.go
+++ b/pkg/provision/engine.go
@@ -3,6 +3,7 @@ package provision
 import (
 	"context"
 	"crypto/ed25519"
+	"encoding/hex"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -12,6 +13,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
+	"github.com/threefoldtech/zos/pkg/substrate"
 
 	"github.com/rs/zerolog/log"
 )
@@ -47,6 +49,21 @@ func WithStartupOrder(t ...gridtypes.WorkloadType) EngineOption {
 	return &withStartupOrder{t}
 }
 
+// WithSubstrate sets the substrate client. If set it will
+// be used by the engine to fetch (and validate) the deployment contract
+// then contract with be available on the deployment context
+func WithSubstrate(sk ed25519.PrivateKey, sub *substrate.Substrate) EngineOption {
+	if len(sk) != ed25519.PrivateKeySize {
+		panic("invalid node private key")
+	}
+	kp, err := substrate.Identity(sk)
+	if err != nil {
+		panic(fmt.Sprintf("failed to get node identity: %s", err))
+	}
+
+	return &withSubstrate{kp.Address, sub}
+}
+
 // WithRerunAll if set forces the engine to re-run all reservations
 // on engine start.
 func WithRerunAll(t bool) EngineOption {
@@ -87,6 +104,9 @@ type NativeEngine struct {
 	admins   Twins
 	order    []gridtypes.WorkloadType
 	rerunAll bool
+	//substrate specific attributes
+	address string
+	sub     *substrate.Substrate
 }
 
 var _ Engine = (*NativeEngine)(nil)
@@ -117,6 +137,16 @@ func (o *withAdminsKeyGetter) apply(e *NativeEngine) {
 	e.admins = o.g
 }
 
+type withSubstrate struct {
+	address string
+	sub     *substrate.Substrate
+}
+
+func (o *withSubstrate) apply(e *NativeEngine) {
+	e.address = o.address
+	e.sub = o.sub
+}
+
 type withStartupOrder struct {
 	o []gridtypes.WorkloadType
 }
@@ -158,6 +188,8 @@ func (n *nullKeyGetter) GetKey(id uint32) (ed25519.PublicKey, error) {
 
 type engineKey struct{}
 type deploymentKey struct{}
+type contractKey struct{}
+type substrateKey struct{}
 
 // GetEngine gets engine from context
 func GetEngine(ctx context.Context) Engine {
@@ -166,7 +198,22 @@ func GetEngine(ctx context.Context) Engine {
 
 // GetDeployment gets a copy of the current deployment
 func GetDeployment(ctx context.Context) gridtypes.Deployment {
-	return ctx.Value(deploymentKey{}).(gridtypes.Deployment)
+	// we store the pointer on the context so changed to deployment object
+	// actually reflect into the value.
+	dl := ctx.Value(deploymentKey{}).(*gridtypes.Deployment)
+	// BUT we always return a copy so caller of GetDeployment can NOT manipulate
+	// other attributed on the object.
+	return *dl
+}
+
+// GetContract of deployment. panics if engine has no substrate set.
+func GetContract(ctx context.Context) *substrate.Contract {
+	return ctx.Value(contractKey{}).(*substrate.Contract)
+}
+
+// GetSubstrate if engine has substrate set, panics otherwise
+func GetSubstrate(ctx context.Context) *substrate.Substrate {
+	return ctx.Value(substrateKey{}).(*substrate.Substrate)
 }
 
 // New creates a new engine. Once started, the engine
@@ -241,7 +288,7 @@ func (e *NativeEngine) Deprovision(ctx context.Context, twin, id uint32, reason
 
 	log.Debug().
 		Uint32("twin", deployment.TwinID).
-		Uint32("deployment", deployment.DeploymentID).
+		Uint32("contract", deployment.ContractID).
 		Msg("schedule for deprovision")
 
 	job := engineJob{
@@ -254,7 +301,7 @@ func (e *NativeEngine) Deprovision(ctx context.Context, twin, id uint32, reason
 
 // Update workloads
 func (e *NativeEngine) Update(ctx context.Context, update gridtypes.Deployment) error {
-	deployment, err := e.storage.Get(update.TwinID, update.DeploymentID)
+	deployment, err := e.storage.Get(update.TwinID, update.ContractID)
 	if err != nil {
 		return err
 	}
@@ -297,8 +344,21 @@ func (e *NativeEngine) Run(root context.Context) error {
 			<-time.After(2 * time.Second)
 			continue
 		}
+
 		job := obj.(*engineJob)
-		ctx := context.WithValue(root, deploymentKey{}, job.Target)
+		ctx := context.WithValue(root, deploymentKey{}, &job.Target)
+
+		// contract processing
+		ctx, err = e.contract(ctx, &job.Target)
+		if err != nil {
+			job.Target.SetError(err)
+			if err := e.storage.Set(job.Target); err != nil {
+				log.Error().Err(err).Msg("failed to set deployment global error")
+			}
+
+			continue
+		}
+
 		switch job.Op {
 		case opProvision:
 			e.installDeployment(ctx, &job.Target)
@@ -326,7 +386,7 @@ func (e *NativeEngine) Run(root context.Context) error {
 			// and update to reflect the current result on those workloads.
 			update, err := job.Source.Upgrade(&job.Target)
 			if err != nil {
-				log.Error().Err(err).Uint32("twin", job.Target.TwinID).Uint32("id", job.Target.DeploymentID).Msg("failed to get update procedure")
+				log.Error().Err(err).Uint32("twin", job.Target.TwinID).Uint32("id", job.Target.ContractID).Msg("failed to get update procedure")
 				break
 			}
 
@@ -343,6 +403,37 @@ func (e *NativeEngine) Run(root context.Context) error {
 	}
 }
 
+// contract validates and injects the deployment contracts is substrate is configured
+// for this instance of the provision engine.
+func (e *NativeEngine) contract(ctx context.Context, dl *gridtypes.Deployment) (context.Context, error) {
+	if e.sub == nil {
+		return ctx, nil
+	}
+
+	ctx = context.WithValue(ctx, substrateKey{}, e.sub)
+	// if substrate is set. we need to get contract
+	contract, err := e.sub.GetContract(uint64(dl.ContractID))
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get deployment contract")
+	}
+
+	ctx = context.WithValue(ctx, contractKey{}, contract)
+	if contract.Node.String() != e.address {
+		return nil, fmt.Errorf("invalid node address in contract")
+	}
+
+	hash, err := dl.ChallengeHash()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to compute deployment hash")
+	}
+
+	if contract.DeploymentHash != hex.EncodeToString(hash) {
+		return nil, errors.Wrap(err, "deployment hash messmatch")
+	}
+
+	return ctx, nil
+}
+
 // boot will make sure to re-deploy all stored reservation
 // on boot.
 func (e *NativeEngine) boot(root context.Context) error {
diff --git a/pkg/provision/mbus/network.go b/pkg/provision/mbus/network.go
index d4ff7a54..8d274a5b 100644
--- a/pkg/provision/mbus/network.go
+++ b/pkg/provision/mbus/network.go
@@ -119,14 +119,13 @@ func (n *Network) listPublicIps() (interface{}, mw.Response) {
 					continue
 				}
 
-				data, err := workload.WorkloadData()
-				if err != nil {
+				var result zos.PublicIPResult
+				if err := workload.Result.Unmarshal(&result); err != nil {
 					return nil, mw.Error(err)
 				}
 
-				ip, _ := data.(*zos.PublicIP)
-				if ip.IP.IP != nil {
-					ips = append(ips, ip.IP.String())
+				if result.IP.IP != nil {
+					ips = append(ips, result.IP.String())
 				}
 			}
 		}
diff --git a/pkg/provision/mbus/reservation.go b/pkg/provision/mbus/reservation.go
index d9ff671d..3228b9ba 100644
--- a/pkg/provision/mbus/reservation.go
+++ b/pkg/provision/mbus/reservation.go
@@ -3,10 +3,10 @@ package mbus
 import (
 	"context"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"time"
 
+	"github.com/pkg/errors"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/threefoldtech/zos/pkg/provision/mw"
@@ -27,11 +27,17 @@ func (d *Deployments) createOrUpdate(ctx context.Context, payload []byte, update
 		return nil, mw.BadRequest(err)
 	}
 
-	deployment.TwinID = rmb.GetTwinID(ctx)
+	if deployment.TwinID == rmb.GetTwinID(ctx) {
+		return nil, mw.UnAuthorized(fmt.Errorf("twin id mismatch"))
+	}
+
 	if err := deployment.Verify(d.engine.Twins()); err != nil {
 		return nil, mw.UnAuthorized(err)
 	}
 
+	// we need to ge the contract here and make sure
+	// we can validate the contract against it.
+
 	ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
 	defer cancel()
 
diff --git a/pkg/provision/storage/storage.go b/pkg/provision/storage/storage.go
index bb258f5a..eb336bac 100644
--- a/pkg/provision/storage/storage.go
+++ b/pkg/provision/storage/storage.go
@@ -45,7 +45,7 @@ func NewFSStore(root string) (*Fs, error) {
 }
 
 func (s *Fs) deploymentPath(d *gridtypes.Deployment) string {
-	return filepath.Join(fmt.Sprint(d.TwinID), fmt.Sprint(d.DeploymentID))
+	return filepath.Join(fmt.Sprint(d.TwinID), fmt.Sprint(d.ContractID))
 }
 
 func (s *Fs) rooted(p ...string) string {
@@ -69,7 +69,7 @@ func (s *Fs) Add(d gridtypes.Deployment) error {
 	)
 
 	if os.IsExist(err) {
-		return errors.Wrapf(provision.ErrDeploymentExists, "object '%d' exist", d.DeploymentID)
+		return errors.Wrapf(provision.ErrDeploymentExists, "object '%d' exist", d.ContractID)
 	} else if err != nil {
 		return errors.Wrap(err, "failed to open workload file")
 	}
@@ -100,7 +100,7 @@ func (s *Fs) Set(dl gridtypes.Deployment) error {
 		0644,
 	)
 	if os.IsNotExist(err) {
-		return errors.Wrapf(provision.ErrDeploymentNotExists, "deployment '%d:%d' does not exist", dl.TwinID, dl.DeploymentID)
+		return errors.Wrapf(provision.ErrDeploymentNotExists, "deployment '%d:%d' does not exist", dl.TwinID, dl.ContractID)
 	} else if err != nil {
 		return errors.Wrap(err, "failed to open workload file")
 	}
diff --git a/pkg/substrate/contract.go b/pkg/substrate/contract.go
new file mode 100644
index 00000000..0d468fb8
--- /dev/null
+++ b/pkg/substrate/contract.go
@@ -0,0 +1,93 @@
+package substrate
+
+import (
+	"fmt"
+
+	"github.com/centrifuge/go-substrate-rpc-client/v3/scale"
+	"github.com/centrifuge/go-substrate-rpc-client/v3/types"
+	"github.com/pkg/errors"
+)
+
+// ContractState enum
+type ContractState struct {
+	IsCreated  bool
+	IsDeployed bool
+}
+
+// Decode implementation for the enum type
+func (r *ContractState) Decode(decoder scale.Decoder) error {
+	b, err := decoder.ReadOneByte()
+	if err != nil {
+		return err
+	}
+
+	switch b {
+	case 0:
+		r.IsCreated = true
+	case 1:
+		r.IsDeployed = true
+	default:
+		return fmt.Errorf("unknown CertificateType value")
+	}
+
+	return nil
+}
+
+// Contract structure
+type Contract struct {
+	Versioned
+	ContractID         types.U64
+	TwinID             types.U32
+	Node               AccountID
+	Data               []byte
+	DeploymentHash     string
+	PublicIPsCount     types.U32
+	State              ContractState
+	LastUpdated        types.U64
+	PreviousNUReported types.U64
+	PublicIPs          []PublicIP
+}
+
+// GetContract we should not have calls to create contract, instead only get
+func (s *Substrate) GetContract(id uint64) (*Contract, error) {
+	bytes, err := types.EncodeToBytes(id)
+	if err != nil {
+		return nil, errors.Wrap(err, "substrate: encoding error building query arguments")
+	}
+
+	key, err := types.CreateStorageKey(s.meta, "SmartContractModule", "Contracts", bytes, nil)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create substrate query key")
+	}
+
+	return s.getContract(key)
+}
+
+func (s *Substrate) getContract(key types.StorageKey) (*Contract, error) {
+	raw, err := s.cl.RPC.State.GetStorageRawLatest(key)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to lookup contract")
+	}
+
+	if len(*raw) == 0 {
+		return nil, errors.Wrap(ErrNotFound, "contract not found")
+	}
+
+	version, err := s.getVersion(*raw)
+	if err != nil {
+		return nil, err
+	}
+
+	var node Contract
+
+	switch version {
+	case 1:
+		if err := types.DecodeFromBytes(*raw, &node); err != nil {
+			return nil, errors.Wrap(err, "failed to load object")
+		}
+	default:
+		return nil, ErrUnknownVersion
+	}
+
+	return &node, nil
+}
diff --git a/pkg/substrate/farm.go b/pkg/substrate/farm.go
index 4662bbc2..0eb7f65e 100644
--- a/pkg/substrate/farm.go
+++ b/pkg/substrate/farm.go
@@ -46,6 +46,14 @@ type Farm struct {
 	CertificationType CertificationType
 	CountryID         types.U32
 	CityID            types.U32
+	PublicIPs         []PublicIP
+}
+
+// PublicIP structure
+type PublicIP struct {
+	IP         string
+	Gateway    string
+	ContractID types.U64
 }
 
 // GetFarm gets a farm with ID


Test patch:
diff --git a/pkg/provision/storage/storage_test.go b/pkg/provision/storage/storage_test.go
index a99940aa..4fc573ee 100644
--- a/pkg/provision/storage/storage_test.go
+++ b/pkg/provision/storage/storage_test.go
@@ -45,10 +45,10 @@ func TestStorageAdd(t *testing.T) {
 	twin := uint32(1)
 	id := uint32(1)
 	err = store.Add(gridtypes.Deployment{
-		TwinID:       twin,
-		DeploymentID: id,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      twin,
+		ContractID:  id,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",
@@ -76,10 +76,10 @@ func TestStorageSet(t *testing.T) {
 	twin := uint32(1)
 	id := uint32(1)
 	deployment := gridtypes.Deployment{
-		TwinID:       twin,
-		DeploymentID: id,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      twin,
+		ContractID:  id,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",
@@ -112,10 +112,10 @@ func TestStorageGet(t *testing.T) {
 	twin := uint32(1)
 	id := uint32(1)
 	deployment := gridtypes.Deployment{
-		TwinID:       twin,
-		DeploymentID: id,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      twin,
+		ContractID:  id,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",
@@ -128,7 +128,7 @@ func TestStorageGet(t *testing.T) {
 	err = store.Add(deployment)
 	require.NoError(err)
 
-	loaded, err := store.Get(deployment.TwinID, deployment.DeploymentID)
+	loaded, err := store.Get(deployment.TwinID, deployment.ContractID)
 	require.NoError(err)
 	require.Equal(deployment.Description, loaded.Description)
 	require.Equal(deployment.Metadata, loaded.Metadata)
@@ -145,10 +145,10 @@ func TestStorageByTwin(t *testing.T) {
 	require.NoError(err)
 
 	deployment1 := gridtypes.Deployment{
-		TwinID:       1,
-		DeploymentID: 1,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      1,
+		ContractID:  1,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",
@@ -162,10 +162,10 @@ func TestStorageByTwin(t *testing.T) {
 	require.NoError(err)
 
 	deployment2 := gridtypes.Deployment{
-		TwinID:       1,
-		DeploymentID: 2,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      1,
+		ContractID:  2,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",
@@ -179,10 +179,10 @@ func TestStorageByTwin(t *testing.T) {
 	require.NoError(err)
 
 	deployment3 := gridtypes.Deployment{
-		TwinID:       2,
-		DeploymentID: 1,
-		Metadata:     "meta",
-		Description:  "descriptions",
+		TwinID:      2,
+		ContractID:  1,
+		Metadata:    "meta",
+		Description: "descriptions",
 		Workloads: []gridtypes.Workload{
 			{
 				Name: "volume",