github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/action.go (about) 1 // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 //---------------------------------------------------------------------------------------- 4 // 5 package holochain 6 7 import ( 8 "errors" 9 "fmt" 10 . "github.com/holochain/holochain-proto/hash" 11 b58 "github.com/jbenet/go-base58" 12 ic "github.com/libp2p/go-libp2p-crypto" 13 peer "github.com/libp2p/go-libp2p-peer" 14 "reflect" 15 "time" 16 ) 17 18 type ArgType int8 19 20 // these constants define the argument types for actions, i.e. system functions callable 21 // from within nuclei 22 const ( 23 HashArg = iota 24 StringArg 25 EntryArg // special arg type for entries, can be a string or a hash 26 IntArg 27 BoolArg 28 MapArg 29 ToStrArg // special arg type that converts anything to a string, used for the debug action 30 ArgsArg // special arg type for arguments passed to the call action 31 ) 32 33 const ( 34 DHTChangeOK = iota 35 DHTChangeUnknownHashQueuedForRetry 36 ) 37 38 // Arg holds the definition of an API function argument 39 type Arg struct { 40 Name string 41 Type ArgType 42 Optional bool 43 MapType reflect.Type 44 value interface{} 45 } 46 47 // APIFunction abstracts the argument structure and the calling of an api function 48 type APIFunction interface { 49 Name() string 50 Args() []Arg 51 Call(h *Holochain) (response interface{}, err error) 52 } 53 54 // Action provides an abstraction for handling node interaction 55 type Action interface { 56 Name() string 57 Receive(dht *DHT, msg *Message) (response interface{}, err error) 58 } 59 60 // CommittingAction provides an abstraction for grouping actions which carry Entry data 61 type CommittingAction interface { 62 Name() string 63 // Performs all validation logic, including sysValidation (must be called explicitly) 64 SysValidation(h *Holochain, def *EntryDef, pkg *Package, sources []peer.ID) (err error) 65 Receive(dht *DHT, msg *Message) (response interface{}, err error) 66 CheckValidationRequest(def *EntryDef) (err error) 67 EntryType() string 68 // returns a GobEntry containing the action's entry in a serialized format 69 Entry() Entry 70 SetHeader(header *Header) 71 GetHeader() (header *Header) 72 // Low level implementation of putting to DHT (assumes validation has been done) 73 Share(h *Holochain, def *EntryDef) (err error) 74 } 75 76 // ValidatingAction provides an abstraction for grouping all the actions that participate in validation loop 77 type ValidatingAction interface { 78 Name() string 79 SysValidation(h *Holochain, def *EntryDef, pkg *Package, sources []peer.ID) (err error) 80 Receive(dht *DHT, msg *Message) (response interface{}, err error) 81 CheckValidationRequest(def *EntryDef) (err error) 82 } 83 84 type ModAgentOptions struct { 85 Identity string 86 Revocation string 87 } 88 89 var NonDHTAction error = errors.New("Not a DHT action") 90 var ErrNotValidForDNAType error = errors.New("Invalid action for DNA type") 91 var ErrNotValidForAgentType error = errors.New("Invalid action for Agent type") 92 var ErrNotValidForKeyType error = errors.New("Invalid action for Key type") 93 var ErrNotValidForHeadersType error = errors.New("Invalid action for Headers type") 94 var ErrNotValidForDelType error = errors.New("Invalid action for Del type") 95 var ErrModInvalidForLinks error = errors.New("mod: invalid for Links entry") 96 var ErrModMissingHeader error = errors.New("mod: missing header") 97 var ErrModReplacesHashNotDifferent error = errors.New("mod: replaces must be different from original hash") 98 var ErrEntryDefInvalid = errors.New("Invalid Entry Defintion") 99 var ErrActionMissingHeader error = errors.New("Action is missing header") 100 var ErrActionReceiveInvalid error = errors.New("Action receive is invalid") 101 102 var ErrNilEntryInvalid error = errors.New("nil entry invalid") 103 104 func prepareSources(sources []peer.ID) (srcs []string) { 105 srcs = make([]string, 0) 106 for _, s := range sources { 107 srcs = append(srcs, peer.IDB58Encode(s)) 108 } 109 return 110 } 111 112 // ValidateAction runs the different phases of validating an action 113 func (h *Holochain) ValidateAction(a ValidatingAction, entryType string, pkg *Package, sources []peer.ID) (def *EntryDef, err error) { 114 115 defer func() { 116 if err != nil { 117 h.dht.dlog.Logf("%T Validation failed with: %v", a, err) 118 } 119 }() 120 121 var z *Zome 122 z, def, err = h.GetEntryDef(entryType) 123 if err != nil { 124 return 125 } 126 127 // run the action's system level validations 128 err = a.SysValidation(h, def, pkg, sources) 129 if err != nil { 130 h.Debugf("Sys ValidateAction(%T) err:%v\n", a, err) 131 return 132 } 133 if !def.IsSysEntry() { 134 135 // validation actions for application defined entry types 136 var vpkg *ValidationPackage 137 vpkg, err = MakeValidationPackage(h, pkg) 138 if err != nil { 139 return 140 } 141 142 // run the action's app level validations 143 var n Ribosome 144 n, err = z.MakeRibosome(h) 145 if err != nil { 146 return 147 } 148 149 err = n.ValidateAction(a, def, vpkg, prepareSources(sources)) 150 if err != nil { 151 h.Debugf("Ribosome ValidateAction(%T) err:%v\n", a, err) 152 } 153 } 154 return 155 } 156 157 // GetValidationResponse check the validation request and builds the validation package based 158 // on the app's requirements 159 func (h *Holochain) GetValidationResponse(a ValidatingAction, hash Hash) (resp ValidateResponse, err error) { 160 var entry Entry 161 entry, resp.Type, err = h.chain.GetEntry(hash) 162 if err == ErrHashNotFound { 163 if hash.String() == h.nodeIDStr { 164 resp.Type = KeyEntryType 165 var pk string 166 pk, err = h.agent.EncodePubKey() 167 if err != nil { 168 return 169 } 170 resp.Entry.C = pk 171 err = nil 172 } else { 173 return 174 } 175 } else if err != nil { 176 return 177 } else { 178 resp.Entry = *(entry.(*GobEntry)) 179 var hd *Header 180 hd, err = h.chain.GetEntryHeader(hash) 181 if err != nil { 182 return 183 } 184 resp.Header = *hd 185 } 186 switch resp.Type { 187 case DNAEntryType: 188 err = ErrNotValidForDNAType 189 return 190 case KeyEntryType: 191 // if key entry there no extra info to return in the package so do nothing 192 case HeadersEntryType: 193 // if headers entry there no extra info to return in the package so do nothing 194 case DelEntryType: 195 // if del entry there no extra info to return in the package so do nothing 196 case AgentEntryType: 197 // if agent, the package to return is the entry-type chain 198 // so that sys validation can confirm this agent entry in the chain 199 req := PackagingReq{PkgReqChain: int64(PkgReqChainOptFull), PkgReqEntryTypes: []string{AgentEntryType}} 200 resp.Package, err = MakePackage(h, req) 201 case MigrateEntryType: 202 // if migrate entry there no extra info to return in the package so do nothing 203 // TODO: later this might not be true, could return whole chain? 204 default: 205 // app defined entry types 206 var def *EntryDef 207 var z *Zome 208 z, def, err = h.GetEntryDef(resp.Type) 209 if err != nil { 210 return 211 } 212 err = a.CheckValidationRequest(def) 213 if err != nil { 214 return 215 } 216 217 // get the packaging request from the app 218 var n Ribosome 219 n, err = z.MakeRibosome(h) 220 if err != nil { 221 return 222 } 223 224 var req PackagingReq 225 req, err = n.ValidatePackagingRequest(a, def) 226 if err != nil { 227 h.Debugf("Ribosome GetValidationPackage(%T) err:%v\n", a, err) 228 } 229 resp.Package, err = MakePackage(h, req) 230 } 231 return 232 } 233 234 // MakeActionFromMessage generates an action from an action protocol messsage 235 func MakeActionFromMessage(msg *Message) (a Action, err error) { 236 var t reflect.Type 237 switch msg.Type { 238 case APP_MESSAGE: 239 a = &ActionSend{} 240 t = reflect.TypeOf(AppMsg{}) 241 case PUT_REQUEST: 242 a = &ActionPut{} 243 t = reflect.TypeOf(HoldReq{}) 244 case GET_REQUEST: 245 a = &ActionGet{} 246 t = reflect.TypeOf(GetReq{}) 247 case MOD_REQUEST: 248 a = &ActionMod{} 249 t = reflect.TypeOf(HoldReq{}) 250 case DEL_REQUEST: 251 a = &ActionDel{} 252 t = reflect.TypeOf(HoldReq{}) 253 case LINK_REQUEST: 254 a = &ActionLink{} 255 t = reflect.TypeOf(HoldReq{}) 256 case GETLINK_REQUEST: 257 a = &ActionGetLinks{} 258 t = reflect.TypeOf(LinkQuery{}) 259 case LISTADD_REQUEST: 260 a = &ActionListAdd{} 261 t = reflect.TypeOf(ListAddReq{}) 262 default: 263 err = fmt.Errorf("message type %d not in holochain-action protocol", int(msg.Type)) 264 } 265 if err == nil && reflect.TypeOf(msg.Body) != t { 266 err = fmt.Errorf("Unexpected request body type '%T' in %s request, expecting %v", msg.Body, a.Name(), t) 267 } 268 return 269 } 270 271 var ErrWrongNargs = errors.New("wrong number of arguments") 272 273 func checkArgCount(args []Arg, l int) (err error) { 274 var min int 275 for _, a := range args { 276 if !a.Optional { 277 min++ 278 } 279 } 280 if l < min || l > len(args) { 281 err = ErrWrongNargs 282 } 283 return 284 } 285 286 func argErr(typeName string, index int, arg Arg) error { 287 return fmt.Errorf("argument %d (%s) should be %s", index, arg.Name, typeName) 288 } 289 290 // doCommit adds an entry to the local chain after validating the action it's part of 291 func (h *Holochain) doCommit(a CommittingAction, change Hash) (d *EntryDef, err error) { 292 293 entryType := a.EntryType() 294 entry := a.Entry() 295 var l int 296 var hash Hash 297 var header *Header 298 var added bool 299 300 chain := h.Chain() 301 bundle := chain.BundleStarted() 302 if bundle != nil { 303 chain = bundle.chain 304 } 305 306 // retry loop incase someone sneaks a new commit in between prepareHeader and addEntry 307 for !added { 308 chain.lk.RLock() 309 count := len(chain.Headers) 310 l, hash, header, err = chain.prepareHeader(time.Now(), entryType, entry, h.agent.PrivKey(), change) 311 chain.lk.RUnlock() 312 if err != nil { 313 return 314 } 315 316 a.SetHeader(header) 317 d, err = h.ValidateAction(a, entryType, nil, []peer.ID{h.nodeID}) 318 if err != nil { 319 return 320 } 321 322 chain.lk.Lock() 323 if count == len(chain.Headers) { 324 err = chain.addEntry(l, hash, header, entry) 325 if err == nil { 326 added = true 327 } 328 } 329 chain.lk.Unlock() 330 if err != nil { 331 return 332 } 333 } 334 return 335 } 336 337 func (h *Holochain) commitAndShare(a CommittingAction, change Hash) (response Hash, err error) { 338 var def *EntryDef 339 def, err = h.doCommit(a, change) 340 if err != nil { 341 return 342 } 343 344 bundle := h.Chain().BundleStarted() 345 if bundle == nil { 346 err = a.Share(h, def) 347 } else { 348 bundle.sharing = append(bundle.sharing, a) 349 } 350 if err != nil { 351 return 352 } 353 response = a.GetHeader().EntryLink 354 return 355 } 356 357 func isValidPubKey(b58pk string) bool { 358 if len(b58pk) != 49 { 359 return false 360 } 361 pk := b58.Decode(b58pk) 362 _, err := ic.UnmarshalPublicKey(pk) 363 if err != nil { 364 return false 365 } 366 return true 367 } 368 369 const ( 370 ValidationFailureBadPublicKeyFormat = "bad public key format" 371 ValidationFailureBadRevocationFormat = "bad revocation format" 372 ) 373 374 func RunValidationPhase(h *Holochain, source peer.ID, msgType MsgType, query Hash, handler func(resp ValidateResponse) error) (err error) { 375 var r interface{} 376 msg := h.node.NewMessage(msgType, ValidateQuery{H: query}) 377 r, err = h.Send(h.node.ctx, ValidateProtocol, source, msg, 0) 378 if err != nil { 379 return 380 } 381 switch resp := r.(type) { 382 case ValidateResponse: 383 err = handler(resp) 384 default: 385 err = fmt.Errorf("expected ValidateResponse from validator got %T", r) 386 } 387 return 388 }