github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/contracts/chequebook/cheque.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:33</date> 10 //</624342613121568768> 11 12 13 //包裹支票簿包裹“支票簿”以太坊智能合约。 14 // 15 //此包中的函数允许使用支票簿 16 //在以太网上签发、接收、验证支票;(自动)在以太网上兑现支票 17 //以及(自动)将以太存入支票簿合同。 18 package chequebook 19 20 //go:生成abigen--sol contract/checkbook.sol--exc contract/凡人.sol:凡人,contract/owned.sol:owned--pkg contract--out contract/checkbook.go 21 //go:生成go run./gencode.go 22 23 import ( 24 "bytes" 25 "context" 26 "crypto/ecdsa" 27 "encoding/json" 28 "fmt" 29 "io/ioutil" 30 "math/big" 31 "os" 32 "sync" 33 "time" 34 35 "github.com/ethereum/go-ethereum/accounts/abi/bind" 36 "github.com/ethereum/go-ethereum/common" 37 "github.com/ethereum/go-ethereum/common/hexutil" 38 "github.com/ethereum/go-ethereum/contracts/chequebook/contract" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/crypto" 41 "github.com/ethereum/go-ethereum/log" 42 "github.com/ethereum/go-ethereum/swarm/services/swap/swap" 43 ) 44 45 //托多(泽利格):观察同龄人的偿付能力,并通知跳票 46 //TODO(Zelig):通过签核启用支票付款 47 48 //有些功能需要与区块链交互: 49 //*设置对等支票簿的当前余额 50 //*发送交易以兑现支票 51 //*将乙醚存入支票簿 52 //*观察进入的乙醚 53 54 var ( 55 gasToCash = uint64(2000000) //使用支票簿的现金交易的天然气成本 56 // 57 ) 58 59 //后端包装支票簿操作所需的所有方法。 60 type Backend interface { 61 bind.ContractBackend 62 TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) 63 BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) 64 } 65 66 //支票代表对单一受益人的付款承诺。 67 type Cheque struct { 68 Contract common.Address //支票簿地址,避免交叉合同提交 69 Beneficiary common.Address 70 Amount *big.Int //所有已发送资金的累计金额 71 Sig []byte //签字(KECCAK256(合同、受益人、金额)、prvkey) 72 } 73 74 func (self *Cheque) String() string { 75 return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig) 76 } 77 78 type Params struct { 79 ContractCode, ContractAbi string 80 } 81 82 var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} 83 84 //支票簿可以创建并签署从单个合同到多个受益人的支票。 85 //它是对等小额支付的传出支付处理程序。 86 type Chequebook struct { 87 path string //支票簿文件路径 88 prvKey *ecdsa.PrivateKey //用于签署支票的私钥 89 lock sync.Mutex // 90 backend Backend //块链API 91 quit chan bool //关闭时会导致自动报告停止 92 owner common.Address //所有者地址(从pubkey派生) 93 contract *contract.Chequebook //非本征结合 94 session *contract.ChequebookSession //Abigen与Tx OPT结合 95 96 //保留字段 97 balance *big.Int // 98 contractAddr common.Address //合同地址 99 sent map[common.Address]*big.Int //受益人计数 100 101 txhash string //上次存款的Tx哈希Tx 102 threshold *big.Int //如果不是零,则触发自动报告的阈值 103 buffer *big.Int //保持平衡的缓冲器,用于叉保护 104 105 log log.Logger //嵌入合同地址的上下文记录器 106 } 107 108 func (self *Chequebook) String() string { 109 return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey) 110 } 111 112 // 113 func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { 114 balance := new(big.Int) 115 sent := make(map[common.Address]*big.Int) 116 117 chbook, err := contract.NewChequebook(contractAddr, backend) 118 if err != nil { 119 return nil, err 120 } 121 transactOpts := bind.NewKeyedTransactor(prvKey) 122 session := &contract.ChequebookSession{ 123 Contract: chbook, 124 TransactOpts: *transactOpts, 125 } 126 127 self = &Chequebook{ 128 prvKey: prvKey, 129 balance: balance, 130 contractAddr: contractAddr, 131 sent: sent, 132 path: path, 133 backend: backend, 134 owner: transactOpts.From, 135 contract: chbook, 136 session: session, 137 log: log.New("contract", contractAddr), 138 } 139 140 if (contractAddr != common.Address{}) { 141 self.setBalanceFromBlockChain() 142 self.log.Trace("New chequebook initialised", "owner", self.owner, "balance", self.balance) 143 } 144 return 145 } 146 147 func (self *Chequebook) setBalanceFromBlockChain() { 148 balance, err := self.backend.BalanceAt(context.TODO(), self.contractAddr, nil) 149 if err != nil { 150 log.Error("Failed to retrieve chequebook balance", "err", err) 151 } else { 152 self.balance.Set(balance) 153 } 154 } 155 156 //LoadChequebook loads a chequebook from disk (file path). 157 func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) { 158 var data []byte 159 data, err = ioutil.ReadFile(path) 160 if err != nil { 161 return 162 } 163 self, _ = NewChequebook(path, common.Address{}, prvKey, backend) 164 165 err = json.Unmarshal(data, self) 166 if err != nil { 167 return nil, err 168 } 169 if checkBalance { 170 self.setBalanceFromBlockChain() 171 } 172 log.Trace("Loaded chequebook from disk", "path", path) 173 174 return 175 } 176 177 //支票簿文件是支票簿的JSON表示。 178 type chequebookFile struct { 179 Balance string 180 Contract string 181 Owner string 182 Sent map[string]string 183 } 184 185 //取消对支票簿的反序列化。 186 func (self *Chequebook) UnmarshalJSON(data []byte) error { 187 var file chequebookFile 188 err := json.Unmarshal(data, &file) 189 if err != nil { 190 return err 191 } 192 _, ok := self.balance.SetString(file.Balance, 10) 193 if !ok { 194 return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) 195 } 196 self.contractAddr = common.HexToAddress(file.Contract) 197 for addr, sent := range file.Sent { 198 self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) 199 if !ok { 200 return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) 201 } 202 } 203 return nil 204 } 205 206 //Marshaljson将支票簿序列化。 207 func (self *Chequebook) MarshalJSON() ([]byte, error) { 208 var file = &chequebookFile{ 209 Balance: self.balance.String(), 210 Contract: self.contractAddr.Hex(), 211 Owner: self.owner.Hex(), 212 Sent: make(map[string]string), 213 } 214 for addr, sent := range self.sent { 215 file.Sent[addr.Hex()] = sent.String() 216 } 217 return json.Marshal(file) 218 } 219 220 //保存将支票簿保存在磁盘上,记住余额、合同地址和 221 // 222 func (self *Chequebook) Save() (err error) { 223 data, err := json.MarshalIndent(self, "", " ") 224 if err != nil { 225 return err 226 } 227 self.log.Trace("Saving chequebook to disk", self.path) 228 229 return ioutil.WriteFile(self.path, data, os.ModePerm) 230 } 231 232 //Stop退出自动报告Go例程以终止 233 func (self *Chequebook) Stop() { 234 defer self.lock.Unlock() 235 self.lock.Lock() 236 if self.quit != nil { 237 close(self.quit) 238 self.quit = nil 239 } 240 } 241 242 //发行创建由支票簿所有者的私钥签名的支票。这个 243 //签字人承诺合同(他们拥有的),受益人和金额。 244 func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { 245 defer self.lock.Unlock() 246 self.lock.Lock() 247 248 if amount.Sign() <= 0 { 249 return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) 250 } 251 if self.balance.Cmp(amount) < 0 { 252 err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, self.balance) 253 } else { 254 var sig []byte 255 sent, found := self.sent[beneficiary] 256 if !found { 257 sent = new(big.Int) 258 self.sent[beneficiary] = sent 259 } 260 sum := new(big.Int).Set(sent) 261 sum.Add(sum, amount) 262 263 sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey) 264 if err == nil { 265 ch = &Cheque{ 266 Contract: self.contractAddr, 267 Beneficiary: beneficiary, 268 Amount: sum, 269 Sig: sig, 270 } 271 sent.Set(sum) 272 self.balance.Sub(self.balance, amount) //从余额中减去金额 273 } 274 } 275 276 //如果设置了阈值且余额小于阈值,则自动存款 277 //请注意,即使签发支票失败,也会调用此函数。 278 //所以我们重新尝试存放 279 if self.threshold != nil { 280 if self.balance.Cmp(self.threshold) < 0 { 281 send := new(big.Int).Sub(self.buffer, self.balance) 282 self.deposit(send) 283 } 284 } 285 286 return 287 } 288 289 //现金是兑现支票的一种方便方法。 290 func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { 291 return ch.Cash(self.session) 292 } 293 294 //签署资料:合同地址、受益人、累计发送资金金额 295 func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { 296 bigamount := sum.Bytes() 297 if len(bigamount) > 32 { 298 return nil 299 } 300 var amount32 [32]byte 301 copy(amount32[32-len(bigamount):32], bigamount) 302 input := append(contract.Bytes(), beneficiary.Bytes()...) 303 input = append(input, amount32[:]...) 304 return crypto.Keccak256(input) 305 } 306 307 //余额返回支票簿的当前余额。 308 func (self *Chequebook) Balance() *big.Int { 309 defer self.lock.Unlock() 310 self.lock.Lock() 311 return new(big.Int).Set(self.balance) 312 } 313 314 //所有者返回支票簿的所有者帐户。 315 func (self *Chequebook) Owner() common.Address { 316 return self.owner 317 } 318 319 //地址返回支票簿的链上合同地址。 320 func (self *Chequebook) Address() common.Address { 321 return self.contractAddr 322 } 323 324 //把钱存入支票簿帐户。 325 func (self *Chequebook) Deposit(amount *big.Int) (string, error) { 326 defer self.lock.Unlock() 327 self.lock.Lock() 328 return self.deposit(amount) 329 } 330 331 //存款金额到支票簿帐户。 332 //调用方必须保持self.lock。 333 func (self *Chequebook) deposit(amount *big.Int) (string, error) { 334 //因为这里的金额是可变的,所以我们不使用会话 335 depositTransactor := bind.NewKeyedTransactor(self.prvKey) 336 depositTransactor.Value = amount 337 chbookRaw := &contract.ChequebookRaw{Contract: self.contract} 338 tx, err := chbookRaw.Transfer(depositTransactor) 339 if err != nil { 340 self.log.Warn("Failed to fund chequebook", "amount", amount, "balance", self.balance, "target", self.buffer, "err", err) 341 return "", err 342 } 343 //假设交易实际成功,我们立即添加余额。 344 self.balance.Add(self.balance, amount) 345 self.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", self.balance, "target", self.buffer) 346 return tx.Hash().Hex(), nil 347 } 348 349 //autodeposit(re)设置触发向 350 //支票簿。如果阈值不小于缓冲区,则需要设置合同后端,然后 351 //每一张新支票都会触发存款。 352 func (self *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 353 defer self.lock.Unlock() 354 self.lock.Lock() 355 self.threshold = threshold 356 self.buffer = buffer 357 self.autoDeposit(interval) 358 } 359 360 //自动报告启动一个定期向支票簿发送资金的goroutine 361 //如果checkbook.quit已关闭,则合同调用方将持有执行例程终止的锁。 362 func (self *Chequebook) autoDeposit(interval time.Duration) { 363 if self.quit != nil { 364 close(self.quit) 365 self.quit = nil 366 } 367 //如果阈值大于等于每次签发支票后自动保存余额 368 if interval == time.Duration(0) || self.threshold != nil && self.buffer != nil && self.threshold.Cmp(self.buffer) >= 0 { 369 return 370 } 371 372 ticker := time.NewTicker(interval) 373 self.quit = make(chan bool) 374 quit := self.quit 375 376 go func() { 377 for { 378 select { 379 case <-quit: 380 return 381 case <-ticker.C: 382 self.lock.Lock() 383 if self.balance.Cmp(self.buffer) < 0 { 384 amount := new(big.Int).Sub(self.buffer, self.balance) 385 txhash, err := self.deposit(amount) 386 if err == nil { 387 self.txhash = txhash 388 } 389 } 390 self.lock.Unlock() 391 } 392 } 393 }() 394 } 395 396 //发件箱可以向单个受益人签发来自单个合同的支票。 397 type Outbox struct { 398 chequeBook *Chequebook 399 beneficiary common.Address 400 } 401 402 //新发件箱创建发件箱。 403 func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { 404 return &Outbox{chbook, beneficiary} 405 } 406 407 //发行创建支票。 408 func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) { 409 return self.chequeBook.Issue(self.beneficiary, amount) 410 } 411 412 //自动存单可以在基础支票簿上自动存款。 413 func (self *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { 414 self.chequeBook.AutoDeposit(interval, threshold, buffer) 415 } 416 417 //stop帮助满足swap.outpayment接口。 418 func (self *Outbox) Stop() {} 419 420 //字符串实现fmt.stringer。 421 func (self *Outbox) String() string { 422 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance()) 423 } 424 425 //收件箱可以将单个合同中的支票存入、验证和兑现为单个合同中的支票。 426 //受益人。它是对等小额支付的传入支付处理程序。 427 type Inbox struct { 428 lock sync.Mutex 429 contract common.Address //同行支票簿合同 430 beneficiary common.Address //本地对等机的接收地址 431 sender common.Address //要从中发送兑现发送信息的本地对等方地址 432 signer *ecdsa.PublicKey //对等方的公钥 433 txhash string //上次兑现的Tx哈希Tx 434 session *contract.ChequebookSession //ABI与TX OPT的后端合同 435 quit chan bool //当关闭时,自动灰烬停止 436 maxUncashed *big.Int // 437 cashed *big.Int //累计兑现金额 438 cheque *Cheque //最后一张支票,如果没有收到,则为零。 439 log log.Logger //嵌入合同地址的上下文记录器 440 } 441 442 //newinbox创建一个收件箱。未持久化收件箱,将更新累积和 443 //收到第一张支票时从区块链获取。 444 func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) { 445 if signer == nil { 446 return nil, fmt.Errorf("signer is null") 447 } 448 chbook, err := contract.NewChequebook(contractAddr, abigen) 449 if err != nil { 450 return nil, err 451 } 452 transactOpts := bind.NewKeyedTransactor(prvKey) 453 transactOpts.GasLimit = gasToCash 454 session := &contract.ChequebookSession{ 455 Contract: chbook, 456 TransactOpts: *transactOpts, 457 } 458 sender := transactOpts.From 459 460 self = &Inbox{ 461 contract: contractAddr, 462 beneficiary: beneficiary, 463 sender: sender, 464 signer: signer, 465 session: session, 466 cashed: new(big.Int).Set(common.Big0), 467 log: log.New("contract", contractAddr), 468 } 469 self.log.Trace("New chequebook inbox initialized", "beneficiary", self.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer))) 470 return 471 } 472 473 func (self *Inbox) String() string { 474 return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.contract.Hex(), self.beneficiary.Hex(), self.cheque.Amount) 475 } 476 477 //停止退出自动清除引擎。 478 func (self *Inbox) Stop() { 479 defer self.lock.Unlock() 480 self.lock.Lock() 481 if self.quit != nil { 482 close(self.quit) 483 self.quit = nil 484 } 485 } 486 487 //现金试图兑现当前支票。 488 func (self *Inbox) Cash() (txhash string, err error) { 489 if self.cheque != nil { 490 txhash, err = self.cheque.Cash(self.session) 491 self.log.Trace("Cashing in chequebook cheque", "amount", self.cheque.Amount, "beneficiary", self.beneficiary) 492 self.cashed = self.cheque.Amount 493 } 494 return 495 } 496 497 //autocash(re)设置触发上次未兑现兑现的最大时间和金额。 498 //如果maxuncashed设置为0,则在收到时自动清除。 499 func (self *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { 500 defer self.lock.Unlock() 501 self.lock.Lock() 502 self.maxUncashed = maxUncashed 503 self.autoCash(cashInterval) 504 } 505 506 //autocash启动一个循环,周期性地清除最后一张支票。 507 //如果对等方是可信的。清理时间可以是24小时或一周。 508 //调用方必须保持self.lock。 509 func (self *Inbox) autoCash(cashInterval time.Duration) { 510 if self.quit != nil { 511 close(self.quit) 512 self.quit = nil 513 } 514 //如果maxuncashed设置为0,则在接收时自动清除 515 if cashInterval == time.Duration(0) || self.maxUncashed != nil && self.maxUncashed.Sign() == 0 { 516 return 517 } 518 519 ticker := time.NewTicker(cashInterval) 520 self.quit = make(chan bool) 521 quit := self.quit 522 523 go func() { 524 for { 525 select { 526 case <-quit: 527 return 528 case <-ticker.C: 529 self.lock.Lock() 530 if self.cheque != nil && self.cheque.Amount.Cmp(self.cashed) != 0 { 531 txhash, err := self.Cash() 532 if err == nil { 533 self.txhash = txhash 534 } 535 } 536 self.lock.Unlock() 537 } 538 } 539 }() 540 } 541 542 //Receive被调用将最新的支票存入接收的收件箱。 543 //给出的承诺必须是一张*支票。 544 func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { 545 ch := promise.(*Cheque) 546 547 defer self.lock.Unlock() 548 self.lock.Lock() 549 550 var sum *big.Int 551 if self.cheque == nil { 552 //一旦收到支票,金额将与区块链核对。 553 tally, err := self.session.Sent(self.beneficiary) 554 if err != nil { 555 return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) 556 } 557 sum = tally 558 } else { 559 sum = self.cheque.Amount 560 } 561 562 amount, err := ch.Verify(self.signer, self.contract, self.beneficiary, sum) 563 var uncashed *big.Int 564 if err == nil { 565 self.cheque = ch 566 567 if self.maxUncashed != nil { 568 uncashed = new(big.Int).Sub(ch.Amount, self.cashed) 569 if self.maxUncashed.Cmp(uncashed) < 0 { 570 self.Cash() 571 } 572 } 573 self.log.Trace("Received cheque in chequebook inbox", "amount", amount, "uncashed", uncashed) 574 } 575 576 return amount, err 577 } 578 579 //核实支票的签字人、合同人、受益人、金额、有效签字。 580 func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { 581 log.Trace("Verifying chequebook cheque", "cheque", self, "sum", sum) 582 if sum == nil { 583 return nil, fmt.Errorf("invalid amount") 584 } 585 586 if self.Beneficiary != beneficiary { 587 return nil, fmt.Errorf("beneficiary mismatch: %v != %v", self.Beneficiary.Hex(), beneficiary.Hex()) 588 } 589 if self.Contract != contract { 590 return nil, fmt.Errorf("contract mismatch: %v != %v", self.Contract.Hex(), contract.Hex()) 591 } 592 593 amount := new(big.Int).Set(self.Amount) 594 if sum != nil { 595 amount.Sub(amount, sum) 596 if amount.Sign() <= 0 { 597 return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) 598 } 599 } 600 601 pubKey, err := crypto.SigToPub(sigHash(self.Contract, beneficiary, self.Amount), self.Sig) 602 if err != nil { 603 return nil, fmt.Errorf("invalid signature: %v", err) 604 } 605 if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { 606 return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) 607 } 608 return amount, nil 609 } 610 611 //签名的V/R/S表示 612 func sig2vrs(sig []byte) (v byte, r, s [32]byte) { 613 v = sig[64] + 27 614 copy(r[:], sig[:32]) 615 copy(s[:], sig[32:64]) 616 return 617 } 618 619 //现金通过发送以太坊交易来兑现支票。 620 func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) { 621 v, r, s := sig2vrs(self.Sig) 622 tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s) 623 if err != nil { 624 return "", err 625 } 626 return tx.Hash().Hex(), nil 627 } 628 629 //validatecode检查地址处的链上代码是否与预期的支票簿匹配 630 //合同代码。这用于检测自杀支票簿。 631 func ValidateCode(ctx context.Context, b Backend, address common.Address) (ok bool, err error) { 632 code, err := b.CodeAt(ctx, address, nil) 633 if err != nil { 634 return false, err 635 } 636 return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil 637 } 638