All Downloads are FREE. Search and download functionalities are using the official Maven repository.

convex.core.registry.cvx Maven / Gradle / Ivy

The newest version!
'convex.registry

(set-holding *address*
             {:description ["Actor hosting a registry for resolving arbitrary symbols to addresses."
                            "Typically, actors and libraries are registered so that they can be retrieved and consumed using standard `import`."
                            "Each record in the registry has a controller that can update that record in any way."
                            "A controller is an address or, more speficially, a trust monitor as described in `convex.trust`."
                            "This actor also provides a standard way for adding metadata to an address."]
              :name        "Convex Name Service"})


;;
;;
;; Deployed by default during network initialisation at a well-known address.
;; Initialization takes care of registering this registry alongside other actors and libraries.
;;
;; This make it accessible from early in network bootstrap as a way to register and locate Accounts.
;;
;;


;;;;;;;;;; Values

(def trust
  ^{:private true}
  ;; Address of `convex.trust`, it is deployed right after this account, hence it is predictable.
  (address (inc (long *address*))))

;;;;;;;;;; Address metadata registry


(defn lookup
  ^{:callable true
    :doc       {:description "Looks up registry metadata for a given address."
                :examples    [{:code "(call *registry* (lookup somebody)"}]
                :signature   [{:params [addr]}]}}
  [addr]
  (get-holding (address addr)))


(defn register
  ^{:callable true
    :doc       {:description "Registers metadata for the *caller* account. Metadata can be an arbitrary value, but by convention is a map with defined fields."
                :examples    [{:code "(call *registry* (register  {:name \"My Name\"})"}]
                :signature   [{:params [metadata]}]}}
  [data]
  (set-holding *caller*
               data))

;;;;;;;;;;;;;; Convex Name System root

;; CNS root reference, i.e. this registry with an empty vector key
(def root [~*address* []])

