This post attempts to explain the automatic RL constraint generation in GOOSE and how it affects dynamic dispatch.
Currently, the RL constraints associated with an object / class are derived from the constructors, destructors and methods the user has defined, plus explicitly specified extra constraints. In most cases, the user does not need to explicitly add any extra constraints. The automatically generated RL constraints require that the object is created / destroyed / modified with one of the constructors / destructors / methods defined in its class.
This arguably results in a user-friendly approach to class definitions. For example, when the user writes a Kudos class with a single transfer method which requires owner authorization, they would naturally expect that there is no other way of transferring the Kudos (for example with a new transfer method omitting the authorization check). Only the methods explicitly specified in the class definition can be used – other ways of modifying the object are not allowed.
class Kudos {
owner : PublicIden
originator : PublicIden
denom : Denomination
quantity : Nat
constructor create(owner, denom, amount) : Kudos {
return Kudos {
owner := owner
originator := owner
denom := denom
quantity := amount
}
}
method transfer(newOwner : PublicIden) {
check authorizedBy(self.owner)
self.owner := newOwner
}
}
The disadvantage of the implicit-constraints approach is that it makes dynamic dispatch and other dynamic aspects of OOP hard or impossible. Because we check whether a called method is “known”, we cannot add new ones in subclasses, or override existing ones.
For example, consider:
class KudosStore {
owner : PublicIden
store : Stack<Kudos>
constructor create() {..}
method insert(k : Kudos) {
k.transfer(self.owner)
self.store.push(k)
}
}
------
class Kudos' : Kudos {
override method transfer(newOwner : PublicIden) {
...
}
}
If Kudos' is defined separately after defining Kudos and KudosStore, it is not possible to implement the above in a way which would allow Kudos' to be a subtype of Kudos so that it could be passed to KudosStore.insert and the right overridden Kudos'.transfer method would be called. This is because when compiling KudosStore we don’t know about the existence of the overridden Kudos'.transfer, so the generated RL cannot check for it.
We also cannot selectively allow the overriding of some methods while preventing the overriding of others. Once we open the possibility of allowing unknown calls in the object RL, arbitrary modifications of the object (modulo extra explicit constraints) can be submitted by a malicious attacker.
To see this more clearly, let’s look in more detail at how automatic constraint generation is done in GOOSE. The RL of the object checks if the received message is “known” and extra explicit per-object constraints are satisfied. The RL of the message checks if the object is modified in a way specified by the message / method. One could, of course, relax the RL of the object to allow any message. But then anything (modulo extra up-front constraints) can be done to the object because the RL of the message is not constrained.
In an explicit-constraints approach, the only thing that constrains the possible object changes are the up-front constraints the user who originally defines the object needs to come up with and make sure themselves that they fit all possible future situations. One could pursue this approach, but it seems less user-friendly because it is easy to forget about some constraints. In the implicit-constraints approach, simply no object modification is allowed that doesn’t correspond to one of the defined methods. In the explicit-constraints approach, the burden of coming up with the right constraints (simultaneously sufficiently general and restricted) is shifted to the user.
The explicit-constraints approach is compatible with GOOSE - one could simply indicate with some keyword to omit the known message check in the RL for a given class. The disadvantage is the necessity of coming up with explicit constraints. The advantage is that “dynamic” aspects of OOP become possible (method overriding, dynamic dispatch, etc).