github.com/inklabsfoundation/inkchain@v0.17.1-0.20181025012015-c3cef8062f19/common/config/proposer.go (about) 1 /* 2 Copyright IBM Corp. 2017 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "fmt" 21 "sync" 22 23 "github.com/golang/protobuf/proto" 24 "github.com/inklabsfoundation/inkchain/common/flogging" 25 ) 26 27 var logger = flogging.MustGetLogger("common/config") 28 29 // ValueDeserializer provides a mechanism to retrieve proto messages to deserialize config values into 30 type ValueDeserializer interface { 31 // Deserialize takes a Value key as a string, and a marshaled Value value as bytes 32 // and returns the deserialized version of that value. Note, this function operates 33 // with side effects intended. Using a ValueDeserializer to deserialize a message will 34 // generally set the value in the Values interface that the ValueDeserializer derived from 35 // Therefore, the proto.Message may be safely discarded, but may be retained for 36 // inspection and or debugging purposes. 37 Deserialize(key string, value []byte) (proto.Message, error) 38 } 39 40 // Values defines a mechanism to supply messages to unamrshal from config 41 // and a mechanism to validate the results 42 type Values interface { 43 ValueDeserializer 44 45 // Validate should ensure that the values set into the proto messages are correct 46 // and that the new group values are allowed. It also includes a tx ID in case cross 47 // Handler invocations (ie to the MSP Config Manager) must be made 48 Validate(interface{}, map[string]ValueProposer) error 49 50 // Commit should call back into the Value handler to update the config 51 Commit() 52 } 53 54 // Handler 55 type Handler interface { 56 Allocate() Values 57 NewGroup(name string) (ValueProposer, error) 58 } 59 60 type config struct { 61 allocated Values 62 groups map[string]ValueProposer 63 } 64 65 type Proposer struct { 66 vh Handler 67 pending map[interface{}]*config 68 current *config 69 pendingLock sync.RWMutex 70 } 71 72 func NewProposer(vh Handler) *Proposer { 73 return &Proposer{ 74 vh: vh, 75 current: &config{}, 76 pending: make(map[interface{}]*config), 77 } 78 } 79 80 // BeginValueProposals called when a config proposal is begun 81 func (p *Proposer) BeginValueProposals(tx interface{}, groups []string) (ValueDeserializer, []ValueProposer, error) { 82 p.pendingLock.Lock() 83 defer p.pendingLock.Unlock() 84 if _, ok := p.pending[tx]; ok { 85 logger.Panicf("Duplicated BeginValueProposals without Rollback or Commit") 86 } 87 88 result := make([]ValueProposer, len(groups)) 89 90 pending := &config{ 91 allocated: p.vh.Allocate(), 92 groups: make(map[string]ValueProposer), 93 } 94 95 for i, groupName := range groups { 96 var group ValueProposer 97 var ok bool 98 99 if p.current == nil { 100 ok = false 101 } else { 102 group, ok = p.current.groups[groupName] 103 } 104 105 if !ok { 106 var err error 107 group, err = p.vh.NewGroup(groupName) 108 if err != nil { 109 pending = nil 110 return nil, nil, fmt.Errorf("Error creating group %s: %s", groupName, err) 111 } 112 } 113 114 pending.groups[groupName] = group 115 result[i] = group 116 } 117 118 p.pending[tx] = pending 119 120 return pending.allocated, result, nil 121 } 122 123 // Validate ensures that the new config values is a valid change 124 func (p *Proposer) PreCommit(tx interface{}) error { 125 p.pendingLock.RLock() 126 pending, ok := p.pending[tx] 127 p.pendingLock.RUnlock() 128 if !ok { 129 logger.Panicf("Serious Programming Error: attempted to pre-commit tx which had not been begun") 130 } 131 return pending.allocated.Validate(tx, pending.groups) 132 } 133 134 // RollbackProposals called when a config proposal is abandoned 135 func (p *Proposer) RollbackProposals(tx interface{}) { 136 p.pendingLock.Lock() 137 defer p.pendingLock.Unlock() 138 delete(p.pending, tx) 139 } 140 141 // CommitProposals called when a config proposal is committed 142 func (p *Proposer) CommitProposals(tx interface{}) { 143 p.pendingLock.Lock() 144 defer p.pendingLock.Unlock() 145 pending, ok := p.pending[tx] 146 if !ok { 147 logger.Panicf("Serious Programming Error: attempted to commit tx which had not been begun") 148 } 149 p.current = pending 150 p.current.allocated.Commit() 151 delete(p.pending, tx) 152 }