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