github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/accounts/abi/bind/base.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 19:16:30</date> 10 //</624450060037394432> 11 12 13 package bind 14 15 import ( 16 "context" 17 "errors" 18 "fmt" 19 "math/big" 20 21 "github.com/ethereum/go-ethereum" 22 "github.com/ethereum/go-ethereum/accounts/abi" 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/core/types" 25 "github.com/ethereum/go-ethereum/crypto" 26 "github.com/ethereum/go-ethereum/event" 27 ) 28 29 //当约定要求方法 30 //提交前签署交易。 31 type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) 32 33 //Callopts是对合同调用请求进行微调的选项集合。 34 type CallOpts struct { 35 Pending bool //是否对挂起状态或最后一个已知状态进行操作 36 From common.Address //可选发件人地址,否则使用第一个帐户 37 BlockNumber *big.Int //可选应在其上执行调用的块编号 38 Context context.Context //支持取消和超时的网络上下文(nil=无超时) 39 } 40 41 //TransactioOpts是创建 42 //有效的以太坊事务。 43 type TransactOpts struct { 44 From common.Address //用于发送交易的以太坊帐户 45 Nonce *big.Int //nonce用于事务执行(nil=使用挂起状态) 46 Signer SignerFn //用于签署交易的方法(强制) 47 48 Value *big.Int //随交易转移的资金(零=0=无资金) 49 GasPrice *big.Int //用于交易执行的天然气价格(零=天然气价格Oracle) 50 GasLimit uint64 //为交易执行设定的气体限制(0=估计) 51 52 Context context.Context //支持取消和超时的网络上下文(nil=无超时) 53 } 54 55 //filteropts是用于微调事件筛选的选项集合。 56 //在有约束力的合同中。 57 type FilterOpts struct { 58 Start uint64 //查询范围的开始 59 End *uint64 //范围结束(零=最新) 60 61 Context context.Context //支持取消和超时的网络上下文(nil=无超时) 62 } 63 64 //watchopts是对事件订阅进行微调的选项集合。 65 //在有约束力的合同中。 66 type WatchOpts struct { 67 Start *uint64 //查询范围的开始(nil=最新) 68 Context context.Context //支持取消和超时的网络上下文(nil=无超时) 69 } 70 71 //BoundContract是反映在 72 //以太坊网络。它包含由 73 //要操作的更高级别合同绑定。 74 type BoundContract struct { 75 address common.Address //以太坊区块链上合同的部署地址 76 abi abi.ABI //基于反射的ABI访问正确的以太坊方法 77 caller ContractCaller //读取与区块链交互的界面 78 transactor ContractTransactor //编写与区块链交互的接口 79 filterer ContractFilterer //与区块链交互的事件过滤 80 } 81 82 //NewboundContract创建一个低级合同接口,通过它调用 83 //交易可以通过。 84 func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { 85 return &BoundContract{ 86 address: address, 87 abi: abi, 88 caller: caller, 89 transactor: transactor, 90 filterer: filterer, 91 } 92 } 93 94 //DeployContract将合同部署到以太坊区块链上,并绑定 95 //使用Go包装器的部署地址。 96 func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { 97 //否则,尝试部署合同 98 c := NewBoundContract(common.Address{}, abi, backend, backend, backend) 99 100 input, err := c.abi.Pack("", params...) 101 if err != nil { 102 return common.Address{}, nil, nil, err 103 } 104 tx, err := c.transact(opts, nil, append(bytecode, input...)) 105 if err != nil { 106 return common.Address{}, nil, nil, err 107 } 108 c.address = crypto.CreateAddress(opts.From, tx.Nonce()) 109 return c.address, tx, c, nil 110 } 111 112 //调用调用(常量)contract方法,参数作为输入值,并且 113 //将输出设置为结果。结果类型可能是用于 114 //返回、匿名返回的接口切片和命名的结构 115 //返回。 116 func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { 117 //不要在懒惰的用户身上崩溃 118 if opts == nil { 119 opts = new(CallOpts) 120 } 121 //打包输入,调用并解压缩结果 122 input, err := c.abi.Pack(method, params...) 123 if err != nil { 124 return err 125 } 126 var ( 127 msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} 128 ctx = ensureContext(opts.Context) 129 code []byte 130 output []byte 131 ) 132 if opts.Pending { 133 pb, ok := c.caller.(PendingContractCaller) 134 if !ok { 135 return ErrNoPendingState 136 } 137 output, err = pb.PendingCallContract(ctx, msg) 138 if err == nil && len(output) == 0 { 139 //确保我们有一份合同要执行,否则就要保释。 140 if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { 141 return err 142 } else if len(code) == 0 { 143 return ErrNoCode 144 } 145 } 146 } else { 147 output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) 148 if err == nil && len(output) == 0 { 149 //确保我们有一份合同要执行,否则就要保释。 150 if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { 151 return err 152 } else if len(code) == 0 { 153 return ErrNoCode 154 } 155 } 156 } 157 if err != nil { 158 return err 159 } 160 return c.abi.Unpack(result, method, output) 161 } 162 163 //Transact使用参数作为输入值调用(付费)Contract方法。 164 func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 165 //否则,打包参数并调用合同 166 input, err := c.abi.Pack(method, params...) 167 if err != nil { 168 return nil, err 169 } 170 return c.transact(opts, &c.address, input) 171 } 172 173 //转账启动普通交易以将资金转移到合同,调用 174 //它的默认方法(如果有)。 175 func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { 176 return c.transact(opts, &c.address, nil) 177 } 178 179 //Transact执行实际的事务调用,首先派生任何缺少的 180 //授权字段,然后安排事务执行。 181 func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { 182 var err error 183 184 //确保有效的值字段并立即解析帐户 185 value := opts.Value 186 if value == nil { 187 value = new(big.Int) 188 } 189 var nonce uint64 190 if opts.Nonce == nil { 191 nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) 192 if err != nil { 193 return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) 194 } 195 } else { 196 nonce = opts.Nonce.Uint64() 197 } 198 //计算燃气补贴和燃气价格 199 gasPrice := opts.GasPrice 200 if gasPrice == nil { 201 gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context)) 202 if err != nil { 203 return nil, fmt.Errorf("failed to suggest gas price: %v", err) 204 } 205 } 206 gasLimit := opts.GasLimit 207 if gasLimit == 0 { 208 //如果没有方法调用代码,则无法成功估计气体 209 if contract != nil { 210 if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { 211 return nil, err 212 } else if len(code) == 0 { 213 return nil, ErrNoCode 214 } 215 } 216 //如果合同确实有代码(或不需要代码),则估计交易 217 msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input} 218 gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg) 219 if err != nil { 220 return nil, fmt.Errorf("failed to estimate gas needed: %v", err) 221 } 222 } 223 //创建事务,签名并计划执行 224 var rawTx *types.Transaction 225 if contract == nil { 226 rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) 227 } else { 228 rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) 229 } 230 if opts.Signer == nil { 231 return nil, errors.New("no signer to authorize the transaction with") 232 } 233 signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) 234 if err != nil { 235 return nil, err 236 } 237 if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { 238 return nil, err 239 } 240 return signedTx, nil 241 } 242 243 //filterlogs过滤过去块的合同日志,返回必要的 244 //在其上构造强类型绑定迭代器的通道。 245 func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { 246 //不要在懒惰的用户身上崩溃 247 if opts == nil { 248 opts = new(FilterOpts) 249 } 250 //将事件选择器附加到查询参数并构造主题集 251 query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...) 252 253 topics, err := makeTopics(query...) 254 if err != nil { 255 return nil, nil, err 256 } 257 //启动后台筛选 258 logs := make(chan types.Log, 128) 259 260 config := ethereum.FilterQuery{ 261 Addresses: []common.Address{c.address}, 262 Topics: topics, 263 FromBlock: new(big.Int).SetUint64(opts.Start), 264 } 265 if opts.End != nil { 266 config.ToBlock = new(big.Int).SetUint64(*opts.End) 267 } 268 /*TODO(karalabe):在支持时用此替换下面方法的其余部分 269 sub,err:=c.filter.subscribeBilterLogs(ensureContext(opts.context)、config、logs) 270 **/ 271 272 buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) 273 if err != nil { 274 return nil, nil, err 275 } 276 sub, err := event.NewSubscription(func(quit <-chan struct{}) error { 277 for _, log := range buff { 278 select { 279 case logs <- log: 280 case <-quit: 281 return nil 282 } 283 } 284 return nil 285 }), nil 286 287 if err != nil { 288 return nil, nil, err 289 } 290 return logs, sub, nil 291 } 292 293 //watchlogs过滤器订阅未来块的合同日志,返回 294 //可用于关闭观察程序的订阅对象。 295 func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { 296 //不要在懒惰的用户身上崩溃 297 if opts == nil { 298 opts = new(WatchOpts) 299 } 300 //将事件选择器附加到查询参数并构造主题集 301 query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...) 302 303 topics, err := makeTopics(query...) 304 if err != nil { 305 return nil, nil, err 306 } 307 //启动后台筛选 308 logs := make(chan types.Log, 128) 309 310 config := ethereum.FilterQuery{ 311 Addresses: []common.Address{c.address}, 312 Topics: topics, 313 } 314 if opts.Start != nil { 315 config.FromBlock = new(big.Int).SetUint64(*opts.Start) 316 } 317 sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) 318 if err != nil { 319 return nil, nil, err 320 } 321 return logs, sub, nil 322 } 323 324 //解包日志将检索到的日志解包到提供的输出结构中。 325 func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { 326 if len(log.Data) > 0 { 327 if err := c.abi.Unpack(out, event, log.Data); err != nil { 328 return err 329 } 330 } 331 var indexed abi.Arguments 332 for _, arg := range c.abi.Events[event].Inputs { 333 if arg.Indexed { 334 indexed = append(indexed, arg) 335 } 336 } 337 return parseTopics(out, indexed, log.Topics[1:]) 338 } 339 340 //EnsureContext是一个助手方法,用于确保上下文不为零,即使 341 //用户指定了它。 342 func ensureContext(ctx context.Context) context.Context { 343 if ctx == nil { 344 return context.TODO() 345 } 346 return ctx 347 } 348