convex.core.registry.cvx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
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])))