github.com/metacurrency/holochain@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  }