cmd/abigen, accounts/abi: add support for log subscriptions

This commit is contained in:
Bas van Kervel 2017-10-05 13:36:12 +02:00
parent c0a1f1c907
commit 7943c71912
No known key found for this signature in database
GPG key ID: BFB23B252EF5812B
5 changed files with 109 additions and 9 deletions

View file

@ -90,8 +90,14 @@ type ContractTransactor interface {
SendTransaction(ctx context.Context, tx *types.Transaction) error
}
// ContractEventer defines methods to listen for events raised by the contract.
type ContractEventer interface {
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
}
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
type ContractBackend interface {
ContractCaller
ContractTransactor
ContractEventer
}

View file

@ -63,16 +63,18 @@ type BoundContract struct {
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
caller ContractCaller // Read interface to interact with the blockchain
transactor ContractTransactor // Write interface to interact with the blockchain
eventer ContractEventer // Listen for events raised by the contract
}
// NewBoundContract creates a low level contract interface through which calls
// and transactions may be made through.
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract {
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, eventer ContractEventer) *BoundContract {
return &BoundContract{
address: address,
abi: abi,
caller: caller,
transactor: transactor,
eventer: eventer,
}
}
@ -80,7 +82,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller
// deployment address with a Go wrapper.
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
// Otherwise try to deploy the contract
c := NewBoundContract(common.Address{}, abi, backend, backend)
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
input, err := c.abi.Pack("", params...)
if err != nil {
@ -225,6 +227,18 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
return signedTx, nil
}
// SubscribeFilterLogs creates a log subscription that streams logs raised by this contract
// with topics matching
func (c *BoundContract) SubscribeFilterLogs(opts *CallOpts, topics [][]common.Hash, ch chan<- types.Log) (ethereum.Subscription, error) {
ctx := ensureContext(opts.Context)
q := ethereum.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
}
return c.eventer.SubscribeFilterLogs(ctx, q, ch)
}
func ensureContext(ctx context.Context) context.Context {
if ctx == nil {
return context.TODO()

View file

@ -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]*tmplEvent)
)
for _, original := range evmABI.Methods {
// Normalize the method for capital cases and non-anonymous inputs/outputs
@ -94,6 +95,16 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
}
}
for _, e := range evmABI.Events {
events[e.Name] = &tmplEvent{
Name: e.Name,
Inputs: e.Inputs,
ID: fmt.Sprintf("%x", e.Id()),
Anonymous: e.Anonymous,
}
}
contracts[types[i]] = &tmplContract{
Type: capitalise(types[i]),
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
@ -101,6 +112,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
Constructor: evmABI.Constructor,
Calls: calls,
Transacts: transacts,
Events: events, // TODO: add event subscription support to the java template
}
}
// Generate the contract template data content and render it
@ -116,6 +128,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
"capitalise": capitalise,
"decapitalise": decapitalise,
}
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
if err := tmpl.Execute(buffer, data); err != nil {
return "", err

View file

@ -16,7 +16,12 @@
package bind
import "github.com/ethereum/go-ethereum/accounts/abi"
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
)
// tmplData is the data structure required to fill the binding template.
type tmplData struct {
@ -32,6 +37,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]*tmplEvent // Contract events
}
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
@ -42,6 +48,39 @@ type tmplMethod struct {
Structured bool // Whether the returns should be accumulated into a contract
}
// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed
// and cached data fields.
type tmplEvent struct {
Name string
Inputs []abi.Argument
ID string
Anonymous bool
}
func (e tmplEvent) Canonical() string {
var (
args []string
anonymous string
)
for _, a := range e.Inputs {
indexed := ""
if a.Indexed {
indexed = " indexed"
}
if a.Name != "" {
args = append(args, fmt.Sprintf("%s%s %s", a.Type, indexed, a.Name))
} else {
args = append(args, fmt.Sprintf("%s%s", a.Type, indexed))
}
}
if e.Anonymous {
anonymous = " anonymous"
}
return fmt.Sprintf("%s(%s)%s", e.Name, strings.Join(args, ", "), anonymous)
}
// tmplSource is language to template mapping containing all the supported
// programming languages the package can generate to.
var tmplSource = map[Lang]string{
@ -83,6 +122,7 @@ package {{.Package}}
type {{.Type}} struct {
{{.Type}}Caller // Read-only binding to the contract
{{.Type}}Transactor // Write-only binding to the contract
{{.Type}}Eventer // Event listener binding to the contract
}
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
@ -95,6 +135,12 @@ package {{.Package}}
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// {{.Type}}Eventer is an auto generated write-only Go binding around an Ethereum contract.
type {{.Type}}Eventer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
address common.Address // Contract address
}
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type {{.Type}}Session struct {
@ -134,7 +180,7 @@ package {{.Package}}
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
contract, err := bind{{.Type}}(address, backend, backend)
contract, err := bind{{.Type}}(address, backend, backend, backend)
if err != nil {
return nil, err
}
@ -143,7 +189,7 @@ package {{.Package}}
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
contract, err := bind{{.Type}}(address, caller, nil)
contract, err := bind{{.Type}}(address, caller, nil, nil)
if err != nil {
return nil, err
}
@ -152,20 +198,29 @@ package {{.Package}}
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
contract, err := bind{{.Type}}(address, nil, transactor)
contract, err := bind{{.Type}}(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &{{.Type}}Transactor{contract: contract}, nil
}
// New{{.Type}}Eventer creates a new listen only instance of {{.Type}}, bound to a specific deployed contract.
func New{{.Type}}Eventer(address common.Address, eventer bind.ContractEventer) (*{{.Type}}Eventer, error) {
contract, err := bind{{.Type}}(address, nil, nil, eventer)
if err != nil {
return nil, err
}
return &{{.Type}}Eventer{contract: contract, address: address}, nil
}
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) {
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, eventer bind.ContractEventer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor), nil
return bind.NewBoundContract(address, parsed, caller, transactor, eventer), nil
}
// Call invokes the (constant) contract method with params as input values and
@ -263,6 +318,18 @@ package {{.Package}}
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
}
{{end}}
{{range .Events}}
// Solidity: event {{.Canonical}}
//
// {{if not .Anonymous}}Note: this method will fill in the Event ID topic{{end}}
func (_{{$contract.Type}} *{{$contract.Type}}Caller) Subscribe{{.Name}}(opts *bind.CallOpts, ch chan<- types.Log, topics ...[]common.Hash) (ethereum.Subscription, error) {
{{if not .Anonymous}}id := []common.Hash{common.HexToHash("{{.ID}}")}
topics = append([][]common.Hash{id}, topics...)
{{end}}
return _{{$contract.Type}}.contract.SubscribeFilterLogs(opts, topics, ch)
}
{{end}}
{{end}}
`

View file

@ -138,7 +138,7 @@ func BindContract(address *Address, abiJSON string, client *EthereumClient) (con
return nil, err
}
return &BoundContract{
contract: bind.NewBoundContract(address.address, parsed, client.client, client.client),
contract: bind.NewBoundContract(address.address, parsed, client.client, client.client, client.client),
address: address.address,
}, nil
}