Skip to content

Commit

Permalink
Merge pull request #1268 from statechannels/more-queries
Browse files Browse the repository at this point in the history
Add more query functions to the API
  • Loading branch information
lalexgap authored May 11, 2023
2 parents 4fde87e + 79d5878 commit bf9f688
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 60 deletions.
6 changes: 3 additions & 3 deletions channel/consensus_channel/consensus_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func (c *ConsensusChannel) Follower() common.Address {

// FundingTargets returns a list of channels funded by the ConsensusChannel
func (c *ConsensusChannel) FundingTargets() []types.Destination {
return c.current.Outcome.fundingTargets()
return c.current.Outcome.FundingTargets()
}

func (c *ConsensusChannel) Accept(p SignedProposal) error {
Expand Down Expand Up @@ -496,8 +496,8 @@ func (o *LedgerOutcome) AsOutcome() outcome.Exit {
}
}

// fundingTargets returns a list of channels funded by the LedgerOutcome
func (o *LedgerOutcome) fundingTargets() []types.Destination {
// FundingTargets returns a list of channels funded by the LedgerOutcome
func (o LedgerOutcome) FundingTargets() []types.Destination {
targets := []types.Destination{}

for dest := range o.guarantees {
Expand Down
10 changes: 10 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ func (c *Client) GetPaymentChannel(id types.Destination) (query.PaymentChannelIn
return query.GetPaymentChannelInfo(id, c.store, c.vm)
}

// GetPaymentChannelsByLedger returns all active payment channels that are funded by the given ledger channel.
func (c *Client) GetPaymentChannelsByLedger(ledgerId types.Destination) ([]query.PaymentChannelInfo, error) {
return query.GetPaymentChannelsByLedger(ledgerId, c.store, c.vm)
}

// GetAllLedgerChannels returns all ledger channels.
func (c *Client) GetAllLedgerChannels() ([]query.LedgerChannelInfo, error) {
return query.GetAllLedgerChannels(c.store, c.engine.GetConsensusAppAddress())
}

// GetLedgerChannel returns the ledger channel with the given id.
// If no ledger channel exists with the given id an error is returned.
func (c *Client) GetLedgerChannel(id types.Destination) (query.LedgerChannelInfo, error) {
Expand Down
92 changes: 92 additions & 0 deletions client/engine/store/durablestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,73 @@ func (ds *DurableStore) getChannelById(id types.Destination) (channel.Channel, e
return ch, nil
}

// GetChannelsByIds returns any channels with ids in the supplied list.
func (ds *DurableStore) GetChannelsByIds(ids []types.Destination) ([]*channel.Channel, error) {
toReturn := []*channel.Channel{}
// We know every channel has a unique id
// so we can stop looking once we've found the correct number of channels

var err error

txError := ds.channels.View(func(tx *buntdb.Tx) error {
return tx.Ascend("", func(key, chJSON string) bool {
var ch channel.Channel
err = json.Unmarshal([]byte(chJSON), &ch)
if err != nil {
return false
}

// If the channel is one of the ones we're looking for, add it to the list
if contains(ids, ch.Id) {
toReturn = append(toReturn, &ch)
}

// If we've found all the channels we need, stop looking
if len(toReturn) == len(ids) {
return false
}
return true // otherwise, continue looking
})
})

if txError != nil {
return []*channel.Channel{}, txError
}
if err != nil {
return []*channel.Channel{}, err
}

return toReturn, nil
}

// GetChannelsByAppDefinition returns any channels that include the given app definition
func (ds *DurableStore) GetChannelsByAppDefinition(appDef types.Address) ([]*channel.Channel, error) {
toReturn := []*channel.Channel{}
var unmarshErr error
err := ds.channels.View(func(tx *buntdb.Tx) error {
return tx.Ascend("", func(key, chJSON string) bool {
var ch channel.Channel
unmarshErr = json.Unmarshal([]byte(chJSON), &ch)
if unmarshErr != nil {
return false
}

if ch.AppDefinition == appDef {
toReturn = append(toReturn, &ch)
}

return true
})
})
if err != nil {
return []*channel.Channel{}, err
}
if unmarshErr != nil {
return []*channel.Channel{}, unmarshErr
}
return toReturn, nil
}

// GetChannelsByParticipant returns any channels that include the given participant
func (ds *DurableStore) GetChannelsByParticipant(participant types.Address) []*channel.Channel {
toReturn := []*channel.Channel{}
Expand All @@ -292,6 +359,31 @@ func (ds *DurableStore) GetChannelsByParticipant(participant types.Address) []*c
return toReturn
}

func (ds *DurableStore) GetAllConsensusChannels() ([]*consensus_channel.ConsensusChannel, error) {
toReturn := []*consensus_channel.ConsensusChannel{}
var unmarshErr error
err := ds.consensusChannels.View(func(tx *buntdb.Tx) error {
return tx.Ascend("", func(key, chJSON string) bool {
var ch consensus_channel.ConsensusChannel

unmarshErr = json.Unmarshal([]byte(chJSON), &ch)
if unmarshErr != nil {
return false
}
toReturn = append(toReturn, &ch)
return true
})
})
if err != nil {
return []*consensus_channel.ConsensusChannel{}, err
}

if unmarshErr != nil {
return []*consensus_channel.ConsensusChannel{}, unmarshErr
}
return toReturn, nil
}

// GetConsensusChannelById returns a ConsensusChannel with the given channel id
func (ds *DurableStore) GetConsensusChannelById(id types.Destination) (channel *consensus_channel.ConsensusChannel, err error) {
var ch *consensus_channel.ConsensusChannel
Expand Down
85 changes: 85 additions & 0 deletions client/engine/store/memstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,61 @@ func (ms *MemStore) getChannelById(id types.Destination) (channel.Channel, error
return ch, nil
}

// GetChannelsByIds returns a collection of channels with the given ids
func (ms *MemStore) GetChannelsByIds(ids []types.Destination) ([]*channel.Channel, error) {
toReturn := []*channel.Channel{}

var err error

ms.channels.Range(func(key string, chJSON []byte) bool {
var ch channel.Channel
err = json.Unmarshal(chJSON, &ch)
if err != nil {
return false
}

// If the channel is one of the ones we're looking for, add it to the list
if contains(ids, ch.Id) {
toReturn = append(toReturn, &ch)
}

// If we've found all the channels we need, stop looking
if len(toReturn) == len(ids) {
return false
}

return true // otherwise, continue looking
})
if err != nil {
return []*channel.Channel{}, err
}
return toReturn, nil
}

// GetChannelsByAppDefinition returns any channels that include the given app definition
func (ms *MemStore) GetChannelsByAppDefinition(appDef types.Address) ([]*channel.Channel, error) {
toReturn := []*channel.Channel{}
var err error
ms.channels.Range(func(key string, chJSON []byte) bool {
var ch channel.Channel
err = json.Unmarshal(chJSON, &ch)
if err != nil {
return false
}
if ch.AppDefinition == appDef {
toReturn = append(toReturn, &ch)
}

return true // channel not found: continue looking
})

if err != nil {
return []*channel.Channel{}, err
}

return toReturn, nil
}

// GetChannelsByParticipant returns any channels that include the given participant
func (ms *MemStore) GetChannelsByParticipant(participant types.Address) []*channel.Channel {
toReturn := []*channel.Channel{}
Expand Down Expand Up @@ -245,6 +300,26 @@ func (ms *MemStore) GetConsensusChannel(counterparty types.Address) (channel *co
return
}

func (ms *MemStore) GetAllConsensusChannels() ([]*consensus_channel.ConsensusChannel, error) {
toReturn := []*consensus_channel.ConsensusChannel{}
var err error
ms.consensusChannels.Range(func(key string, chJSON []byte) bool {
var ch consensus_channel.ConsensusChannel

err = json.Unmarshal(chJSON, &ch)
if err != nil {
return false
}

toReturn = append(toReturn, &ch)
return true // channel not found: continue looking
})
if err != nil {
return nil, err
}
return toReturn, nil
}

func (ms *MemStore) GetObjectiveByChannelId(channelId types.Destination) (protocols.Objective, bool) {
// todo: locking
id, found := ms.channelToObjective.Load(channelId.String())
Expand Down Expand Up @@ -404,3 +479,13 @@ func (ms *MemStore) RemoveVoucherInfo(channelId types.Destination) error {
ms.vouchers.Delete(channelId.String())
return nil
}

// contains is a helper function which returns true if the given item is included in col
func contains[T types.Destination | protocols.ObjectiveId](col []T, item T) bool {
for _, i := range col {
if i == item {
return true
}
}
return false
}
12 changes: 6 additions & 6 deletions client/engine/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,26 @@ var (

// Store is responsible for persisting objectives, objective metadata, states, signatures, private keys and blockchain data
type Store interface {
GetChannelSecretKey() *[]byte // Get a pointer to a secret key for signing channel updates
GetAddress() *types.Address // Get the (Ethereum) address associated with the ChannelSecretKey

GetChannelSecretKey() *[]byte // Get a pointer to a secret key for signing channel updates
GetAddress() *types.Address // Get the (Ethereum) address associated with the ChannelSecretKey
GetObjectiveById(protocols.ObjectiveId) (protocols.Objective, error) // Read an existing objective
GetObjectiveByChannelId(types.Destination) (obj protocols.Objective, ok bool) // Get the objective that currently owns the channel with the supplied ChannelId
SetObjective(protocols.Objective) error // Write an objective

GetChannelsByIds(ids []types.Destination) ([]*channel.Channel, error) // Returns a collection of channels with the given ids
GetChannelById(id types.Destination) (c *channel.Channel, ok bool)
GetChannelsByParticipant(participant types.Address) []*channel.Channel // Returns any channels that includes the given participant
SetChannel(*channel.Channel) error
DestroyChannel(id types.Destination)

ReleaseChannelFromOwnership(types.Destination) // Release channel from being owned by any objective
GetChannelsByAppDefinition(appDef types.Address) ([]*channel.Channel, error) // Returns any channels that includes the given app definition
ReleaseChannelFromOwnership(types.Destination) // Release channel from being owned by any objective

ConsensusChannelStore
payments.VoucherStore
io.Closer
}

type ConsensusChannelStore interface {
GetAllConsensusChannels() ([]*consensus_channel.ConsensusChannel, error)
GetConsensusChannel(counterparty types.Address) (channel *consensus_channel.ConsensusChannel, ok bool)
GetConsensusChannelById(id types.Destination) (channel *consensus_channel.ConsensusChannel, err error)
SetConsensusChannel(*consensus_channel.ConsensusChannel) error
Expand Down
61 changes: 60 additions & 1 deletion client/query/query.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package query

import (
"errors"
"fmt"
"math/big"

Expand Down Expand Up @@ -127,6 +128,65 @@ func GetPaymentChannelInfo(id types.Destination, store store.Store, vm *payments
return PaymentChannelInfo{}, fmt.Errorf("could not find channel with id %v", id)
}

// GetAllLedgerChannels returns a `LedgerChannelInfo` for each ledger channel in the store.
func GetAllLedgerChannels(store store.Store, consensusAppDefinition types.Address) ([]LedgerChannelInfo, error) {
toReturn := []LedgerChannelInfo{}

allConsensus, err := store.GetAllConsensusChannels()
if err != nil {
return []LedgerChannelInfo{}, err
}
for _, con := range allConsensus {
toReturn = append(toReturn, ConstructLedgerInfoFromConsensus(con))
}
allChannels, err := store.GetChannelsByAppDefinition(consensusAppDefinition)
if err != nil {
return []LedgerChannelInfo{}, err
}
for _, c := range allChannels {
toReturn = append(toReturn, ConstructLedgerInfoFromChannel(c))
}
return toReturn, nil
}

// GetPaymentChannelsByLedger returns a `PaymentChannelInfo` for each active payment channel funded by the given ledger channel.
func GetPaymentChannelsByLedger(ledgerId types.Destination, s store.Store, vm *payments.VoucherManager) ([]PaymentChannelInfo, error) {
// If a ledger channel is actively funding payment channels it must be in the form of a consensus channel
con, err := s.GetConsensusChannelById(ledgerId)
// If the ledger channel is not a consensus channel we know that there are no payment channels funded by it
if errors.Is(err, store.ErrNoSuchChannel) {
return []PaymentChannelInfo{}, nil
}
if err != nil {
return []PaymentChannelInfo{}, fmt.Errorf("could not find any payment channels funded by %s: %w", ledgerId, err)
}

toQuery := con.ConsensusVars().Outcome.FundingTargets()

paymentChannels, err := s.GetChannelsByIds(toQuery)
if err != nil {
return []PaymentChannelInfo{}, fmt.Errorf("could not query the store about ids %v: %w", toQuery, err)
}

toReturn := []PaymentChannelInfo{}
for _, p := range paymentChannels {
paid, remaining, err := GetVoucherBalance(p.Id, vm)
if err != nil {
return []PaymentChannelInfo{}, err
}
// TODO: n+1 query problem
// We should query for the vfos in bulk, rather than one at a time
// Or we should be able to determine the status soley from the channel
vfo, _ := GetVirtualFundObjective(p.Id, s)
info, err := ConstructPaymentInfo(p, vfo, paid, remaining)
if err != nil {
return []PaymentChannelInfo{}, err
}
toReturn = append(toReturn, info)
}
return toReturn, nil
}

// GetLedgerChannelInfo returns the LedgerChannelInfo for the given channel
// It does this by querying the provided store
func GetLedgerChannelInfo(id types.Destination, store store.Store) (LedgerChannelInfo, error) {
Expand Down Expand Up @@ -166,7 +226,6 @@ func ConstructLedgerInfoFromChannel(c *channel.Channel) LedgerChannelInfo {

func ConstructPaymentInfo(c *channel.Channel, vfo *virtualfund.Objective, paid, remaining *big.Int) (PaymentChannelInfo, error) {
status := getStatusFromChannel(c)

if vfo != nil && vfo.Status == protocols.Completed {
// This means intermediaries may not have a fully signed postfund state even though the channel is "ready"
// To determine the the correct status we check the status of the virtual fund objective
Expand Down
Loading

0 comments on commit bf9f688

Please sign in to comment.