github.com/decred/dcrlnd@v0.7.6/lnwallet/sigpool.go (about) 1 package lnwallet 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/decred/dcrd/dcrec/secp256k1/v4" 8 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 9 "github.com/decred/dcrd/wire" 10 "github.com/decred/dcrlnd/input" 11 "github.com/decred/dcrlnd/lnwire" 12 ) 13 14 const ( 15 // jobBuffer is a constant the represents the buffer of jobs in the two 16 // main queues. This allows clients avoid necessarily blocking when 17 // submitting jobs into the queue. 18 jobBuffer = 100 19 20 // TODO(roasbeef): job buffer pool? 21 ) 22 23 // VerifyJob is a job sent to the sigPool sig pool to verify a signature 24 // on a transaction. The items contained in the struct are necessary and 25 // sufficient to verify the full signature. The passed sigHash closure function 26 // should be set to a function that generates the relevant sighash. 27 // 28 // TODO(roasbeef): when we move to ecschnorr, make into batch signature 29 // verification using bos-coster (or pip?). 30 type VerifyJob struct { 31 // PubKey is the public key that was used to generate the purported 32 // valid signature. Note that with the current channel construction, 33 // this public key will likely have been tweaked using the current per 34 // commitment point for a particular commitment transactions. 35 PubKey *secp256k1.PublicKey 36 37 // Sig is the raw signature generated using the above public key. This 38 // is the signature to be verified. 39 Sig *ecdsa.Signature 40 41 // SigHash is a function closure generates the sighashes that the 42 // passed signature is known to have signed. 43 SigHash func() ([]byte, error) 44 45 // HtlcIndex is the index of the HTLC from the PoV of the remote 46 // party's update log. 47 HtlcIndex uint64 48 49 // Cancel is a channel that should be closed if the caller wishes to 50 // cancel all pending verification jobs part of a single batch. This 51 // channel is to be closed in the case that a single signature in a 52 // batch has been returned as invalid, as there is no need to verify 53 // the remainder of the signatures. 54 Cancel chan struct{} 55 56 // ErrResp is the channel that the result of the signature verification 57 // is to be sent over. In the see that the signature is valid, a nil 58 // error will be passed. Otherwise, a concrete error detailing the 59 // issue will be passed. 60 ErrResp chan *HtlcIndexErr 61 } 62 63 // HtlcIndexErr is a special type of error that also includes a pointer to the 64 // original validation job. This error message allows us to craft more detailed 65 // errors at upper layers. 66 type HtlcIndexErr struct { 67 error 68 69 *VerifyJob 70 } 71 72 // SignJob is a job sent to the sigPool sig pool to generate a valid 73 // signature according to the passed input.SignDescriptor for the passed transaction. 74 // Jobs are intended to be sent in batches in order to parallelize the job of 75 // generating signatures for a new commitment transaction. 76 type SignJob struct { 77 // SignDesc is intended to be a full populated input.SignDescriptor which 78 // encodes the necessary material (keys, witness script, etc) required 79 // to generate a valid signature for the specified input. 80 SignDesc input.SignDescriptor 81 82 // Tx is the transaction to be signed. This is required to generate the 83 // proper sighash for the input to be signed. 84 Tx *wire.MsgTx 85 86 // OutputIndex is the output index of the HTLC on the commitment 87 // transaction being signed. 88 OutputIndex int32 89 90 // Cancel is a channel that should be closed if the caller wishes to 91 // abandon all pending sign jobs part of a single batch. 92 Cancel chan struct{} 93 94 // Resp is the channel that the response to this particular SignJob 95 // will be sent over. 96 // 97 // TODO(roasbeef): actually need to allow caller to set, need to retain 98 // order mark commit sig as special 99 Resp chan SignJobResp 100 } 101 102 // SignJobResp is the response to a sign job. Both channels are to be read in 103 // order to ensure no unnecessary goroutine blocking occurs. Additionally, both 104 // channels should be buffered. 105 type SignJobResp struct { 106 // Sig is the generated signature for a particular SignJob In the case 107 // of an error during signature generation, then this value sent will 108 // be nil. 109 Sig lnwire.Sig 110 111 // Err is the error that occurred when executing the specified 112 // signature job. In the case that no error occurred, this value will 113 // be nil. 114 Err error 115 } 116 117 // TODO(roasbeef); fix description 118 119 // SigPool is a struct that is meant to allow the current channel state 120 // machine to parallelize all signature generation and verification. This 121 // struct is needed as _each_ HTLC when creating a commitment transaction 122 // requires a signature, and similarly a receiver of a new commitment must 123 // verify all the HTLC signatures included within the CommitSig message. A pool 124 // of workers will be maintained by the sigPool. Batches of jobs (either 125 // to sign or verify) can be sent to the pool of workers which will 126 // asynchronously perform the specified job. 127 type SigPool struct { 128 started sync.Once 129 stopped sync.Once 130 131 signer input.Signer 132 133 verifyJobs chan VerifyJob 134 signJobs chan SignJob 135 136 wg sync.WaitGroup 137 quit chan struct{} 138 139 numWorkers int 140 } 141 142 // NewSigPool creates a new signature pool with the specified number of 143 // workers. The recommended parameter for the number of works is the number of 144 // physical CPU cores available on the target machine. 145 func NewSigPool(numWorkers int, signer input.Signer) *SigPool { 146 return &SigPool{ 147 signer: signer, 148 numWorkers: numWorkers, 149 verifyJobs: make(chan VerifyJob, jobBuffer), 150 signJobs: make(chan SignJob, jobBuffer), 151 quit: make(chan struct{}), 152 } 153 } 154 155 // Start starts of all goroutines that the sigPool sig pool needs to 156 // carry out its duties. 157 func (s *SigPool) Start() error { 158 s.started.Do(func() { 159 for i := 0; i < s.numWorkers; i++ { 160 s.wg.Add(1) 161 go s.poolWorker() 162 } 163 }) 164 return nil 165 } 166 167 // Stop signals any active workers carrying out jobs to exit so the sigPool can 168 // gracefully shutdown. 169 func (s *SigPool) Stop() error { 170 s.stopped.Do(func() { 171 close(s.quit) 172 s.wg.Wait() 173 }) 174 return nil 175 } 176 177 // poolWorker is the main worker goroutine within the sigPool sig pool. 178 // Individual batches are distributed amongst each of the active workers. The 179 // workers then execute the task based on the type of job, and return the 180 // result back to caller. 181 func (s *SigPool) poolWorker() { 182 defer s.wg.Done() 183 184 for { 185 select { 186 187 // We've just received a new signature job. Given the items 188 // contained within the message, we'll craft a signature and 189 // send the result along with a possible error back to the 190 // caller. 191 case sigMsg := <-s.signJobs: 192 rawSig, err := s.signer.SignOutputRaw( 193 sigMsg.Tx, &sigMsg.SignDesc, 194 ) 195 if err != nil { 196 select { 197 case sigMsg.Resp <- SignJobResp{ 198 Sig: lnwire.Sig{}, 199 Err: err, 200 }: 201 continue 202 case <-sigMsg.Cancel: 203 continue 204 case <-s.quit: 205 return 206 } 207 } 208 209 sig, err := lnwire.NewSigFromSignature(rawSig) 210 select { 211 case sigMsg.Resp <- SignJobResp{ 212 Sig: sig, 213 Err: err, 214 }: 215 case <-sigMsg.Cancel: 216 continue 217 case <-s.quit: 218 return 219 } 220 221 // We've just received a new verification job from the outside 222 // world. We'll attempt to construct the sighash, parse the 223 // signature, and finally verify the signature. 224 case verifyMsg := <-s.verifyJobs: 225 sigHash, err := verifyMsg.SigHash() 226 if err != nil { 227 select { 228 case verifyMsg.ErrResp <- &HtlcIndexErr{ 229 error: err, 230 VerifyJob: &verifyMsg, 231 }: 232 continue 233 case <-verifyMsg.Cancel: 234 continue 235 } 236 } 237 238 rawSig := verifyMsg.Sig 239 240 if !rawSig.Verify(sigHash, verifyMsg.PubKey) { 241 err := fmt.Errorf("invalid signature "+ 242 "sighash: %x, sig: %x", sigHash, 243 rawSig.Serialize()) 244 245 select { 246 case verifyMsg.ErrResp <- &HtlcIndexErr{ 247 error: err, 248 VerifyJob: &verifyMsg, 249 }: 250 case <-verifyMsg.Cancel: 251 case <-s.quit: 252 return 253 } 254 } else { 255 select { 256 case verifyMsg.ErrResp <- nil: 257 case <-verifyMsg.Cancel: 258 case <-s.quit: 259 return 260 } 261 } 262 263 // The sigPool sig pool is exiting, so we will as well. 264 case <-s.quit: 265 return 266 } 267 } 268 } 269 270 // SubmitSignBatch submits a batch of signature jobs to the sigPool. The 271 // response and cancel channels for each of the SignJob's are expected to be 272 // fully populated, as the response for each job will be sent over the 273 // response channel within the job itself. 274 func (s *SigPool) SubmitSignBatch(signJobs []SignJob) { 275 for _, job := range signJobs { 276 select { 277 case s.signJobs <- job: 278 case <-job.Cancel: 279 // TODO(roasbeef): return error? 280 case <-s.quit: 281 return 282 } 283 } 284 } 285 286 // SubmitVerifyBatch submits a batch of verification jobs to the sigPool. For 287 // each job submitted, an error will be passed into the returned channel 288 // denoting if signature verification was valid or not. The passed cancelChan 289 // allows the caller to cancel all pending jobs in the case that they wish to 290 // bail early. 291 func (s *SigPool) SubmitVerifyBatch(verifyJobs []VerifyJob, 292 cancelChan chan struct{}) <-chan *HtlcIndexErr { 293 294 errChan := make(chan *HtlcIndexErr, len(verifyJobs)) 295 296 for _, job := range verifyJobs { 297 job.Cancel = cancelChan 298 job.ErrResp = errChan 299 300 select { 301 case s.verifyJobs <- job: 302 case <-job.Cancel: 303 return errChan 304 } 305 } 306 307 return errChan 308 }