;; Map of `node key` -> `segment symbol` -> `[value trust-monitor meta child]`
;; Where:
;;    value = Target value for CNS resolution, usually an address or scoped address
;;	  trust-monitor = controller for this CNS entry
;;    meta = metadata field		
;;    child = child CNS node, may be nil. Usually a scoped address defining an actor and a path key e.g. [#5675 "bob"] 
;;		
;; Node key is implementation defined in general, but for main registry uses:
;;    Empty vector for CNS root
;;    vector of segment strings for child paths
(def cns-database
		  ^{:private? true}
		  
  {[] {"init" [#1 #1 nil nil]}})

;; Controllers for each CNS node managed under this actor
;; Default root is controlled by INIT account
;; Checked for permissions:
;;   :control - change CNS node ownership
;;   :create  - create new CNS entry in this node (child path segment as object)
;;   :delete  - delete CNS entry in this node     (child path segment as object)
(def cns-owners 
  {[] #1})

(defn -check [sym]
  (or (symbol? sym) (fail :ARGUMENT "CNS name must be a Symbol")))

(defn read ^{
	:doc       {:description "Reads a CNS record as a [value controller metadata child] vector, or nil if record does not exist."
    :examples    [{:code "(*registry*/resolve 'my.actor.name)"}]
    :signature   [{:params [sym]}]}}
  ([sym]
	(-check sym)
	(let [path (split (name sym) \.)
	      n (count path)]
	  (when (zero? n) (fail :ARGUMENT "CNS path must have at least one segment"))
      (loop [i 0 
             ref root]
        (let [pname (nth path i)
              rec (call ref (cns-read pname))]
           (cond 
              ;; return nil if no entry found
        	  (nil? rec) (return nil)
              
              ;; Return record if at end of path
              (>= (inc i) n) (return rec)
        	  
              ;; recurse into next level CNS node
              (recur (inc i) (first rec))
        	  
              ))))))

(defn resolve ^{
	:doc       {:description "Resolves a CNS entry. Returns nil if entry does not exist"
    :examples    [{:code "(*registry*/resolve 'my.actor.name)"}]
    :signature   [{:params [sym]}]}}
  ([sym]
    (if-let [rec (read sym)]
      (first rec))))

(defn create ^{
	:doc  {:description "Creates a CNS entry with given reference, controller and metadata"
	       :examples    [{:code "(*registry*/create 'my.actor.name target *address* {:some :metadata})"}]
	       :signature   [{:params [sym]}]}}

  ([sym] (recur sym nil *address* nil))
  ([sym addr] (recur sym addr *address* nil))
  ([sym addr cont] (recur sym addr cont nil))
  ([sym addr cont meta] 
	(when-not (symbol? sym) (fail :ARGUMENT "CNS path must be a valid symbol"))
	(let [path (split (name sym) \.)
	      n (count path)]
	  (when (zero? n) (fail :ARGUMENT "CNS path must have at least one segment"))
	  (loop [i 0 
		     ref root]
	    (let [pname (get path i)]
		 (cond 
			;; are we at end of path? if so perform write at current position
		    (>= (inc i) n) 
			   (call ref (cns-write pname addr cont meta) )
			   
			(if-let [rec (call ref (cns-read pname))]
				(recur (inc i) (first rec))	
				
				;; need to construct a new CNS node
				(let [nref (call ref (cns-create-node pname cont))] 
				   (call ref (cns-write pname nref cont nil) )
				   (recur (inc i) nref)))
		 ))
	  )
	)
	sym
	))

(def update create)

(defn control
  [sym cont]
  (if-let [rec (read sym)]
    (let [[v c m] rec]
      (update sym v cont m))
    (fail :STATE "CNS record does not exist")))


(defn change-control
	^{:callable true
	  :doc {:description "Changes controller for a CNS node."
			                :examples    [{:code "(call *registry* (cns-control 'my.actor trust-monitor-address)"}]
			                :signature   [{:params [name addr]}]}}
  [cont]
  (let [owners cns-owners
        own (get owners *scope*)]
    (if 
      (trust/trusted? own *caller* :control)
      (set! cns-owners (assoc owners *scope* cont))
      (fail :TRUST "No control right for CNS node"))))


(defn cns-control
  ^{:callable true
    :doc       {:description "Updates a CNS name mapping to set a new controller. May only be performed by a current controller."
                :examples    [{:code "(call *registry* (cns-control 'my.actor trust-monitor-address)"}]
                :signature   [{:params [name addr]}]}}
  [sym controller]
  (-check sym)
  (let [path (split (name sym) \.)
        record (get cns-database path)]
    (when (nil? record)
      (fail :STATE "CNS record does not exist"))
    (when (not (trust/trusted? (second record) *caller* :control))
      (fail :TRUST "Caller is not trusted with transferring control for that CNS record"))
    (set-in! cns-database [path 1] controller)))

(defn cns-resolve
  ^{:callable true
    :doc {:description "Resolves a name in the Convex Name Service."
          :examples    [{:code "(call *registry* (cns-resolve 'convex.registry)"}]
          :signature   [{:params [addr]}]}}
  [sym]
  (-check sym)
  (let [path (split (name sym) \.)
        record (get cns-database path)]
    (if record (first record) nil)))

(defn cns-update
  ^{:callable true
    :doc       {:description "Updates or adds a name mapping in the Convex Name Service. Only the owner of a CNS record may update the mapping for an existing name"
                :examples    [{:code "(call *registry* (cns-update 'my.actor addr)"}]
                :signature   [{:params [name addr]}]}}
  ([sym addr]
    (recur sym addr nil))
  ([sym addr meta]
	(when-not (account addr)
	  (fail :NOBODY "Can only use an existing account"))
	(when-not (symbol? sym)
	  (fail :ARGUMENT "CNS names must be a valid symbol"))
	(let [path (split (name sym) \.)
	      record (get cns-database path) 
	      monitor (if record (second record) *caller*)] ;; TODO limit ability to crteate top level CNS
      (and record (not (trust/trusted? monitor *caller* :update))
        (fail :TRUST "Unauthorised update to CNS record"))
    
      (set! cns-database
         (assoc cns-database
                path
                [addr monitor meta])))))

(defn cns-create-node
	^{:callable true
	  :doc {:description "Creates a child CNS node."
			:examples    [{:code "(call [cns-node cns-key] (cns-create-node \\\"child-name\\\"))"}]
			:signature   [{:params [sym]}]}}
	[pname owner]
	(or (trust/trusted? (get cns-owners *scope*) *caller* :create pname) (fail :TRUST "No permission to create CNS node"))
	(let [path (conj *scope* pname)]
	  (if (get cns-database path) (fail :STATE "CNS node already exists"))
	  (set-in! cns-owners [path] owner)
	  (set-in! cns-database [path] {})
	  [~*address* path]))

(defn cns-read
	^{:callable true
	  :doc {:description "Reads a child CNS record from this CNS node. Assumes a record key passed in *scope*."
			:examples    [{:code "(call [cns-node cns-key] (cns-read \\\"my-name\\\"))"}]
			:signature   [{:params [sym]}]}}
	[pname]
	(get-in cns-database [*scope* pname]))

(defn cns-write
	^{:callable true
	  :doc {:description "Writes a CNS record from this Actor. Assumes a CNS node key passed in *scope*."
	  :examples    [{:code "(call [cns-node cns-key] (cns-write \"my-name\" new-record))"}]
						:signature   [{:params [sym]}]}}
	[pname addr cont meta]
    (let [sm (get cns-database *scope*)]
	  (or (str? pname) (fail :ARGUMENT "CNS path element must be a string"))  
	  (or sm (error :STATE "CNS Node key not valid"))
	  (if-let [rec (get sm pname)]
    	;; This is an existing record, so check record controller
    	(or (trust/trusted? (get rec 1) *caller* :update) (fail :TRUST "No permission to update CNS record"))
    	;; This is a new record, so check create permission TODO use per-node monitor?
    	(or (trust/trusted? (get cns-owners *scope*) *caller* :create pname) (fail :TRUST "No permission to create CNS record")))
    		
      ;; update record since at this point all required checks have passed
	  (set-in! cns-database [*scope* pname] [addr cont meta])))





© 2015 - 2024 Weber Informatics LLC | Privacy Policy