mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
Refactored the subscription with the new unpacking
This commit is contained in:
parent
6fb1523944
commit
4812a4f49e
1 changed files with 85 additions and 88 deletions
|
|
@ -60,9 +60,6 @@ type TransactOpts struct {
|
||||||
// SubscribeOpts is the collection of options to fine tune the contract subscription
|
// SubscribeOpts is the collection of options to fine tune the contract subscription
|
||||||
// and unsubscription requests.
|
// and unsubscription requests.
|
||||||
type SubscribeOpts struct {
|
type SubscribeOpts struct {
|
||||||
History bool // Whether to also retrieve events from the past
|
|
||||||
FromBlock *big.Int // Block at which we want to start retrieving past events
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,17 +84,20 @@ type BoundContract struct {
|
||||||
latestHasCode uint32 // Cached verification that the latest state contains code for this contract
|
latestHasCode uint32 // Cached verification that the latest state contains code for this contract
|
||||||
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
|
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
|
||||||
|
|
||||||
subscriptions map[chan<- vm.Log]ethereum.Subscription
|
subscriptions map[uint32]ethereum.Subscription
|
||||||
errors map[chan<- vm.Log]error
|
errors map[uint32]error
|
||||||
|
nextID uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, backend ContractBackend) *BoundContract {
|
func NewBoundContract(address common.Address, abi abi.ABI, backend ContractBackend) *BoundContract {
|
||||||
return &BoundContract{
|
return &BoundContract{
|
||||||
address: address,
|
address: address,
|
||||||
abi: abi,
|
abi: abi,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
|
subscriptions: make(map[uint32]ethereum.Subscription),
|
||||||
|
errors: make(map[uint32]error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,6 +261,18 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||||
// the given start & end blocks.
|
// the given start & end blocks.
|
||||||
func (c *BoundContract) Events(opts *EventOpts, name string, output interface{}, topics ...[]common.Hash) error {
|
func (c *BoundContract) Events(opts *EventOpts, name string, output interface{}, topics ...[]common.Hash) error {
|
||||||
|
|
||||||
|
// check that output is a pointer
|
||||||
|
ptr := reflect.ValueOf(output)
|
||||||
|
if ptr.Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("need pointer to slice as output parameter, have %T", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that it points to a slice
|
||||||
|
slice := ptr.Elem()
|
||||||
|
if slice.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("need pointer to slice as output parameter, have %T", output)
|
||||||
|
}
|
||||||
|
|
||||||
// get the event so we can encode the name into the first topic
|
// get the event so we can encode the name into the first topic
|
||||||
event, ok := c.abi.Events[name]
|
event, ok := c.abi.Events[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -269,18 +281,6 @@ func (c *BoundContract) Events(opts *EventOpts, name string, output interface{},
|
||||||
names := []common.Hash{event.Id()}
|
names := []common.Hash{event.Id()}
|
||||||
topics = append([][]common.Hash{names}, topics...)
|
topics = append([][]common.Hash{names}, topics...)
|
||||||
|
|
||||||
// check that output is a pointer
|
|
||||||
ptr := reflect.ValueOf(output)
|
|
||||||
if ptr.Kind() != reflect.Ptr {
|
|
||||||
return fmt.Errorf("need pointer to slice as output, have %T", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that it points to a slice
|
|
||||||
slice := ptr.Elem()
|
|
||||||
if slice.Kind() != reflect.Slice {
|
|
||||||
return fmt.Errorf("need pointer to slice as output, have %T", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the filter query and retrieve the events through the backend
|
// create the filter query and retrieve the events through the backend
|
||||||
filterQuery := ethereum.FilterQuery{
|
filterQuery := ethereum.FilterQuery{
|
||||||
FromBlock: opts.FromBlock,
|
FromBlock: opts.FromBlock,
|
||||||
|
|
@ -315,102 +315,99 @@ func (c *BoundContract) Events(opts *EventOpts, name string, output interface{},
|
||||||
|
|
||||||
// Subscribe subscribes to the contract for the given topics. Subscription events
|
// Subscribe subscribes to the contract for the given topics. Subscription events
|
||||||
// will be submitted to the provided channel. Upon cancelation of the subscription,
|
// will be submitted to the provided channel. Upon cancelation of the subscription,
|
||||||
// the channel will be closed. A channel should only be used for ones subscription.
|
// the channel will be closed. A channel should only be used for one subscription.
|
||||||
func (c *BoundContract) Subscribe(opts *SubscribeOpts, name string, channel chan<- vm.Log, topics ...[]common.Hash) error {
|
func (c *BoundContract) Subscribe(opts *SubscribeOpts, name string, output interface{}, topics ...[]common.Hash) (uint32, error) {
|
||||||
|
|
||||||
// check if we already have a subscription on this channel
|
// check that output is a pointer
|
||||||
_, ok := c.subscriptions[channel]
|
channel := reflect.ValueOf(output)
|
||||||
if ok {
|
if channel.Kind() != reflect.Chan {
|
||||||
return fmt.Errorf("cannot reuse channel for multiple subscriptions")
|
return 0, fmt.Errorf("need channel as output parameter, have %T", output)
|
||||||
}
|
}
|
||||||
|
message := channel.Elem()
|
||||||
|
|
||||||
// Build the filter query with our desired options and topics
|
// get the event so we can encode the name into the first topic
|
||||||
|
event, ok := c.abi.Events[name]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unknown event name: %v", name)
|
||||||
|
}
|
||||||
|
names := []common.Hash{event.Id()}
|
||||||
|
topics = append([][]common.Hash{names}, topics...)
|
||||||
|
|
||||||
|
// create the filter query and retrieve the events through the backend
|
||||||
filterQuery := ethereum.FilterQuery{
|
filterQuery := ethereum.FilterQuery{
|
||||||
FromBlock: opts.FromBlock,
|
FromBlock: nil,
|
||||||
ToBlock: nil,
|
ToBlock: nil,
|
||||||
Addresses: []common.Address{c.address},
|
Addresses: []common.Address{c.address},
|
||||||
Topics: topics,
|
Topics: topics,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't want to miss any real-time event logs, we need to subscribe right away
|
// create a channel of log messages that we can decode into events
|
||||||
tempChannel := make(chan vm.Log)
|
logs := make(chan vm.Log)
|
||||||
tempSub, err := c.backend.SubscribeFilterLogs(opts.Context, filterQuery, tempChannel)
|
subscription, err := c.backend.SubscribeFilterLogs(opts.Context, filterQuery, logs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create subscription (%v)", err)
|
return 0, fmt.Errorf("could not subscribe (%v)", err)
|
||||||
}
|
}
|
||||||
|
id := c.nextID
|
||||||
|
c.subscriptions[id] = subscription
|
||||||
|
c.nextID++
|
||||||
|
|
||||||
// Get the desired historical data and feed it into the subscription channel
|
// retrieve logs and transcribe them into typed events
|
||||||
if opts.History {
|
go func() {
|
||||||
var logs []vm.Log
|
Loop:
|
||||||
logs, err = c.backend.FilterLogs(opts.Context, filterQuery)
|
for {
|
||||||
if err != nil {
|
select {
|
||||||
return fmt.Errorf("could not retrieve history of subscription events")
|
case err, ok = <-subscription.Err():
|
||||||
}
|
|
||||||
for _, log := range logs {
|
|
||||||
channel <- log
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feed the real-time events that happenend in the meantime to the subscription channel
|
// error was closed, so we shut down
|
||||||
// and create the new subscription as soon as none are left
|
if !ok {
|
||||||
var subscription ethereum.Subscription
|
break Loop
|
||||||
Feed:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case log := <-tempChannel:
|
|
||||||
channel <- log
|
|
||||||
default:
|
|
||||||
subscription, err = c.backend.SubscribeFilterLogs(opts.Context, filterQuery, channel)
|
|
||||||
tempSub.Unsubscribe()
|
|
||||||
for _ = range tempSub.Err() {
|
|
||||||
}
|
|
||||||
Drain:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tempChannel:
|
|
||||||
default:
|
|
||||||
break Drain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// otherwise just save the error for this subscription
|
||||||
|
c.errors[id] = err
|
||||||
|
|
||||||
|
// receive log and transcribe
|
||||||
|
case log := <-logs:
|
||||||
|
item := reflect.New(message.Type())
|
||||||
|
c.abi.Unpack(item.Interface(), name, log.Data)
|
||||||
|
channel.Send(item)
|
||||||
}
|
}
|
||||||
close(tempChannel)
|
|
||||||
break Feed
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// check if the subscription succeeded
|
// drain the logs channel; this might not always work correctly because we
|
||||||
if err != nil {
|
// are not sure all logs were put in the channel already
|
||||||
return fmt.Errorf("could not create subscription (%v)", err)
|
Drain:
|
||||||
}
|
for {
|
||||||
c.subscriptions[channel] = subscription
|
select {
|
||||||
|
case log := <-logs:
|
||||||
// Read from the error channel in the background
|
item := reflect.New(message.Type())
|
||||||
// When we receive an error, we save it with the channel index
|
c.abi.Unpack(item.Interface(), name, log.Data)
|
||||||
// When the error channel is closed, it indicates an ended subscription
|
channel.Send(item)
|
||||||
// We then close the subscription channel to notify the consumer and remove
|
default:
|
||||||
// the subscription from our map
|
break Drain
|
||||||
go func(errChannel <-chan error) {
|
}
|
||||||
for err := range errChannel {
|
|
||||||
c.errors[channel] = err
|
|
||||||
}
|
}
|
||||||
close(channel)
|
|
||||||
delete(c.subscriptions, channel)
|
// close the output channel
|
||||||
}(subscription.Err())
|
channel.Close()
|
||||||
return nil
|
}()
|
||||||
|
|
||||||
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns the error that was encountered for the subscription on the given
|
// Error returns the error that was encountered for the subscription on the given
|
||||||
// channel. It will reset the error upon retrieval.
|
// channel. It will reset the error upon retrieval.
|
||||||
func (c *BoundContract) Error(channel chan<- vm.Log) error {
|
func (c *BoundContract) Error(id uint32) error {
|
||||||
err := c.errors[channel]
|
err := c.errors[id]
|
||||||
delete(c.errors, channel)
|
delete(c.errors, id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe cancels the subscription if one exists on the given channel. The
|
// Unsubscribe cancels the subscription if one exists on the given channel. The
|
||||||
// subscription channel will be closed once the subscription was successfully
|
// subscription channel will be closed once the subscription was successfully
|
||||||
// canceled.
|
// canceled.
|
||||||
func (c *BoundContract) Unsubscribe(channel chan<- vm.Log) error {
|
func (c *BoundContract) Unsubscribe(id uint32) error {
|
||||||
subscription, ok := c.subscriptions[channel]
|
subscription, ok := c.subscriptions[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("subscription doesn't exist or already closed")
|
return fmt.Errorf("subscription doesn't exist or already closed")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue