未验证 提交 5c83a4e5 编写于 作者: P Péter Szilágyi 提交者: GitHub

Merge pull request #15832 from karalabe/abigen-events

accounts/abi/bind: support event filtering in abigen
...@@ -97,7 +97,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { ...@@ -97,7 +97,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
Type string Type string
Name string Name string
Constant bool Constant bool
Indexed bool
Anonymous bool Anonymous bool
Inputs []Argument Inputs []Argument
Outputs []Argument Outputs []Argument
......
...@@ -52,12 +52,6 @@ type ContractCaller interface { ...@@ -52,12 +52,6 @@ type ContractCaller interface {
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
} }
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
type DeployBackend interface {
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
}
// PendingContractCaller defines methods to perform contract calls on the pending state. // PendingContractCaller defines methods to perform contract calls on the pending state.
// Call will try to discover this interface when access to the pending state is requested. // Call will try to discover this interface when access to the pending state is requested.
// If the backend does not support the pending state, Call returns ErrNoPendingState. // If the backend does not support the pending state, Call returns ErrNoPendingState.
...@@ -90,8 +84,29 @@ type ContractTransactor interface { ...@@ -90,8 +84,29 @@ type ContractTransactor interface {
SendTransaction(ctx context.Context, tx *types.Transaction) error SendTransaction(ctx context.Context, tx *types.Transaction) error
} }
// ContractFilterer defines the methods needed to access log events using one-off
// queries or continuous event subscriptions.
type ContractFilterer interface {
// FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
// SubscribeFilterLogs creates a background log filtering operation, returning
// a subscription immediately, which can be used to stream the found events.
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
}
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
type DeployBackend interface {
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
}
// ContractBackend defines the methods needed to work with contracts on a read-write basis. // ContractBackend defines the methods needed to work with contracts on a read-write basis.
type ContractBackend interface { type ContractBackend interface {
ContractCaller ContractCaller
ContractTransactor ContractTransactor
ContractFilterer
} }
...@@ -30,11 +30,15 @@ import ( ...@@ -30,11 +30,15 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
) )
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. // This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
...@@ -53,6 +57,8 @@ type SimulatedBackend struct { ...@@ -53,6 +57,8 @@ type SimulatedBackend struct {
pendingBlock *types.Block // Currently pending block that will be imported on request pendingBlock *types.Block // Currently pending block that will be imported on request
pendingState *state.StateDB // Currently pending state that will be the active on on request pendingState *state.StateDB // Currently pending state that will be the active on on request
events *filters.EventSystem // Event system for filtering log events live
config *params.ChainConfig config *params.ChainConfig
} }
...@@ -63,7 +69,13 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { ...@@ -63,7 +69,13 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc} genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
genesis.MustCommit(database) genesis.MustCommit(database)
blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{}) blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{})
backend := &SimulatedBackend{database: database, blockchain: blockchain, config: genesis.Config}
backend := &SimulatedBackend{
database: database,
blockchain: blockchain,
config: genesis.Config,
events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false),
}
backend.rollback() backend.rollback()
return backend return backend
} }
...@@ -248,7 +260,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs ...@@ -248,7 +260,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
return hi, nil return hi, nil
} }
// callContract implemens common code between normal and pending contract calls. // callContract implements common code between normal and pending contract calls.
// state is modified during execution, make sure to copy it if necessary. // state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
// Ensure message is initialized properly. // Ensure message is initialized properly.
...@@ -302,7 +314,69 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa ...@@ -302,7 +314,69 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
return nil return nil
} }
// JumpTimeInSeconds adds skip seconds to the clock // FilterLogs executes a log filter operation, blocking during execution and
// returning all the results in one batch.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
// Initialize unset filter boundaried to run from genesis to chain head
from := int64(0)
if query.FromBlock != nil {
from = query.FromBlock.Int64()
}
to := int64(-1)
if query.ToBlock != nil {
to = query.ToBlock.Int64()
}
// Construct and execute the filter
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
logs, err := filter.Logs(ctx)
if err != nil {
return nil, err
}
res := make([]types.Log, len(logs))
for i, log := range logs {
res[i] = *log
}
return res, nil
}
// SubscribeFilterLogs creates a background log filtering operation, returning a
// subscription immediately, which can be used to stream the found events.
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
// Subscribe to contract events
sink := make(chan []*types.Log)
sub, err := b.events.SubscribeLogs(query, sink)
if err != nil {
return nil, err
}
// Since we're getting logs in batches, we need to flatten them into a plain stream
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case logs := <-sink:
for _, log := range logs {
select {
case ch <- *log:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// AdjustTime adds a time shift to the simulated clock.
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
...@@ -331,3 +405,44 @@ func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } ...@@ -331,3 +405,44 @@ func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value } func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data } func (m callmsg) Data() []byte { return m.CallMsg.Data }
// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
type filterBackend struct {
db ethdb.Database
bc *core.BlockChain
}
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
if block == rpc.LatestBlockNumber {
return fb.bc.CurrentHeader(), nil
}
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
}
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
}
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit
return nil
})
}
func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
return fb.bc.SubscribeChainEvent(ch)
}
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
return fb.bc.SubscribeRemovedLogsEvent(ch)
}
func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
return fb.bc.SubscribeLogsEvent(ch)
}
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
panic("not supported")
}
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
) )
// SignerFn is a signer function callback when a contract requires a method to // SignerFn is a signer function callback when a contract requires a method to
...@@ -55,6 +56,22 @@ type TransactOpts struct { ...@@ -55,6 +56,22 @@ type TransactOpts struct {
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
} }
// FilterOpts is the collection of options to fine tune filtering for events
// within a bound contract.
type FilterOpts struct {
Start uint64 // Start of the queried range
End *uint64 // End of the range (nil = latest)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}
// WatchOpts is the collection of options to fine tune subscribing for events
// within a bound contract.
type WatchOpts struct {
Start *uint64 // Start of the queried range (nil = latest)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}
// BoundContract is the base wrapper object that reflects a contract on the // BoundContract is the base wrapper object that reflects a contract on the
// Ethereum network. It contains a collection of methods that are used by the // Ethereum network. It contains a collection of methods that are used by the
// higher level contract bindings to operate. // higher level contract bindings to operate.
...@@ -63,16 +80,18 @@ type BoundContract struct { ...@@ -63,16 +80,18 @@ type BoundContract struct {
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
caller ContractCaller // Read interface to interact with the blockchain caller ContractCaller // Read interface to interact with the blockchain
transactor ContractTransactor // Write interface to interact with the blockchain transactor ContractTransactor // Write interface to interact with the blockchain
filterer ContractFilterer // Event filtering to interact with the blockchain
} }
// NewBoundContract creates a low level contract interface through which calls // NewBoundContract creates a low level contract interface through which calls
// and transactions may be made through. // 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, filterer ContractFilterer) *BoundContract {
return &BoundContract{ return &BoundContract{
address: address, address: address,
abi: abi, abi: abi,
caller: caller, caller: caller,
transactor: transactor, transactor: transactor,
filterer: filterer,
} }
} }
...@@ -80,7 +99,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller ...@@ -80,7 +99,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller
// deployment address with a Go wrapper. // 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) { 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 // 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...) input, err := c.abi.Pack("", params...)
if err != nil { if err != nil {
...@@ -225,6 +244,104 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i ...@@ -225,6 +244,104 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
return signedTx, nil return signedTx, nil
} }
// FilterLogs filters contract logs for past blocks, returning the necessary
// channels to construct a strongly typed bound iterator on top of them.
func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
// Don't crash on a lazy user
if opts == nil {
opts = new(FilterOpts)
}
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
topics, err := makeTopics(query...)
if err != nil {
return nil, nil, err
}
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
FromBlock: new(big.Int).SetUint64(opts.Start),
}
if opts.End != nil {
config.ToBlock = new(big.Int).SetUint64(*opts.End)
}
/* TODO(karalabe): Replace the rest of the method below with this when supported
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
*/
buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config)
if err != nil {
return nil, nil, err
}
sub, err := event.NewSubscription(func(quit <-chan struct{}) error {
for _, log := range buff {
select {
case logs <- log:
case <-quit:
return nil
}
}
return nil
}), nil
if err != nil {
return nil, nil, err
}
return logs, sub, nil
}
// WatchLogs filters subscribes to contract logs for future blocks, returning a
// subscription object that can be used to tear down the watcher.
func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
// Don't crash on a lazy user
if opts == nil {
opts = new(WatchOpts)
}
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
topics, err := makeTopics(query...)
if err != nil {
return nil, nil, err
}
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
}
if opts.Start != nil {
config.FromBlock = new(big.Int).SetUint64(*opts.Start)
}
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
if err != nil {
return nil, nil, err
}
return logs, sub, nil
}
// UnpackLog unpacks a retrieved log into the provided output structure.
func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error {
if len(log.Data) > 0 {
if err := c.abi.Unpack(out, event, log.Data); err != nil {
return err
}
}
var indexed abi.Arguments
for _, arg := range c.abi.Events[event].Inputs {
if arg.Indexed {
indexed = append(indexed, arg)
}
}
return parseTopics(out, indexed, log.Topics[1:])
}
// ensureContext is a helper method to ensure a context is not nil, even if the
// user specified it as such.
func ensureContext(ctx context.Context) context.Context { func ensureContext(ctx context.Context) context.Context {
if ctx == nil { if ctx == nil {
return context.TODO() return context.TODO()
......
...@@ -63,10 +63,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La ...@@ -63,10 +63,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
return r return r
}, abis[i]) }, abis[i])
// Extract the call and transact methods, and sort them alphabetically // Extract the call and transact methods; events; and sort them alphabetically
var ( var (
calls = make(map[string]*tmplMethod) calls = make(map[string]*tmplMethod)
transacts = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod)
events = make(map[string]*tmplEvent)
) )
for _, original := range evmABI.Methods { for _, original := range evmABI.Methods {
// Normalize the method for capital cases and non-anonymous inputs/outputs // Normalize the method for capital cases and non-anonymous inputs/outputs
...@@ -89,11 +90,33 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La ...@@ -89,11 +90,33 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
} }
// Append the methods to the call or transact lists // Append the methods to the call or transact lists
if original.Const { if original.Const {
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
} else { } else {
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
} }
} }
for _, original := range evmABI.Events {
// Skip anonymous events as they don't support explicit filtering
if original.Anonymous {
continue
}
// Normalize the event for capital cases and non-anonymous outputs
normalized := original
normalized.Name = methodNormalizer[lang](original.Name)
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
// Indexed fields are input, non-indexed ones are outputs
if input.Indexed {
if input.Name == "" {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
}
}
// Append the event to the accumulator list
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
}
contracts[types[i]] = &tmplContract{ contracts[types[i]] = &tmplContract{
Type: capitalise(types[i]), Type: capitalise(types[i]),
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
...@@ -101,6 +124,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La ...@@ -101,6 +124,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
Constructor: evmABI.Constructor, Constructor: evmABI.Constructor,
Calls: calls, Calls: calls,
Transacts: transacts, Transacts: transacts,
Events: events,
} }
} }
// Generate the contract template data content and render it // Generate the contract template data content and render it
...@@ -111,10 +135,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La ...@@ -111,10 +135,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
funcs := map[string]interface{}{ funcs := map[string]interface{}{
"bindtype": bindType[lang], "bindtype": bindType[lang],
"namedtype": namedType[lang], "bindtopictype": bindTopicType[lang],
"capitalise": capitalise, "namedtype": namedType[lang],
"decapitalise": decapitalise, "capitalise": capitalise,
"decapitalise": decapitalise,
} }
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
if err := tmpl.Execute(buffer, data); err != nil { if err := tmpl.Execute(buffer, data); err != nil {
...@@ -133,7 +158,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La ...@@ -133,7 +158,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
} }
// bindType is a set of type binders that convert Solidity types to some supported // bindType is a set of type binders that convert Solidity types to some supported
// programming language. // programming language types.
var bindType = map[Lang]func(kind abi.Type) string{ var bindType = map[Lang]func(kind abi.Type) string{
LangGo: bindTypeGo, LangGo: bindTypeGo,
LangJava: bindTypeJava, LangJava: bindTypeJava,
...@@ -254,6 +279,33 @@ func bindTypeJava(kind abi.Type) string { ...@@ -254,6 +279,33 @@ func bindTypeJava(kind abi.Type) string {
} }
} }
// bindTopicType is a set of type binders that convert Solidity types to some
// supported programming language topic types.
var bindTopicType = map[Lang]func(kind abi.Type) string{
LangGo: bindTopicTypeGo,
LangJava: bindTopicTypeJava,
}
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
// funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeGo(kind abi.Type) string {
bound := bindTypeGo(kind)
if bound == "string" || bound == "[]byte" {
bound = "common.Hash"
}
return bound
}
// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
// funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeJava(kind abi.Type) string {
bound := bindTypeJava(kind)
if bound == "String" || bound == "Bytes" {
bound = "Hash"
}
return bound
}
// namedType is a set of functions that transform language specific types to // namedType is a set of functions that transform language specific types to
// named versions that my be used inside method names. // named versions that my be used inside method names.
var namedType = map[Lang]func(string, abi.Type) string{ var namedType = map[Lang]func(string, abi.Type) string{
...@@ -321,14 +373,14 @@ func decapitalise(input string) string { ...@@ -321,14 +373,14 @@ func decapitalise(input string) string {
return strings.ToLower(input[:1]) + input[1:] return strings.ToLower(input[:1]) + input[1:]
} }
// structured checks whether a method has enough information to return a proper // structured checks whether a list of ABI data types has enough information to
// Go struct or if flat returns are needed. // operate through a proper Go struct or if flat returns are needed.
func structured(method abi.Method) bool { func structured(args abi.Arguments) bool {
if len(method.Outputs) < 2 { if len(args) < 2 {
return false return false
} }
exists := make(map[string]bool) exists := make(map[string]bool)
for _, out := range method.Outputs { for _, out := range args {
// If the name is anonymous, we can't organize into a struct // If the name is anonymous, we can't organize into a struct
if out.Name == "" { if out.Name == "" {
return false return false
......
...@@ -148,6 +148,64 @@ var bindTests = []struct { ...@@ -148,6 +148,64 @@ var bindTests = []struct {
fmt.Println(str1, str2, res.Str1, res.Str2, err) fmt.Println(str1, str2, res.Str1, res.Str2, err)
}`, }`,
}, },
// Tests that named, anonymous and indexed events are handled correctly
{
`EventChecker`, ``, ``,
`
[
{"type":"event","name":"empty","inputs":[]},
{"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256","indexed":true}]},
{"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256"}]},
{"type":"event","name":"anonymous","anonymous":true,"inputs":[]},
{"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]}
]
`,
`if e, err := NewEventChecker(common.Address{}, nil); e == nil || err != nil {
t.Fatalf("binding (%v) nil or error (%v) not nil", e, nil)
} else if false { // Don't run, just compile and test types
var (
err error
res bool
str string
dat []byte
hash common.Hash
)
_, err = e.FilterEmpty(nil)
_, err = e.FilterIndexed(nil, []common.Address{}, []*big.Int{})
mit, err := e.FilterMixed(nil, []common.Address{})
res = mit.Next() // Make sure the iterator has a Next method
err = mit.Error() // Make sure the iterator has an Error method
err = mit.Close() // Make sure the iterator has a Close method
fmt.Println(mit.Event.Raw.BlockHash) // Make sure the raw log is contained within the results
fmt.Println(mit.Event.Num) // Make sure the unpacked non-indexed fields are present
fmt.Println(mit.Event.Addr) // Make sure the reconstructed indexed fields are present
dit, err := e.FilterDynamic(nil, []string{}, [][]byte{})
str = dit.Event.Str // Make sure non-indexed strings retain their type
dat = dit.Event.Dat // Make sure non-indexed bytes retain their type
hash = dit.Event.IdxStr // Make sure indexed strings turn into hashes
hash = dit.Event.IdxDat // Make sure indexed bytes turn into hashes
sink := make(chan *EventCheckerMixed)
sub, err := e.WatchMixed(nil, sink, []common.Address{})
defer sub.Unsubscribe()
event := <-sink
fmt.Println(event.Raw.BlockHash) // Make sure the raw log is contained within the results
fmt.Println(event.Num) // Make sure the unpacked non-indexed fields are present
fmt.Println(event.Addr) // Make sure the reconstructed indexed fields are present
fmt.Println(res, str, dat, hash, err)
}
// Run a tiny reflection test to ensure disallowed methods don't appear
if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok {
t.Errorf("binding has disallowed method (FilterAnonymous)")
}`,
},
// Test that contract interactions (deploy, transact and call) generate working code // Test that contract interactions (deploy, transact and call) generate working code
{ {
`Interactor`, `Interactor`,
...@@ -508,6 +566,177 @@ var bindTests = []struct { ...@@ -508,6 +566,177 @@ var bindTests = []struct {
fmt.Println(a, b, err) fmt.Println(a, b, err)
`, `,
}, },
// Tests that logs can be successfully filtered and decoded.
{
`Eventer`,
`
contract Eventer {
event SimpleEvent (
address indexed Addr,
bytes32 indexed Id,
bool indexed Flag,
uint Value
);
function raiseSimpleEvent(address addr, bytes32 id, bool flag, uint value) {
SimpleEvent(addr, id, flag, value);
}
event NodataEvent (
uint indexed Number,
int16 indexed Short,
uint32 indexed Long
);
function raiseNodataEvent(uint number, int16 short, uint32 long) {
NodataEvent(number, short, long);
}
event DynamicEvent (
string indexed IndexedString,
bytes indexed IndexedBytes,
string NonIndexedString,
bytes NonIndexedBytes
);
function raiseDynamicEvent(string str, bytes blob) {
DynamicEvent(str, blob, str, blob);
}
}
`,
`6060604052341561000f57600080fd5b61042c8061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063528300ff1461005c578063630c31e2146100fc578063c7d116dd14610156575b600080fd5b341561006757600080fd5b6100fa600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610194565b005b341561010757600080fd5b610154600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035600019169060200190919080351515906020019091908035906020019091905050610367565b005b341561016157600080fd5b610192600480803590602001909190803560010b90602001909190803563ffffffff169060200190919050506103c3565b005b806040518082805190602001908083835b6020831015156101ca57805182526020820191506020810190506020830392506101a5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020826040518082805190602001908083835b60208310151561022d5780518252602082019150602081019050602083039250610208565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f3281fd4f5e152dd3385df49104a3f633706e21c9e80672e88d3bcddf33101f008484604051808060200180602001838103835285818151815260200191508051906020019080838360005b838110156102c15780820151818401526020810190506102a6565b50505050905090810190601f1680156102ee5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b8381101561032757808201518184015260208101905061030c565b50505050905090810190601f1680156103545780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a35050565b81151583600019168573ffffffffffffffffffffffffffffffffffffffff167f1f097de4289df643bd9c11011cc61367aa12983405c021056e706eb5ba1250c8846040518082815260200191505060405180910390a450505050565b8063ffffffff168260010b847f3ca7f3a77e5e6e15e781850bc82e32adfa378a2a609370db24b4d0fae10da2c960405160405180910390a45050505600a165627a7a72305820d1f8a8bbddbc5bb29f285891d6ae1eef8420c52afdc05e1573f6114d8e1714710029`,
`[{"constant":false,"inputs":[{"name":"str","type":"string"},{"name":"blob","type":"bytes"}],"name":"raiseDynamicEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"id","type":"bytes32"},{"name":"flag","type":"bool"},{"name":"value","type":"uint256"}],"name":"raiseSimpleEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"number","type":"uint256"},{"name":"short","type":"int16"},{"name":"long","type":"uint32"}],"name":"raiseNodataEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Addr","type":"address"},{"indexed":true,"name":"Id","type":"bytes32"},{"indexed":true,"name":"Flag","type":"bool"},{"indexed":false,"name":"Value","type":"uint256"}],"name":"SimpleEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Number","type":"uint256"},{"indexed":true,"name":"Short","type":"int16"},{"indexed":true,"name":"Long","type":"uint32"}],"name":"NodataEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedString","type":"string"},{"indexed":true,"name":"IndexedBytes","type":"bytes"},{"indexed":false,"name":"NonIndexedString","type":"string"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes"}],"name":"DynamicEvent","type":"event"}]`,
`
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}})
// Deploy an eventer contract
_, _, eventer, err := DeployEventer(auth, sim)
if err != nil {
t.Fatalf("Failed to deploy eventer contract: %v", err)
}
sim.Commit()
// Inject a few events into the contract, gradually more in each block
for i := 1; i <= 3; i++ {
for j := 1; j <= i; j++ {
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
}
}
sim.Commit()
}
// Test filtering for certain events and ensure they can be found
sit, err := eventer.FilterSimpleEvent(nil, []common.Address{common.Address{1}, common.Address{3}}, [][32]byte{{byte(1)}, {byte(2)}, {byte(3)}}, []bool{true})
if err != nil {
t.Fatalf("failed to filter for simple events: %v", err)
}
defer sit.Close()
sit.Next()
if sit.Event.Value.Uint64() != 11 || !sit.Event.Flag {
t.Errorf("simple log content mismatch: have %v, want {11, true}", sit.Event)
}
sit.Next()
if sit.Event.Value.Uint64() != 21 || !sit.Event.Flag {
t.Errorf("simple log content mismatch: have %v, want {21, true}", sit.Event)
}
sit.Next()
if sit.Event.Value.Uint64() != 31 || !sit.Event.Flag {
t.Errorf("simple log content mismatch: have %v, want {31, true}", sit.Event)
}
sit.Next()
if sit.Event.Value.Uint64() != 33 || !sit.Event.Flag {
t.Errorf("simple log content mismatch: have %v, want {33, true}", sit.Event)
}
if sit.Next() {
t.Errorf("unexpected simple event found: %+v", sit.Event)
}
if err = sit.Error(); err != nil {
t.Fatalf("simple event iteration failed: %v", err)
}
// Test raising and filtering for an event with no data component
if _, err := eventer.RaiseNodataEvent(auth, big.NewInt(314), 141, 271); err != nil {
t.Fatalf("failed to raise nodata event: %v", err)
}
sim.Commit()
nit, err := eventer.FilterNodataEvent(nil, []*big.Int{big.NewInt(314)}, []int16{140, 141, 142}, []uint32{271})
if err != nil {
t.Fatalf("failed to filter for nodata events: %v", err)
}
defer nit.Close()
if !nit.Next() {
t.Fatalf("nodata log not found: %v", nit.Error())
}
if nit.Event.Number.Uint64() != 314 {
t.Errorf("nodata log content mismatch: have %v, want 314", nit.Event.Number)
}
if nit.Next() {
t.Errorf("unexpected nodata event found: %+v", nit.Event)
}
if err = nit.Error(); err != nil {
t.Fatalf("nodata event iteration failed: %v", err)
}
// Test raising and filtering for events with dynamic indexed components
if _, err := eventer.RaiseDynamicEvent(auth, "Hello", []byte("World")); err != nil {
t.Fatalf("failed to raise dynamic event: %v", err)
}
sim.Commit()
dit, err := eventer.FilterDynamicEvent(nil, []string{"Hi", "Hello", "Bye"}, [][]byte{[]byte("World")})
if err != nil {
t.Fatalf("failed to filter for dynamic events: %v", err)
}
defer dit.Close()
if !dit.Next() {
t.Fatalf("dynamic log not found: %v", dit.Error())
}
if dit.Event.NonIndexedString != "Hello" || string(dit.Event.NonIndexedBytes) != "World" || dit.Event.IndexedString != common.HexToHash("0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2") || dit.Event.IndexedBytes != common.HexToHash("0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18") {
t.Errorf("dynamic log content mismatch: have %v, want {'0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2, '0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18', 'Hello', 'World'}", dit.Event)
}
if dit.Next() {
t.Errorf("unexpected dynamic event found: %+v", dit.Event)
}
if err = dit.Error(); err != nil {
t.Fatalf("dynamic event iteration failed: %v", err)
}
// Test subscribing to an event and raising it afterwards
ch := make(chan *EventerSimpleEvent, 16)
sub, err := eventer.WatchSimpleEvent(nil, ch, nil, nil, nil)
if err != nil {
t.Fatalf("failed to subscribe to simple events: %v", err)
}
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{255}, [32]byte{255}, true, big.NewInt(255)); err != nil {
t.Fatalf("failed to raise subscribed simple event: %v", err)
}
sim.Commit()
select {
case event := <-ch:
if event.Value.Uint64() != 255 {
t.Errorf("simple log content mismatch: have %v, want 255", event)
}
case <-time.After(250 * time.Millisecond):
t.Fatalf("subscribed simple event didn't arrive")
}
// Unsubscribe from the event and make sure we're not delivered more
sub.Unsubscribe()
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{254}, [32]byte{254}, true, big.NewInt(254)); err != nil {
t.Fatalf("failed to raise subscribed simple event: %v", err)
}
sim.Commit()
select {
case event := <-ch:
t.Fatalf("unsubscribed simple event arrived: %v", event)
case <-time.After(250 * time.Millisecond):
}
`,
},
} }
// Tests that packages generated by the binder can be successfully compiled and // Tests that packages generated by the binder can be successfully compiled and
...@@ -559,7 +788,7 @@ func TestBindings(t *testing.T) { ...@@ -559,7 +788,7 @@ func TestBindings(t *testing.T) {
} }
} }
// Test the entire package and report any failures // Test the entire package and report any failures
cmd := exec.Command(gocmd, "test", "-v") cmd := exec.Command(gocmd, "test", "-v", "-count", "1")
cmd.Dir = pkg cmd.Dir = pkg
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to run binding test: %v\n%s", err, out) t.Fatalf("failed to run binding test: %v\n%s", err, out)
......
...@@ -32,6 +32,7 @@ type tmplContract struct { ...@@ -32,6 +32,7 @@ type tmplContract struct {
Constructor abi.Method // Contract constructor for deploy parametrization Constructor abi.Method // Contract constructor for deploy parametrization
Calls map[string]*tmplMethod // Contract calls that only read state data Calls map[string]*tmplMethod // Contract calls that only read state data
Transacts map[string]*tmplMethod // Contract calls that write state data Transacts map[string]*tmplMethod // Contract calls that write state data
Events map[string]*tmplEvent // Contract events accessors
} }
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed // tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
...@@ -39,7 +40,13 @@ type tmplContract struct { ...@@ -39,7 +40,13 @@ type tmplContract struct {
type tmplMethod struct { type tmplMethod struct {
Original abi.Method // Original method as parsed by the abi package Original abi.Method // Original method as parsed by the abi package
Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns)
Structured bool // Whether the returns should be accumulated into a contract Structured bool // Whether the returns should be accumulated into a struct
}
// tmplEvent is a wrapper around an a
type tmplEvent struct {
Original abi.Event // Original event as parsed by the abi package
Normalized abi.Event // Normalized version of the parsed fields
} }
// tmplSource is language to template mapping containing all the supported // tmplSource is language to template mapping containing all the supported
...@@ -75,7 +82,7 @@ package {{.Package}} ...@@ -75,7 +82,7 @@ package {{.Package}}
if err != nil { if err != nil {
return common.Address{}, nil, nil, err return common.Address{}, nil, nil, err
} }
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
} }
{{end}} {{end}}
...@@ -83,6 +90,7 @@ package {{.Package}} ...@@ -83,6 +90,7 @@ package {{.Package}}
type {{.Type}} struct { type {{.Type}} struct {
{{.Type}}Caller // Read-only binding to the contract {{.Type}}Caller // Read-only binding to the contract
{{.Type}}Transactor // Write-only binding to the contract {{.Type}}Transactor // Write-only binding to the contract
{{.Type}}Filterer // Log filterer for contract events
} }
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract. // {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
...@@ -95,6 +103,11 @@ package {{.Package}} ...@@ -95,6 +103,11 @@ package {{.Package}}
contract *bind.BoundContract // Generic contract wrapper for the low level calls contract *bind.BoundContract // Generic contract wrapper for the low level calls
} }
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
type {{.Type}}Filterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract, // {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options. // with pre-set call and transact options.
type {{.Type}}Session struct { type {{.Type}}Session struct {
...@@ -134,16 +147,16 @@ package {{.Package}} ...@@ -134,16 +147,16 @@ package {{.Package}}
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract. // 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) { 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 { if err != nil {
return nil, err return nil, err
} }
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
} }
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract. // 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) { 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -152,20 +165,29 @@ package {{.Package}} ...@@ -152,20 +165,29 @@ package {{.Package}}
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract. // 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) { 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 { if err != nil {
return nil, err return nil, err
} }
return &{{.Type}}Transactor{contract: contract}, nil return &{{.Type}}Transactor{contract: contract}, nil
} }
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
contract, err := bind{{.Type}}(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &{{.Type}}Filterer{contract: contract}, nil
}
// bind{{.Type}} binds a generic wrapper to an already deployed contract. // 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, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return bind.NewBoundContract(address, parsed, caller, transactor), nil return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
} }
// Call invokes the (constant) contract method with params as input values and // Call invokes the (constant) contract method with params as input values and
...@@ -263,6 +285,137 @@ package {{.Package}} ...@@ -263,6 +285,137 @@ package {{.Package}}
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
} }
{{end}} {{end}}
{{range .Events}}
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
// If the iterator failed, stop iterating
if (it.fail != nil) {
return false
}
// If the iterator completed, deliver directly whatever's available
if (it.done) {
select {
case log := <-it.logs:
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}}
Raw types.Log // Blockchain specific contextual infos
}
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
//
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
{{range .Normalized.Inputs}}
{{if .Indexed}}var {{.Name}}Rule []interface{}
for _, {{.Name}}Item := range {{.Name}} {
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
}{{end}}{{end}}
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
if err != nil {
return nil, err
}
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
}
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
//
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (event.Subscription, error) {
{{range .Normalized.Inputs}}
{{if .Indexed}}var {{.Name}}Rule []interface{}
for _, {{.Name}}Item := range {{.Name}} {
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
}{{end}}{{end}}
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new({{$contract.Type}}{{.Normalized.Name}})
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
{{end}}
{{end}} {{end}}
` `
......
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bind
import (
"errors"
"fmt"
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// makeTopics converts a filter query argument list into a filter topic set.
func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topics := make([][]common.Hash, len(query))
for i, filter := range query {
for _, rule := range filter {
var topic common.Hash
// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int16:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int32:
blob := big.NewInt(int64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case int64:
blob := big.NewInt(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])
default:
// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[common.HashLength-val.Len():]), val)
default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
}
topics[i] = append(topics[i], topic)
}
}
return topics, nil
}
// Big batch of reflect types for topic reconstruction.
var (
reflectHash = reflect.TypeOf(common.Hash{})
reflectAddress = reflect.TypeOf(common.Address{})
reflectBigInt = reflect.TypeOf(new(big.Int))
)
// parseTopics converts the indexed topic fields into actual log field values.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for _, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
// Try to parse the topic back into the fields based on primitive types
switch field.Kind() {
case reflect.Bool:
if topics[0][common.HashLength-1] == 1 {
field.Set(reflect.ValueOf(true))
}
case reflect.Int8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int8(num.Int64())))
case reflect.Int16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int16(num.Int64())))
case reflect.Int32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int32(num.Int64())))
case reflect.Int64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Int64()))
case reflect.Uint8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint8(num.Uint64())))
case reflect.Uint16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint16(num.Uint64())))
case reflect.Uint32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint32(num.Uint64())))
case reflect.Uint64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Uint64()))
default:
// Ran out of plain primitive types, try custom types
switch field.Type() {
case reflectHash: // Also covers all dynamic types
field.Set(reflect.ValueOf(topics[0]))
case reflectAddress:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
field.Set(reflect.ValueOf(addr))
case reflectBigInt:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num))
default:
// Ran out of custom types, try the crazies
switch {
case arg.Type.T == abi.FixedBytesTy:
reflect.Copy(field, reflect.ValueOf(topics[0][common.HashLength-arg.Type.Size:]))
default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
}
}
}
topics = topics[1:]
}
return nil
}
...@@ -33,6 +33,17 @@ type Event struct { ...@@ -33,6 +33,17 @@ type Event struct {
Inputs Arguments Inputs Arguments
} }
func (event Event) String() string {
inputs := make([]string, len(event.Inputs))
for i, input := range event.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
if input.Indexed {
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
}
}
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
}
// Id returns the canonical representation of the event's signature used by the // Id returns the canonical representation of the event's signature used by the
// abi definition to identify event names and types. // abi definition to identify event names and types.
func (e Event) Id() common.Hash { func (e Event) Id() common.Hash {
......
...@@ -18,7 +18,6 @@ package main ...@@ -18,7 +18,6 @@ package main
import ( import (
"bufio" "bufio"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -29,7 +28,6 @@ import ( ...@@ -29,7 +28,6 @@ import (
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/contracts/release"
"github.com/ethereum/go-ethereum/dashboard" "github.com/ethereum/go-ethereum/dashboard"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
...@@ -177,21 +175,6 @@ func makeFullNode(ctx *cli.Context) *node.Node { ...@@ -177,21 +175,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
if cfg.Ethstats.URL != "" { if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
} }
// Add the release oracle service so it boots along with node.
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
config := release.Config{
Oracle: relOracle,
Major: uint32(params.VersionMajor),
Minor: uint32(params.VersionMinor),
Patch: uint32(params.VersionPatch),
}
commit, _ := hex.DecodeString(gitCommit)
copy(config.Commit[:], commit)
return release.NewReleaseService(ctx, config)
}); err != nil {
utils.Fatalf("Failed to register the Geth release oracle service: %v", err)
}
return stack return stack
} }
......
...@@ -7,17 +7,19 @@ import ( ...@@ -7,17 +7,19 @@ import (
"math/big" "math/big"
"strings" "strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
) )
// ChequebookABI is the input ABI used to generate the binding from. // ChequebookABI is the input ABI used to generate the binding from.
const ChequebookABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"sent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"beneficiary\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"sig_v\",\"type\":\"uint8\"},{\"name\":\"sig_r\",\"type\":\"bytes32\"},{\"name\":\"sig_s\",\"type\":\"bytes32\"}],\"name\":\"cash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"deadbeat\",\"type\":\"address\"}],\"name\":\"Overdraft\",\"type\":\"event\"}]" const ChequebookABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"sent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"beneficiary\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"sig_v\",\"type\":\"uint8\"},{\"name\":\"sig_r\",\"type\":\"bytes32\"},{\"name\":\"sig_s\",\"type\":\"bytes32\"}],\"name\":\"cash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"deadbeat\",\"type\":\"address\"}],\"name\":\"Overdraft\",\"type\":\"event\"}]"
// ChequebookBin is the compiled bytecode used for deploying new contracts. // ChequebookBin is the compiled bytecode used for deploying new contracts.
const ChequebookBin = `0x606060405260008054600160a060020a033316600160a060020a03199091161790556102ec806100306000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a7230582014e927522ca5cd8f68529ac4d3b9cdf36d40e09d8a33b70008248d1abebf79680029` const ChequebookBin = `0x606060405260008054600160a060020a033316600160a060020a03199091161790556102ec806100306000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029`
// DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it. // DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it.
func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) { func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) {
...@@ -29,13 +31,14 @@ func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (co ...@@ -29,13 +31,14 @@ func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (co
if err != nil { if err != nil {
return common.Address{}, nil, nil, err return common.Address{}, nil, nil, err
} }
return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil
} }
// Chequebook is an auto generated Go binding around an Ethereum contract. // Chequebook is an auto generated Go binding around an Ethereum contract.
type Chequebook struct { type Chequebook struct {
ChequebookCaller // Read-only binding to the contract ChequebookCaller // Read-only binding to the contract
ChequebookTransactor // Write-only binding to the contract ChequebookTransactor // Write-only binding to the contract
ChequebookFilterer // Log filterer for contract events
} }
// ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract. // ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract.
...@@ -48,6 +51,11 @@ type ChequebookTransactor struct { ...@@ -48,6 +51,11 @@ type ChequebookTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls contract *bind.BoundContract // Generic contract wrapper for the low level calls
} }
// ChequebookFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type ChequebookFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// ChequebookSession is an auto generated Go binding around an Ethereum contract, // ChequebookSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options. // with pre-set call and transact options.
type ChequebookSession struct { type ChequebookSession struct {
...@@ -87,16 +95,16 @@ type ChequebookTransactorRaw struct { ...@@ -87,16 +95,16 @@ type ChequebookTransactorRaw struct {
// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract. // NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract.
func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) { func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) {
contract, err := bindChequebook(address, backend, backend) contract, err := bindChequebook(address, backend, backend, backend)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil
} }
// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract. // NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract.
func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) { func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) {
contract, err := bindChequebook(address, caller, nil) contract, err := bindChequebook(address, caller, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -105,20 +113,29 @@ func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*C ...@@ -105,20 +113,29 @@ func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*C
// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract. // NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract.
func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) { func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) {
contract, err := bindChequebook(address, nil, transactor) contract, err := bindChequebook(address, nil, transactor, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ChequebookTransactor{contract: contract}, nil return &ChequebookTransactor{contract: contract}, nil
} }
// NewChequebookFilterer creates a new log filterer instance of Chequebook, bound to a specific deployed contract.
func NewChequebookFilterer(address common.Address, filterer bind.ContractFilterer) (*ChequebookFilterer, error) {
contract, err := bindChequebook(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &ChequebookFilterer{contract: contract}, nil
}
// bindChequebook binds a generic wrapper to an already deployed contract. // bindChequebook binds a generic wrapper to an already deployed contract.
func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) parsed, err := abi.JSON(strings.NewReader(ChequebookABI))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return bind.NewBoundContract(address, parsed, caller, transactor), nil return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
} }
// Call invokes the (constant) contract method with params as input values and // Call invokes the (constant) contract method with params as input values and
...@@ -226,3 +243,125 @@ func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) { ...@@ -226,3 +243,125 @@ func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) {
func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) { func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) {
return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts)
} }
// ChequebookOverdraftIterator is returned from FilterOverdraft and is used to iterate over the raw logs and unpacked data for Overdraft events raised by the Chequebook contract.
type ChequebookOverdraftIterator struct {
Event *ChequebookOverdraft // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *ChequebookOverdraftIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(ChequebookOverdraft)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(ChequebookOverdraft)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error retruned any retrieval or parsing error occurred during filtering.
func (it *ChequebookOverdraftIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *ChequebookOverdraftIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// ChequebookOverdraft represents a Overdraft event raised by the Chequebook contract.
type ChequebookOverdraft struct {
Deadbeat common.Address
Raw types.Log // Blockchain specific contextual infos
}
// FilterOverdraft is a free log retrieval operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978.
//
// Solidity: event Overdraft(deadbeat address)
func (_Chequebook *ChequebookFilterer) FilterOverdraft(opts *bind.FilterOpts) (*ChequebookOverdraftIterator, error) {
logs, sub, err := _Chequebook.contract.FilterLogs(opts, "Overdraft")
if err != nil {
return nil, err
}
return &ChequebookOverdraftIterator{contract: _Chequebook.contract, event: "Overdraft", logs: logs, sub: sub}, nil
}
// WatchOverdraft is a free log subscription operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978.
//
// Solidity: event Overdraft(deadbeat address)
func (_Chequebook *ChequebookFilterer) WatchOverdraft(opts *bind.WatchOpts, sink chan<- *ChequebookOverdraft) (event.Subscription, error) {
logs, sub, err := _Chequebook.contract.WatchLogs(opts, "Overdraft")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(ChequebookOverdraft)
if err := _Chequebook.contract.UnpackLog(event, "Overdraft", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
...@@ -2,4 +2,4 @@ package contract ...@@ -2,4 +2,4 @@ package contract
// ContractDeployedCode is used to detect suicides. This constant needs to be // ContractDeployedCode is used to detect suicides. This constant needs to be
// updated when the contract code is changed. // updated when the contract code is changed.
const ContractDeployedCode = "0x6060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a7230582014e927522ca5cd8f68529ac4d3b9cdf36d40e09d8a33b70008248d1abebf79680029" const ContractDeployedCode = "0x6060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029"
此差异已折叠。
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
const FIFSRegistrarABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"subnode\",\"type\":\"bytes32\"},{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"register\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"},{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" const FIFSRegistrarABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"subnode\",\"type\":\"bytes32\"},{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"register\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"},{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]"
// FIFSRegistrarBin is the compiled bytecode used for deploying new contracts. // FIFSRegistrarBin is the compiled bytecode used for deploying new contracts.
const FIFSRegistrarBin = `0x6060604052341561000f57600080fd5b604051604080610224833981016040528080519190602001805160008054600160a060020a03909516600160a060020a03199095169490941790935550506001556101c58061005f6000396000f3006060604052600436106100275763ffffffff60e060020a600035041663d22057a9811461002c575b600080fd5b341561003757600080fd5b61004e600435600160a060020a0360243516610050565b005b816000806001548360405191825260208201526040908101905190819003902060008054919350600160a060020a03909116906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156100c857600080fd5b6102c65a03f115156100d957600080fd5b5050506040518051915050600160a060020a0381161580159061010e575033600160a060020a031681600160a060020a031614155b1561011857600080fd5b600054600154600160a060020a03909116906306ab592390878760405160e060020a63ffffffff861602815260048101939093526024830191909152600160a060020a03166044820152606401600060405180830381600087803b151561017e57600080fd5b6102c65a03f1151561018f57600080fd5b50505050505050505600a165627a7a723058209b0c0f4ed76e4fe49a71d4b838ab3d00d6bad29021172db7ced9f36abcafbf510029` const FIFSRegistrarBin = `0x6060604052341561000f57600080fd5b604051604080610224833981016040528080519190602001805160008054600160a060020a03909516600160a060020a03199095169490941790935550506001556101c58061005f6000396000f3006060604052600436106100275763ffffffff60e060020a600035041663d22057a9811461002c575b600080fd5b341561003757600080fd5b61004e600435600160a060020a0360243516610050565b005b816000806001548360405191825260208201526040908101905190819003902060008054919350600160a060020a03909116906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156100c857600080fd5b6102c65a03f115156100d957600080fd5b5050506040518051915050600160a060020a0381161580159061010e575033600160a060020a031681600160a060020a031614155b1561011857600080fd5b600054600154600160a060020a03909116906306ab592390878760405160e060020a63ffffffff861602815260048101939093526024830191909152600160a060020a03166044820152606401600060405180830381600087803b151561017e57600080fd5b6102c65a03f1151561018f57600080fd5b50505050505050505600a165627a7a723058206fb963cb168d5e3a51af12cd6bb23e324dbd32dd4954f43653ba27e66b68ea650029`
// DeployFIFSRegistrar deploys a new Ethereum contract, binding an instance of FIFSRegistrar to it. // DeployFIFSRegistrar deploys a new Ethereum contract, binding an instance of FIFSRegistrar to it.
func DeployFIFSRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend, ensAddr common.Address, node [32]byte) (common.Address, *types.Transaction, *FIFSRegistrar, error) { func DeployFIFSRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend, ensAddr common.Address, node [32]byte) (common.Address, *types.Transaction, *FIFSRegistrar, error) {
...@@ -28,13 +28,14 @@ func DeployFIFSRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend, ...@@ -28,13 +28,14 @@ func DeployFIFSRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend,
if err != nil { if err != nil {
return common.Address{}, nil, nil, err return common.Address{}, nil, nil, err
} }
return address, tx, &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}}, nil return address, tx, &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}, FIFSRegistrarFilterer: FIFSRegistrarFilterer{contract: contract}}, nil
} }
// FIFSRegistrar is an auto generated Go binding around an Ethereum contract. // FIFSRegistrar is an auto generated Go binding around an Ethereum contract.
type FIFSRegistrar struct { type FIFSRegistrar struct {
FIFSRegistrarCaller // Read-only binding to the contract FIFSRegistrarCaller // Read-only binding to the contract
FIFSRegistrarTransactor // Write-only binding to the contract FIFSRegistrarTransactor // Write-only binding to the contract
FIFSRegistrarFilterer // Log filterer for contract events
} }
// FIFSRegistrarCaller is an auto generated read-only Go binding around an Ethereum contract. // FIFSRegistrarCaller is an auto generated read-only Go binding around an Ethereum contract.
...@@ -47,6 +48,11 @@ type FIFSRegistrarTransactor struct { ...@@ -47,6 +48,11 @@ type FIFSRegistrarTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls contract *bind.BoundContract // Generic contract wrapper for the low level calls
} }
// FIFSRegistrarFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type FIFSRegistrarFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// FIFSRegistrarSession is an auto generated Go binding around an Ethereum contract, // FIFSRegistrarSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options. // with pre-set call and transact options.
type FIFSRegistrarSession struct { type FIFSRegistrarSession struct {
...@@ -86,16 +92,16 @@ type FIFSRegistrarTransactorRaw struct { ...@@ -86,16 +92,16 @@ type FIFSRegistrarTransactorRaw struct {
// NewFIFSRegistrar creates a new instance of FIFSRegistrar, bound to a specific deployed contract. // NewFIFSRegistrar creates a new instance of FIFSRegistrar, bound to a specific deployed contract.
func NewFIFSRegistrar(address common.Address, backend bind.ContractBackend) (*FIFSRegistrar, error) { func NewFIFSRegistrar(address common.Address, backend bind.ContractBackend) (*FIFSRegistrar, error) {
contract, err := bindFIFSRegistrar(address, backend, backend) contract, err := bindFIFSRegistrar(address, backend, backend, backend)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}}, nil return &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}, FIFSRegistrarFilterer: FIFSRegistrarFilterer{contract: contract}}, nil
} }
// NewFIFSRegistrarCaller creates a new read-only instance of FIFSRegistrar, bound to a specific deployed contract. // NewFIFSRegistrarCaller creates a new read-only instance of FIFSRegistrar, bound to a specific deployed contract.
func NewFIFSRegistrarCaller(address common.Address, caller bind.ContractCaller) (*FIFSRegistrarCaller, error) { func NewFIFSRegistrarCaller(address common.Address, caller bind.ContractCaller) (*FIFSRegistrarCaller, error) {
contract, err := bindFIFSRegistrar(address, caller, nil) contract, err := bindFIFSRegistrar(address, caller, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -104,20 +110,29 @@ func NewFIFSRegistrarCaller(address common.Address, caller bind.ContractCaller) ...@@ -104,20 +110,29 @@ func NewFIFSRegistrarCaller(address common.Address, caller bind.ContractCaller)
// NewFIFSRegistrarTransactor creates a new write-only instance of FIFSRegistrar, bound to a specific deployed contract. // NewFIFSRegistrarTransactor creates a new write-only instance of FIFSRegistrar, bound to a specific deployed contract.
func NewFIFSRegistrarTransactor(address common.Address, transactor bind.ContractTransactor) (*FIFSRegistrarTransactor, error) { func NewFIFSRegistrarTransactor(address common.Address, transactor bind.ContractTransactor) (*FIFSRegistrarTransactor, error) {
contract, err := bindFIFSRegistrar(address, nil, transactor) contract, err := bindFIFSRegistrar(address, nil, transactor, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &FIFSRegistrarTransactor{contract: contract}, nil return &FIFSRegistrarTransactor{contract: contract}, nil
} }
// NewFIFSRegistrarFilterer creates a new log filterer instance of FIFSRegistrar, bound to a specific deployed contract.
func NewFIFSRegistrarFilterer(address common.Address, filterer bind.ContractFilterer) (*FIFSRegistrarFilterer, error) {
contract, err := bindFIFSRegistrar(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &FIFSRegistrarFilterer{contract: contract}, nil
}
// bindFIFSRegistrar binds a generic wrapper to an already deployed contract. // bindFIFSRegistrar binds a generic wrapper to an already deployed contract.
func bindFIFSRegistrar(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { func bindFIFSRegistrar(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(FIFSRegistrarABI)) parsed, err := abi.JSON(strings.NewReader(FIFSRegistrarABI))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return bind.NewBoundContract(address, parsed, caller, transactor), nil return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
} }
// Call invokes the (constant) contract method with params as input values and // Call invokes the (constant) contract method with params as input values and
......
此差异已折叠。
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.4.18;
// ReleaseOracle is an Ethereum contract to store the current and previous
// versions of the go-ethereum implementation. Its goal is to allow Geth to
// check for new releases automatically without the need to consult a central
// repository.
//
// The contract takes a vote based approach on both assigning authorised signers
// as well as signing off on new Geth releases.
//
// Note, when a signer is demoted, the currently pending release is auto-nuked.
// The reason is to prevent suprises where a demotion actually tilts the votes
// in favor of one voter party and pushing out a new release as a consequence of
// a simple demotion.
contract ReleaseOracle {
// Votes is an internal data structure to count votes on a specific proposal
struct Votes {
address[] pass; // List of signers voting to pass a proposal
address[] fail; // List of signers voting to fail a proposal
}
// Version is the version details of a particular Geth release
struct Version {
uint32 major; // Major version component of the release
uint32 minor; // Minor version component of the release
uint32 patch; // Patch version component of the release
bytes20 commit; // Git SHA1 commit hash of the release
uint64 time; // Timestamp of the release approval
Votes votes; // Votes that passed this release
}
// Oracle authorization details
mapping(address => bool) authorised; // Set of accounts allowed to vote on updating the contract
address[] voters; // List of addresses currently accepted as signers
// Various proposals being voted on
mapping(address => Votes) authProps; // Currently running user authorization proposals
address[] authPend; // List of addresses being voted on (map indexes)
Version verProp; // Currently proposed release being voted on
Version[] releases; // All the positively voted releases
// isSigner is a modifier to authorize contract transactions.
modifier isSigner() {
if (authorised[msg.sender]) {
_;
}
}
// Constructor to assign the initial set of signers.
function ReleaseOracle(address[] signers) {
// If no signers were specified, assign the creator as the sole signer
if (signers.length == 0) {
authorised[msg.sender] = true;
voters.push(msg.sender);
return;
}
// Otherwise assign the individual signers one by one
for (uint i = 0; i < signers.length; i++) {
authorised[signers[i]] = true;
voters.push(signers[i]);
}
}
// signers is an accessor method to retrieve all the signers (public accessor
// generates an indexed one, not a retrieve-all version).
function signers() constant returns(address[]) {
return voters;
}
// authProposals retrieves the list of addresses that authorization proposals
// are currently being voted on.
function authProposals() constant returns(address[]) {
return authPend;
}
// authVotes retrieves the current authorization votes for a particular user
// to promote him into the list of signers, or demote him from there.
function authVotes(address user) constant returns(address[] promote, address[] demote) {
return (authProps[user].pass, authProps[user].fail);
}
// currentVersion retrieves the semantic version, commit hash and release time
// of the currently votec active release.
function currentVersion() constant returns (uint32 major, uint32 minor, uint32 patch, bytes20 commit, uint time) {
if (releases.length == 0) {
return (0, 0, 0, 0, 0);
}
var release = releases[releases.length - 1];
return (release.major, release.minor, release.patch, release.commit, release.time);
}
// proposedVersion retrieves the semantic version, commit hash and the current
// votes for the next proposed release.
function proposedVersion() constant returns (uint32 major, uint32 minor, uint32 patch, bytes20 commit, address[] pass, address[] fail) {
return (verProp.major, verProp.minor, verProp.patch, verProp.commit, verProp.votes.pass, verProp.votes.fail);
}
// promote pitches in on a voting campaign to promote a new user to a signer
// position.
function promote(address user) {
updateSigner(user, true);
}
// demote pitches in on a voting campaign to demote an authorised user from
// its signer position.
function demote(address user) {
updateSigner(user, false);
}
// release votes for a particular version to be included as the next release.
function release(uint32 major, uint32 minor, uint32 patch, bytes20 commit) {
updateRelease(major, minor, patch, commit, true);
}
// nuke votes for the currently proposed version to not be included as the next
// release. Nuking doesn't require a specific version number for simplicity.
function nuke() {
updateRelease(0, 0, 0, 0, false);
}
// updateSigner marks a vote for changing the status of an Ethereum user, either
// for or against the user being an authorised signer.
function updateSigner(address user, bool authorize) internal isSigner {
// Gather the current votes and ensure we don't double vote
Votes votes = authProps[user];
for (uint i = 0; i < votes.pass.length; i++) {
if (votes.pass[i] == msg.sender) {
return;
}
}
for (i = 0; i < votes.fail.length; i++) {
if (votes.fail[i] == msg.sender) {
return;
}
}
// If no authorization proposal is open, add the user to the index for later lookups
if (votes.pass.length == 0 && votes.fail.length == 0) {
authPend.push(user);
}
// Cast the vote and return if the proposal cannot be resolved yet
if (authorize) {
votes.pass.push(msg.sender);
if (votes.pass.length <= voters.length / 2) {
return;
}
} else {
votes.fail.push(msg.sender);
if (votes.fail.length <= voters.length / 2) {
return;
}
}
// Proposal resolved in our favor, execute whatever we voted on
if (authorize && !authorised[user]) {
authorised[user] = true;
voters.push(user);
} else if (!authorize && authorised[user]) {
authorised[user] = false;
for (i = 0; i < voters.length; i++) {
if (voters[i] == user) {
voters[i] = voters[voters.length - 1];
voters.length--;
delete verProp; // Nuke any version proposal (no surprise releases!)
break;
}
}
}
// Finally delete the resolved proposal, index and garbage collect
delete authProps[user];
for (i = 0; i < authPend.length; i++) {
if (authPend[i] == user) {
authPend[i] = authPend[authPend.length - 1];
authPend.length--;
break;
}
}
}
// updateRelease votes for a particular version to be included as the next release,
// or for the currently proposed release to be nuked out.
function updateRelease(uint32 major, uint32 minor, uint32 patch, bytes20 commit, bool release) internal isSigner {
// Skip nuke votes if no proposal is pending
if (!release && verProp.votes.pass.length == 0) {
return;
}
// Mark a new release if no proposal is pending
if (verProp.votes.pass.length == 0) {
verProp.major = major;
verProp.minor = minor;
verProp.patch = patch;
verProp.commit = commit;
}
// Make sure positive votes match the current proposal
if (release && (verProp.major != major || verProp.minor != minor || verProp.patch != patch || verProp.commit != commit)) {
return;
}
// Gather the current votes and ensure we don't double vote
Votes votes = verProp.votes;
for (uint i = 0; i < votes.pass.length; i++) {
if (votes.pass[i] == msg.sender) {
return;
}
}
for (i = 0; i < votes.fail.length; i++) {
if (votes.fail[i] == msg.sender) {
return;
}
}
// Cast the vote and return if the proposal cannot be resolved yet
if (release) {
votes.pass.push(msg.sender);
if (votes.pass.length <= voters.length / 2) {
return;
}
} else {
votes.fail.push(msg.sender);
if (votes.fail.length <= voters.length / 2) {
return;
}
}
// Proposal resolved in our favor, execute whatever we voted on
if (release) {
verProp.time = uint64(now);
releases.push(verProp);
delete verProp;
} else {
delete verProp;
}
}
}
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package release
import (
"crypto/ecdsa"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
)
// setupReleaseTest creates a blockchain simulator and deploys a version oracle
// contract for testing.
func setupReleaseTest(t *testing.T, prefund ...*ecdsa.PrivateKey) (*ecdsa.PrivateKey, *ReleaseOracle, *backends.SimulatedBackend) {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
alloc := core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}
for _, key := range prefund {
alloc[crypto.PubkeyToAddress(key.PublicKey)] = core.GenesisAccount{Balance: big.NewInt(10000000000)}
}
sim := backends.NewSimulatedBackend(alloc)
// Deploy a version oracle contract, commit and return
_, _, oracle, err := DeployReleaseOracle(auth, sim, []common.Address{auth.From})
if err != nil {
t.Fatalf("Failed to deploy version contract: %v", err)
}
sim.Commit()
return key, oracle, sim
}
// Tests that the version contract can be deployed and the creator is assigned
// the sole authorized signer.
func TestContractCreation(t *testing.T) {
key, oracle, _ := setupReleaseTest(t)
owner := crypto.PubkeyToAddress(key.PublicKey)
signers, err := oracle.Signers(nil)
if err != nil {
t.Fatalf("Failed to retrieve list of signers: %v", err)
}
if len(signers) != 1 || signers[0] != owner {
t.Fatalf("Initial signer mismatch: have %v, want %v", signers, owner)
}
}
// Tests that subsequent signers can be promoted, each requiring half plus one
// votes for it to pass through.
func TestSignerPromotion(t *testing.T) {
// Prefund a few accounts to authorize with and create the oracle
keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
}
key, oracle, sim := setupReleaseTest(t, keys...)
// Gradually promote the keys, until all are authorized
keys = append([]*ecdsa.PrivateKey{key}, keys...)
for i := 1; i < len(keys); i++ {
// Check that no votes are accepted from the not yet authorized user
if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[i]), common.Address{}); err != nil {
t.Fatalf("Iter #%d: failed invalid promotion attempt: %v", i, err)
}
sim.Commit()
pend, err := oracle.AuthProposals(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve active proposals: %v", i, err)
}
if len(pend) != 0 {
t.Fatalf("Iter #%d: proposal count mismatch: have %d, want 0", i, len(pend))
}
// Promote with half - 1 voters and check that the user's not yet authorized
for j := 0; j < i/2; j++ {
if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err)
}
}
sim.Commit()
signers, err := oracle.Signers(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", i, err)
}
if len(signers) != i {
t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", i, len(signers), i)
}
// Promote with the last one needed to pass the promotion
if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[i/2]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion completion attempt: %v", i, err)
}
sim.Commit()
signers, err = oracle.Signers(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", i, err)
}
if len(signers) != i+1 {
t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", i, len(signers), i+1)
}
}
}
// Tests that subsequent signers can be demoted, each requiring half plus one
// votes for it to pass through.
func TestSignerDemotion(t *testing.T) {
// Prefund a few accounts to authorize with and create the oracle
keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
}
key, oracle, sim := setupReleaseTest(t, keys...)
// Authorize all the keys as valid signers and verify cardinality
keys = append([]*ecdsa.PrivateKey{key}, keys...)
for i := 1; i < len(keys); i++ {
for j := 0; j <= i/2; j++ {
if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err)
}
}
sim.Commit()
}
signers, err := oracle.Signers(nil)
if err != nil {
t.Fatalf("Failed to retrieve list of signers: %v", err)
}
if len(signers) != len(keys) {
t.Fatalf("Signer count mismatch: have %v, want %v", len(signers), len(keys))
}
// Gradually demote users until we run out of signers
for i := len(keys) - 1; i >= 0; i-- {
// Demote with half - 1 voters and check that the user's not yet dropped
for j := 0; j < (i+1)/2; j++ {
if _, err = oracle.Demote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid demotion attempt: %v", len(keys)-i, err)
}
}
sim.Commit()
signers, err := oracle.Signers(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", len(keys)-i, err)
}
if len(signers) != i+1 {
t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", len(keys)-i, len(signers), i+1)
}
// Demote with the last one needed to pass the demotion
if _, err = oracle.Demote(bind.NewKeyedTransactor(keys[(i+1)/2]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid demotion completion attempt: %v", i, err)
}
sim.Commit()
signers, err = oracle.Signers(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve list of signers: %v", len(keys)-i, err)
}
if len(signers) != i {
t.Fatalf("Iter #%d: signer count mismatch: have %v, want %v", len(keys)-i, len(signers), i)
}
// Check that no votes are accepted from the already demoted users
if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[i]), common.Address{}); err != nil {
t.Fatalf("Iter #%d: failed invalid promotion attempt: %v", i, err)
}
sim.Commit()
pend, err := oracle.AuthProposals(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve active proposals: %v", i, err)
}
if len(pend) != 0 {
t.Fatalf("Iter #%d: proposal count mismatch: have %d, want 0", i, len(pend))
}
}
}
// Tests that new versions can be released, honouring both voting rights as well
// as the minimum required vote count.
func TestVersionRelease(t *testing.T) {
// Prefund a few accounts to authorize with and create the oracle
keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
}
key, oracle, sim := setupReleaseTest(t, keys...)
// Track the "current release"
var (
verMajor = uint32(0)
verMinor = uint32(0)
verPatch = uint32(0)
verCommit = [20]byte{}
)
// Gradually push releases, always requiring more signers than previously
keys = append([]*ecdsa.PrivateKey{key}, keys...)
for i := 1; i < len(keys); i++ {
// Check that no votes are accepted from the not yet authorized user
if _, err := oracle.Release(bind.NewKeyedTransactor(keys[i]), 0, 0, 0, [20]byte{0}); err != nil {
t.Fatalf("Iter #%d: failed invalid release attempt: %v", i, err)
}
sim.Commit()
prop, err := oracle.ProposedVersion(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err)
}
if len(prop.Pass) != 0 {
t.Fatalf("Iter #%d: proposal vote count mismatch: have %d, want 0", i, len(prop.Pass))
}
// Authorize the user to make releases
for j := 0; j <= i/2; j++ {
if _, err = oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err)
}
}
sim.Commit()
// Propose release with half voters and check that the release does not yet go through
for j := 0; j < (i+1)/2; j++ {
if _, err = oracle.Release(bind.NewKeyedTransactor(keys[j]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{byte(i + 3)}); err != nil {
t.Fatalf("Iter #%d: failed valid release attempt: %v", i, err)
}
}
sim.Commit()
ver, err := oracle.CurrentVersion(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve current version: %v", i, err)
}
if ver.Major != verMajor || ver.Minor != verMinor || ver.Patch != verPatch || ver.Commit != verCommit {
t.Fatalf("Iter #%d: version mismatch: have %d.%d.%d-%x, want %d.%d.%d-%x", i, ver.Major, ver.Minor, ver.Patch, ver.Commit, verMajor, verMinor, verPatch, verCommit)
}
// Pass the release and check that it became the next version
verMajor, verMinor, verPatch, verCommit = uint32(i), uint32(i+1), uint32(i+2), [20]byte{byte(i + 3)}
if _, err = oracle.Release(bind.NewKeyedTransactor(keys[(i+1)/2]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{byte(i + 3)}); err != nil {
t.Fatalf("Iter #%d: failed valid release completion attempt: %v", i, err)
}
sim.Commit()
ver, err = oracle.CurrentVersion(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve current version: %v", i, err)
}
if ver.Major != verMajor || ver.Minor != verMinor || ver.Patch != verPatch || ver.Commit != verCommit {
t.Fatalf("Iter #%d: version mismatch: have %d.%d.%d-%x, want %d.%d.%d-%x", i, ver.Major, ver.Minor, ver.Patch, ver.Commit, verMajor, verMinor, verPatch, verCommit)
}
}
}
// Tests that proposed versions can be nuked out of existence.
func TestVersionNuking(t *testing.T) {
// Prefund a few accounts to authorize with and create the oracle
keys := make([]*ecdsa.PrivateKey, 9)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
}
key, oracle, sim := setupReleaseTest(t, keys...)
// Authorize all the keys as valid signers
keys = append([]*ecdsa.PrivateKey{key}, keys...)
for i := 1; i < len(keys); i++ {
for j := 0; j <= i/2; j++ {
if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err)
}
}
sim.Commit()
}
// Propose releases with more and more keys, always retaining enough users to nuke the proposals
for i := 1; i < (len(keys)+1)/2; i++ {
// Propose release with an initial set of signers
for j := 0; j < i; j++ {
if _, err := oracle.Release(bind.NewKeyedTransactor(keys[j]), uint32(i), uint32(i+1), uint32(i+2), [20]byte{byte(i + 3)}); err != nil {
t.Fatalf("Iter #%d: failed valid proposal attempt: %v", i, err)
}
}
sim.Commit()
prop, err := oracle.ProposedVersion(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err)
}
if len(prop.Pass) != i {
t.Fatalf("Iter #%d: proposal vote count mismatch: have %d, want %d", i, len(prop.Pass), i)
}
// Nuke the release with half+1 voters
for j := i; j <= i+(len(keys)+1)/2; j++ {
if _, err := oracle.Nuke(bind.NewKeyedTransactor(keys[j])); err != nil {
t.Fatalf("Iter #%d: failed valid nuke attempt: %v", i, err)
}
}
sim.Commit()
prop, err = oracle.ProposedVersion(nil)
if err != nil {
t.Fatalf("Iter #%d: failed to retrieve active proposal: %v", i, err)
}
if len(prop.Pass) != 0 || len(prop.Fail) != 0 {
t.Fatalf("Iter #%d: proposal vote count mismatch: have %d/%d pass/fail, want 0/0", i, len(prop.Pass), len(prop.Fail))
}
}
}
// Tests that demoting a signer will auto-nuke the currently pending release.
func TestVersionAutoNuke(t *testing.T) {
// Prefund a few accounts to authorize with and create the oracle
keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
}
key, oracle, sim := setupReleaseTest(t, keys...)
// Authorize all the keys as valid signers
keys = append([]*ecdsa.PrivateKey{key}, keys...)
for i := 1; i < len(keys); i++ {
for j := 0; j <= i/2; j++ {
if _, err := oracle.Promote(bind.NewKeyedTransactor(keys[j]), crypto.PubkeyToAddress(keys[i].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid promotion attempt: %v", i, err)
}
}
sim.Commit()
}
// Make a release proposal and check it's existence
if _, err := oracle.Release(bind.NewKeyedTransactor(keys[0]), 1, 2, 3, [20]byte{4}); err != nil {
t.Fatalf("Failed valid proposal attempt: %v", err)
}
sim.Commit()
prop, err := oracle.ProposedVersion(nil)
if err != nil {
t.Fatalf("Failed to retrieve active proposal: %v", err)
}
if len(prop.Pass) != 1 {
t.Fatalf("Proposal vote count mismatch: have %d, want 1", len(prop.Pass))
}
// Demote a signer and check release proposal deletion
for i := 0; i <= len(keys)/2; i++ {
if _, err := oracle.Demote(bind.NewKeyedTransactor(keys[i]), crypto.PubkeyToAddress(keys[len(keys)-1].PublicKey)); err != nil {
t.Fatalf("Iter #%d: failed valid demotion attempt: %v", i, err)
}
}
sim.Commit()
prop, err = oracle.ProposedVersion(nil)
if err != nil {
t.Fatalf("Failed to retrieve active proposal: %v", err)
}
if len(prop.Pass) != 0 {
t.Fatalf("Proposal vote count mismatch: have %d, want 0", len(prop.Pass))
}
}
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package release contains the node service that tracks client releases.
package release
//go:generate abigen --sol ./contract.sol --pkg release --out ./contract.go
import (
"context"
"fmt"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
)
// Interval to check for new releases
const releaseRecheckInterval = time.Hour
// Config contains the configurations of the release service.
type Config struct {
Oracle common.Address // Ethereum address of the release oracle
Major uint32 // Major version component of the release
Minor uint32 // Minor version component of the release
Patch uint32 // Patch version component of the release
Commit [20]byte // Git SHA1 commit hash of the release
}
// ReleaseService is a node service that periodically checks the blockchain for
// newly released versions of the client being run and issues a warning to the
// user about it.
type ReleaseService struct {
config Config // Current version to check releases against
oracle *ReleaseOracle // Native binding to the release oracle contract
quit chan chan error // Quit channel to terminate the version checker
}
// NewReleaseService creates a new service to periodically check for new client
// releases and notify the user of such.
func NewReleaseService(ctx *node.ServiceContext, config Config) (node.Service, error) {
// Retrieve the Ethereum service dependency to access the blockchain
var apiBackend ethapi.Backend
var ethereum *eth.Ethereum
if err := ctx.Service(&ethereum); err == nil {
apiBackend = ethereum.ApiBackend
} else {
var ethereum *les.LightEthereum
if err := ctx.Service(&ethereum); err == nil {
apiBackend = ethereum.ApiBackend
} else {
return nil, err
}
}
// Construct the release service
contract, err := NewReleaseOracle(config.Oracle, eth.NewContractBackend(apiBackend))
if err != nil {
return nil, err
}
return &ReleaseService{
config: config,
oracle: contract,
quit: make(chan chan error),
}, nil
}
// Protocols returns an empty list of P2P protocols as the release service does
// not have a networking component.
func (r *ReleaseService) Protocols() []p2p.Protocol { return nil }
// APIs returns an empty list of RPC descriptors as the release service does not
// expose any functioanlity to the outside world.
func (r *ReleaseService) APIs() []rpc.API { return nil }
// Start spawns the periodic version checker goroutine
func (r *ReleaseService) Start(server *p2p.Server) error {
go r.checker()
return nil
}
// Stop terminates all goroutines belonging to the service, blocking until they
// are all terminated.
func (r *ReleaseService) Stop() error {
errc := make(chan error)
r.quit <- errc
return <-errc
}
// checker runs indefinitely in the background, periodically checking for new
// client releases.
func (r *ReleaseService) checker() {
// Set up the timers to periodically check for releases
timer := time.NewTimer(0) // Immediately fire a version check
defer timer.Stop()
for {
select {
case <-timer.C:
// Rechedule the timer before continuing
timer.Reset(releaseRecheckInterval)
r.checkVersion()
case errc := <-r.quit:
errc <- nil
return
}
}
}
func (r *ReleaseService) checkVersion() {
// Retrieve the current version, and handle missing contracts gracefully
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
opts := &bind.CallOpts{Context: ctx}
defer cancel()
version, err := r.oracle.CurrentVersion(opts)
if err != nil {
if err == bind.ErrNoCode {
log.Debug("Release oracle not found", "contract", r.config.Oracle)
} else if err != les.ErrNoPeers {
log.Error("Failed to retrieve current release", "err", err)
}
return
}
// Version was successfully retrieved, notify if newer than ours
if version.Major > r.config.Major ||
(version.Major == r.config.Major && version.Minor > r.config.Minor) ||
(version.Major == r.config.Major && version.Minor == r.config.Minor && version.Patch > r.config.Patch) {
warning := fmt.Sprintf("Client v%d.%d.%d-%x seems older than the latest upstream release v%d.%d.%d-%x",
r.config.Major, r.config.Minor, r.config.Patch, r.config.Commit[:4], version.Major, version.Minor, version.Patch, version.Commit[:4])
howtofix := fmt.Sprintf("Please check https://github.com/ethereum/go-ethereum/releases for new releases")
separator := strings.Repeat("-", len(warning))
log.Warn(separator)
log.Warn(warning)
log.Warn(howtofix)
log.Warn(separator)
} else {
log.Debug("Client seems up to date with upstream",
"local", fmt.Sprintf("v%d.%d.%d-%x", r.config.Major, r.config.Minor, r.config.Patch, r.config.Commit[:4]),
"upstream", fmt.Sprintf("v%d.%d.%d-%x", version.Major, version.Minor, version.Patch, version.Commit[:4]))
}
}
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"context"
"math/big"
"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/internal/ethapi"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
// ContractBackend implements bind.ContractBackend with direct calls to Ethereum
// internals to support operating on contracts within subprotocols like eth and
// swarm.
//
// Internally this backend uses the already exposed API endpoints of the Ethereum
// object. These should be rewritten to internal Go method calls when the Go API
// is refactored to support a clean library use.
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
}
// NewContractBackend creates a new native contract backend using an existing
// Ethereum object.
func NewContractBackend(apiBackend ethapi.Backend) *ContractBackend {
return &ContractBackend{
eapi: ethapi.NewPublicEthereumAPI(apiBackend),
bcapi: ethapi.NewPublicBlockChainAPI(apiBackend),
txapi: ethapi.NewPublicTransactionPoolAPI(apiBackend, new(ethapi.AddrLocker)),
}
}
// CodeAt retrieves any code associated with the contract from the local API.
func (b *ContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNum *big.Int) ([]byte, error) {
return b.bcapi.GetCode(ctx, contract, toBlockNumber(blockNum))
}
// CodeAt retrieves any code associated with the contract from the local API.
func (b *ContractBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
return b.bcapi.GetCode(ctx, contract, rpc.PendingBlockNumber)
}
// ContractCall implements bind.ContractCaller executing an Ethereum contract
// call with the specified data as the input. The pending flag requests execution
// against the pending block, not the stable head of the chain.
func (b *ContractBackend) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNum *big.Int) ([]byte, error) {
out, err := b.bcapi.Call(ctx, toCallArgs(msg), toBlockNumber(blockNum))
return out, err
}
// ContractCall implements bind.ContractCaller executing an Ethereum contract
// call with the specified data as the input. The pending flag requests execution
// against the pending block, not the stable head of the chain.
func (b *ContractBackend) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
out, err := b.bcapi.Call(ctx, toCallArgs(msg), rpc.PendingBlockNumber)
return out, err
}
func toCallArgs(msg ethereum.CallMsg) ethapi.CallArgs {
args := ethapi.CallArgs{
To: msg.To,
From: msg.From,
Data: msg.Data,
Gas: hexutil.Uint64(msg.Gas),
}
if msg.GasPrice != nil {
args.GasPrice = hexutil.Big(*msg.GasPrice)
}
if msg.Value != nil {
args.Value = hexutil.Big(*msg.Value)
}
return args
}
func toBlockNumber(num *big.Int) rpc.BlockNumber {
if num == nil {
return rpc.LatestBlockNumber
}
return rpc.BlockNumber(num.Int64())
}
// PendingAccountNonce implements bind.ContractTransactor retrieving the current
// pending nonce associated with an account.
func (b *ContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (nonce uint64, err error) {
out, err := b.txapi.GetTransactionCount(ctx, account, rpc.PendingBlockNumber)
if out != nil {
nonce = uint64(*out)
}
return nonce, err
}
// SuggestGasPrice implements bind.ContractTransactor retrieving the currently
// suggested gas price to allow a timely execution of a transaction.
func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
return b.eapi.GasPrice(ctx)
}
// EstimateGasLimit implements bind.ContractTransactor triing to estimate the gas
// needed to execute a specific transaction based on the current pending state of
// the backend blockchain. There is no guarantee that this is the true gas limit
// requirement as other transactions may be added or removed by miners, but it
// should provide a basis for setting a reasonable default.
func (b *ContractBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
gas, err := b.bcapi.EstimateGas(ctx, toCallArgs(msg))
return uint64(gas), err
}
// SendTransaction implements bind.ContractTransactor injects the transaction
// into the pending pool for execution.
func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
raw, _ := rlp.EncodeToBytes(tx)
_, err := b.txapi.SendRawTransaction(ctx, raw)
return err
}
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"sync" "sync"
"time" "time"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -240,7 +241,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc ...@@ -240,7 +241,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
matchedLogs = make(chan []*types.Log) matchedLogs = make(chan []*types.Log)
) )
logsSub, err := api.events.SubscribeLogs(crit, matchedLogs) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -267,6 +268,8 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc ...@@ -267,6 +268,8 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
} }
// FilterCriteria represents a request to create a new filter. // FilterCriteria represents a request to create a new filter.
//
// TODO(karalabe): Kill this in favor of ethereum.FilterQuery.
type FilterCriteria struct { type FilterCriteria struct {
FromBlock *big.Int FromBlock *big.Int
ToBlock *big.Int ToBlock *big.Int
...@@ -289,7 +292,7 @@ type FilterCriteria struct { ...@@ -289,7 +292,7 @@ type FilterCriteria struct {
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
logs := make(chan []*types.Log) logs := make(chan []*types.Log)
logsSub, err := api.events.SubscribeLogs(crit, logs) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs)
if err != nil { if err != nil {
return rpc.ID(""), err return rpc.ID(""), err
} }
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"sync" "sync"
"time" "time"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -75,7 +76,7 @@ type subscription struct { ...@@ -75,7 +76,7 @@ type subscription struct {
id rpc.ID id rpc.ID
typ Type typ Type
created time.Time created time.Time
logsCrit FilterCriteria logsCrit ethereum.FilterQuery
logs chan []*types.Log logs chan []*types.Log
hashes chan common.Hash hashes chan common.Hash
headers chan *types.Header headers chan *types.Header
...@@ -162,7 +163,7 @@ func (es *EventSystem) subscribe(sub *subscription) *Subscription { ...@@ -162,7 +163,7 @@ func (es *EventSystem) subscribe(sub *subscription) *Subscription {
// SubscribeLogs creates a subscription that will write all logs matching the // SubscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel. Default value for the from and to // given criteria to the given logs channel. Default value for the from and to
// block is "latest". If the fromBlock > toBlock an error is returned. // block is "latest". If the fromBlock > toBlock an error is returned.
func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []*types.Log) (*Subscription, error) { func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) {
var from, to rpc.BlockNumber var from, to rpc.BlockNumber
if crit.FromBlock == nil { if crit.FromBlock == nil {
from = rpc.LatestBlockNumber from = rpc.LatestBlockNumber
...@@ -200,7 +201,7 @@ func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []*types.Log ...@@ -200,7 +201,7 @@ func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []*types.Log
// subscribeMinedPendingLogs creates a subscription that returned mined and // subscribeMinedPendingLogs creates a subscription that returned mined and
// pending logs that match the given criteria. // pending logs that match the given criteria.
func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan []*types.Log) *Subscription { func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
sub := &subscription{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: MinedAndPendingLogsSubscription, typ: MinedAndPendingLogsSubscription,
...@@ -217,7 +218,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan ...@@ -217,7 +218,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan
// subscribeLogs creates a subscription that will write all logs matching the // subscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel. // given criteria to the given logs channel.
func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []*types.Log) *Subscription { func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
sub := &subscription{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: LogsSubscription, typ: LogsSubscription,
...@@ -234,7 +235,7 @@ func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []*types.Log ...@@ -234,7 +235,7 @@ func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []*types.Log
// subscribePendingLogs creates a subscription that writes transaction hashes for // subscribePendingLogs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool. // transactions that enter the transaction pool.
func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []*types.Log) *Subscription { func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
sub := &subscription{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: PendingLogsSubscription, typ: PendingLogsSubscription,
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"testing" "testing"
"time" "time"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
...@@ -488,27 +489,27 @@ func TestPendingLogsSubscription(t *testing.T) { ...@@ -488,27 +489,27 @@ func TestPendingLogsSubscription(t *testing.T) {
} }
testCases = []struct { testCases = []struct {
crit FilterCriteria crit ethereum.FilterQuery
expected []*types.Log expected []*types.Log
c chan []*types.Log c chan []*types.Log
sub *Subscription sub *Subscription
}{ }{
// match all // match all
{FilterCriteria{}, convertLogs(allLogs), nil, nil}, {ethereum.FilterQuery{}, convertLogs(allLogs), nil, nil},
// match none due to no matching addresses // match none due to no matching addresses
{FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, nil, nil},
// match logs based on addresses, ignore topics // match logs based on addresses, ignore topics
{FilterCriteria{Addresses: []common.Address{firstAddr}}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{firstAddr}}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil},
// match none due to no matching topics (match with address) // match none due to no matching topics (match with address)
{FilterCriteria{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}}, []*types.Log{}, nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}}, []*types.Log{}, nil, nil},
// match logs based on addresses and topics // match logs based on addresses and topics
{FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, append(convertLogs(allLogs[3:5]), allLogs[5].Logs[0]), nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, append(convertLogs(allLogs[3:5]), allLogs[5].Logs[0]), nil, nil},
// match logs based on multiple addresses and "or" topics // match logs based on multiple addresses and "or" topics
{FilterCriteria{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, append(convertLogs(allLogs[2:5]), allLogs[5].Logs[0]), nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, append(convertLogs(allLogs[2:5]), allLogs[5].Logs[0]), nil, nil},
// block numbers are ignored for filters created with New***Filter, these return all logs that match the given criteria when the state changes // block numbers are ignored for filters created with New***Filter, these return all logs that match the given criteria when the state changes
{FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(2), ToBlock: big.NewInt(3)}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(2), ToBlock: big.NewInt(3)}, append(convertLogs(allLogs[:2]), allLogs[5].Logs[3]), nil, nil},
// multiple pending logs, should match only 2 topics from the logs in block 5 // multiple pending logs, should match only 2 topics from the logs in block 5
{FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}}, []*types.Log{allLogs[5].Logs[0], allLogs[5].Logs[2]}, nil, nil}, {ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}}, []*types.Log{allLogs[5].Logs[0], allLogs[5].Logs[2]}, nil, nil},
} }
) )
......
...@@ -72,7 +72,7 @@ public class AndroidTest extends InstrumentationTestCase { ...@@ -72,7 +72,7 @@ public class AndroidTest extends InstrumentationTestCase {
Transaction tx = new Transaction( Transaction tx = new Transaction(
1, new Address("0x0000000000000000000000000000000000000000"), 1, new Address("0x0000000000000000000000000000000000000000"),
new BigInt(0), new BigInt(0), new BigInt(1), null); // Random empty transaction new BigInt(0), 0, new BigInt(1), null); // Random empty transaction
BigInt chain = new BigInt(1); // Chain identifier of the main net BigInt chain = new BigInt(1); // Chain identifier of the main net
// Sign a transaction with a single authorization // Sign a transaction with a single authorization
...@@ -164,12 +164,17 @@ func TestAndroid(t *testing.T) { ...@@ -164,12 +164,17 @@ func TestAndroid(t *testing.T) {
t.Skip("command gradle not found, skipping") t.Skip("command gradle not found, skipping")
} }
if sdk := os.Getenv("ANDROID_HOME"); sdk == "" { if sdk := os.Getenv("ANDROID_HOME"); sdk == "" {
t.Skip("ANDROID_HOME environment var not set, skipping") // Android SDK not explicitly given, try to auto-resolve
autopath := filepath.Join(os.Getenv("HOME"), "Android", "Sdk")
if _, err := os.Stat(autopath); err != nil {
t.Skip("ANDROID_HOME environment var not set, skipping")
}
os.Setenv("ANDROID_HOME", autopath)
} }
if _, err := exec.Command("which", "gomobile").CombinedOutput(); err != nil { if _, err := exec.Command("which", "gomobile").CombinedOutput(); err != nil {
t.Log("gomobile missing, installing it...") t.Log("gomobile missing, installing it...")
if _, err := exec.Command("go", "install", "golang.org/x/mobile/cmd/gomobile").CombinedOutput(); err != nil { if out, err := exec.Command("go", "get", "golang.org/x/mobile/cmd/gomobile").CombinedOutput(); err != nil {
t.Fatalf("install failed: %v", err) t.Fatalf("install failed: %v\n%s", err, string(out))
} }
t.Log("initializing gomobile...") t.Log("initializing gomobile...")
start := time.Now() start := time.Now()
...@@ -239,7 +244,7 @@ const gradleConfig = `buildscript { ...@@ -239,7 +244,7 @@ const gradleConfig = `buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.5.0' classpath 'com.android.tools.build:gradle:2.2.3'
} }
} }
allprojects { allprojects {
......
...@@ -138,7 +138,7 @@ func BindContract(address *Address, abiJSON string, client *EthereumClient) (con ...@@ -138,7 +138,7 @@ func BindContract(address *Address, abiJSON string, client *EthereumClient) (con
return nil, err return nil, err
} }
return &BoundContract{ 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, address: address.address,
}, nil }, nil
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册