diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 25b61928e1..0058f31e14 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -50,6 +50,10 @@ type ContractCaller interface { // ContractCall executes an Ethereum contract call with the specified data as the // input. CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + // SubscribeFilterLogs subscribes to the results of a streaming filter query. + SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) + // FilterLogs executes a filter query. + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]*types.Log, error) } // DeployBackend wraps the operations needed by WaitMined and WaitDeployed. diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 88fb3331e8..497a0b9023 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -40,6 +40,7 @@ import ( var _ bind.ContractBackend = (*SimulatedBackend)(nil) var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") +var errLogsUnsupported = errors.New("SimulatedBackend cannot query logs") // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // the background. Its main purpose is to allow easily testing contract bindings. @@ -284,6 +285,16 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa return nil } +//cannot query logs on simulated blockchain +func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, errLogsUnsupported +} + +//cannot query logs on simulated blockchain +func (b *SimulatedBackend) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]*types.Log, error) { + return nil, errLogsUnsupported +} + // callmsg implements core.Message to allow passing it as a transaction simulator. type callmsg struct { ethereum.CallMsg diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index b40bd65e80..9e6c2bba5e 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -145,6 +145,33 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, return c.abi.Unpack(result, method, output) } +// SubscribeFilterLogs query logs on blockchain with q. +// it returns a channel which tell all the already happed logs and the logs that will happen. +// you can cancel listen logs by Subscription's Unsubscribe +func (c *BoundContract) SubscribeFilterLogs(opts *CallOpts, q ethereum.FilterQuery) (<-chan types.Log, ethereum.Subscription, error) { + if opts == nil { + opts = new(CallOpts) + } + var ctx = ensureContext(opts.Context) + q.Addresses = []common.Address{c.address} + //first get all happened logs on the blockchain. + logs, err := c.caller.FilterLogs(ctx, q) + if err != nil { + return nil, nil, err + } + var ch = make(chan types.Log, len(logs)) + //subcribe logs that will happen. + sub, err := c.caller.SubscribeFilterLogs(ctx, q, ch) + if err != nil { + close(ch) + return nil, nil, err + } + for _, log := range logs { + ch <- *log + } + return ch, sub, err +} + // Transact invokes the (paid) contract method with params as input values. func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { // Otherwise pack up the parameters and invoke the contract diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index f587580884..b39d4e4b75 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -67,6 +67,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La var ( calls = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod) + events = make(map[string]string) ) for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs @@ -94,6 +95,9 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} } } + for _, ev := range evmABI.Events { + events[ev.Name] = ev.Name + } contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), @@ -101,6 +105,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La Constructor: evmABI.Constructor, Calls: calls, Transacts: transacts, + Events: events, } } // Generate the contract template data content and render it diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index d07610e7c2..c834af0fad 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -32,6 +32,7 @@ type tmplContract struct { Constructor abi.Method // Contract constructor for deploy parametrization Calls map[string]*tmplMethod // Contract calls that only read state data Transacts map[string]*tmplMethod // Contract calls that write state data + Events map[string]string //all contract events except the anonymous events todo events's indexed argument information should be used to generate code. } // tmplMethod is a wrapper around an abi.Method that contains a few preprocessed @@ -263,6 +264,56 @@ package {{.Package}} return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) } {{end}} + func (_{{$contract.Type}} *{{$contract.Type}}Caller) EventSubscribe(opts *bind.CallOpts, fromBlock rpc.BlockNumber, + toBlock rpc.BlockNumber, eventName string) (<-chan types.Log, ethereum.Subscription, error) { + parsed, err := abi.JSON(strings.NewReader(MyTokenABI)) + if err != nil { + return nil, nil, err + } + var q ethereum.FilterQuery + q.FromBlock = big.NewInt(int64(fromBlock)) + if toBlock == rpc.LatestBlockNumber { + q.ToBlock = nil + } else { + q.ToBlock = big.NewInt(int64(toBlock)) + } + q.Topics = [][]common.Hash{ + {parsed.Events[eventName].Id()}, //event signature + } + return _{{$contract.Type}}.contract.SubscribeFilterLogs(opts, q) + } + + {{range $name,$value := .Events}} + /* + get all event {{$name}} happened from [fromBlock] to [toBlock] + if [toBlock] is -1, you can get all the events that will happen later. + you can cancel event listenging through Subscription's Unsubscribe + */ + func (t *{{$contract.Type}}Caller) Event{{$name}}Subscribe(opts *bind.CallOpts, fromBlock rpc.BlockNumber, + toBlock rpc.BlockNumber) (<-chan types.Log, ethereum.Subscription, error) { + return t.EventSubscribe(opts, fromBlock, toBlock, "{{$name}}") + } + + /* + get all event {{$name}} happened from [fromBlock] to [toBlock] + if [toBlock] is -1, you can get all the events that will happen later. + you can cancel event listenging through Subscription's Unsubscribe + */ + func (t *{{$contract.Type}}Session) Event{{$name}}Subscribe(fromBlock rpc.BlockNumber, + toBlock rpc.BlockNumber) (<-chan types.Log, ethereum.Subscription, error) { + return t.Contract.Event{{$name}}Subscribe(&t.CallOpts, fromBlock, toBlock) + } + + /* + get all event {{$name}} happened from [fromBlock] to [toBlock] + if [toBlock] is -1, you can get all the events that will happen later. + you can cancel event listenging through Subscription's Unsubscribe + */ + func (t *{{$contract.Type}}CallerSession) Event{{$name}}Subscribe(opts *bind.CallOpts, fromBlock rpc.BlockNumber, + toBlock rpc.BlockNumber) (<-chan types.Log, ethereum.Subscription, error) { + return t.Contract.Event{{$name}}Subscribe(&t.CallOpts, fromBlock, toBlock) + } + {{end}} {{end}} ` diff --git a/eth/bind.go b/eth/bind.go index 0385db1f9f..510c9c6a6d 100644 --- a/eth/bind.go +++ b/eth/bind.go @@ -20,10 +20,12 @@ import ( "context" "math/big" + "errors" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -40,6 +42,7 @@ type ContractBackend struct { eapi *ethapi.PublicEthereumAPI // Wrapper around the Ethereum object to access metadata bcapi *ethapi.PublicBlockChainAPI // Wrapper around the blockchain to access chain data txapi *ethapi.PublicTransactionPoolAPI // Wrapper around the transaction pool to access transaction data + fapi *filters.PublicFilterAPI //wrapper around the filter to filter logs } // NewContractBackend creates a new native contract backend using an existing @@ -49,6 +52,7 @@ func NewContractBackend(apiBackend ethapi.Backend) *ContractBackend { eapi: ethapi.NewPublicEthereumAPI(apiBackend), bcapi: ethapi.NewPublicBlockChainAPI(apiBackend), txapi: ethapi.NewPublicTransactionPoolAPI(apiBackend, new(ethapi.AddrLocker)), + fapi: filters.NewPublicFilterAPI(apiBackend, false), } } @@ -136,3 +140,17 @@ func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transac _, err := b.txapi.SendRawTransaction(ctx, raw) return err } + +func (b *ContractBackend) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, errors.New("not support right now") +} + +func (b *ContractBackend) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]*types.Log, error) { + crit := filters.FilterCriteria{ + FromBlock: q.FromBlock, + ToBlock: q.ToBlock, + Addresses: q.Addresses, + Topics: q.Topics, + } + return b.fapi.GetLogs(ctx, crit) +} diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 48639d9492..421252f2b1 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -304,8 +304,8 @@ func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumb // Filters // FilterLogs executes a filter query. -func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - var result []types.Log +func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]*types.Log, error) { + var result []*types.Log err := ec.c.CallContext(ctx, &result, "eth_getLogs", toFilterArg(q)) return result, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index be17ffeae4..0a3fe4af09 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -67,6 +67,9 @@ type Backend interface { TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription + SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription + SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription + ChainConfig() *params.ChainConfig CurrentBlock() *types.Block }