From 40f78cbf8ea26160274c0cbf0483d416c8ffa2c6 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Sat, 11 Jan 2025 15:24:11 -0500 Subject: [PATCH] fix events marshalling --- api/api_test.go | 18 +++++++-------- api/client.go | 12 ++++++++++ api/server.go | 49 +++++++++++++++++++++++++++++++++++++++++ explorer/events.go | 52 ++++++++++++++++++++++++++++++++++++++++++++ explorer/explorer.go | 11 ++++++++++ 5 files changed, 133 insertions(+), 9 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index e46273d..9f466dd 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -414,15 +414,15 @@ func TestAPI(t *testing.T) { }}, // There is an issue with JSON unmarshaling of events. // TODO: fix when explorer.Events are replaced with wallet.Events - // { - // resp, err := client.AddressEvents(addr1, 0, 500) - // if err != nil { - // t.Fatal(err) - // } - // if len(resp) == 0 { - // t.Fatal("no events for addr1") - // } - // } + {"Events", func(t *testing.T) { + resp, err := client.AddressEvents(addr1, 0, 500) + if err != nil { + t.Fatal(err) + } + if len(resp) == 0 { + t.Fatal("no events for addr1") + } + }}, {"Contract", func(t *testing.T) { resp, err := client.Contract(txn1.FileContractID(0)) if err != nil { diff --git a/api/client.go b/api/client.go index c9e0622..f289c9b 100644 --- a/api/client.go +++ b/api/client.go @@ -181,6 +181,18 @@ func (c *Client) AddressEvents(address types.Address, offset, limit uint64) (res return } +// AddressUnconfirmedEvents returns the specified address' unconfirmed events. +func (c *Client) AddressUnconfirmedEvents(address types.Address) (resp []explorer.Event, err error) { + err = c.c.GET(fmt.Sprintf("/addresses/%s/events/unconfirmed", address), &resp) + return +} + +// Event returns the specified event. +func (c *Client) Event(id types.Hash256) (resp explorer.Event, err error) { + err = c.c.GET(fmt.Sprintf("/events/%s", id), &resp) + return +} + // AddressBalance returns the specified address' balance. func (c *Client) AddressBalance(address types.Address) (resp AddressBalanceResponse, err error) { err = c.c.GET(fmt.Sprintf("/addresses/%s/balance", address), &resp) diff --git a/api/server.go b/api/server.go index d2c75a3..04f7306 100644 --- a/api/server.go +++ b/api/server.go @@ -63,6 +63,8 @@ type ( UnspentSiafundOutputs(address types.Address, offset, limit uint64) ([]explorer.SiafundOutput, error) AddressEvents(address types.Address, offset, limit uint64) (events []explorer.Event, err error) AddressUnconfirmedEvents(address types.Address) ([]explorer.Event, error) + Events(ids []types.Hash256) ([]explorer.Event, error) + UnconfirmedEvents(index types.ChainIndex, timestamp time.Time, v1 []types.Transaction, v2 []types.V2Transaction) ([]explorer.Event, error) Contracts(ids []types.FileContractID) (result []explorer.ExtendedFileContract, err error) ContractsKey(key types.PublicKey) (result []explorer.ExtendedFileContract, err error) ContractRevisions(id types.FileContractID) (result []explorer.ExtendedFileContract, err error) @@ -98,6 +100,9 @@ var ( // ErrHostNotFound is returned by /pubkey/:key/host when we are unable to // find the host with the pubkey `key`. ErrHostNotFound = errors.New("no host found") + // ErrEventNotFound is returned by /events/:id when we can't find the event + // with the id `id`. + ErrEventNotFound = errors.New("no event found") // ErrNoSearchResults is returned by /search/:id when we do not find any // elements with that ID. @@ -483,6 +488,48 @@ func (s *server) addressessAddressEventsUnconfirmedHandler(jc jape.Context) { jc.Encode(events) } +func (s *server) eventsIDHandler(jc jape.Context) { + var id types.Hash256 + if jc.DecodeParam("id", &id) != nil { + return + } + + events, err := s.e.Events([]types.Hash256{id}) + if err != nil { + return + } else if len(events) > 0 { + jc.Encode(events[0]) + return + } + + { + v1, v2 := s.cm.PoolTransactions(), s.cm.V2PoolTransactions() + + relevantV1 := v1[:0] + for _, txn := range v1 { + relevantV1 = append(relevantV1, txn) + } + + relevantV2 := v2[:0] + for _, txn := range v2 { + relevantV2 = append(relevantV2, txn) + } + + events, err := s.e.UnconfirmedEvents(types.ChainIndex{}, types.CurrentTimestamp(), relevantV1, relevantV2) + if jc.Check("failed to annotate events", err) != nil { + return + } + for _, event := range events { + if event.ID == id { + jc.Encode(event) + return + } + } + } + + jc.Error(ErrEventNotFound, http.StatusNotFound) +} + func (s *server) outputsSiacoinHandler(jc jape.Context) { var id types.SiacoinOutputID if jc.DecodeParam("id", &id) != nil { @@ -746,6 +793,8 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler { "GET /addresses/:address/events/unconfirmed": srv.addressessAddressEventsUnconfirmedHandler, "GET /addresses/:address/balance": srv.addressessAddressBalanceHandler, + "GET /events/:id": srv.eventsIDHandler, + "GET /outputs/siacoin/:id": srv.outputsSiacoinHandler, "GET /outputs/siafund/:id": srv.outputsSiafundHandler, diff --git a/explorer/events.go b/explorer/events.go index 764769f..22290ec 100644 --- a/explorer/events.go +++ b/explorer/events.go @@ -1,6 +1,8 @@ package explorer import ( + "encoding/json" + "fmt" "time" "go.sia.tech/core/consensus" @@ -66,6 +68,56 @@ func (EventV1ContractResolution) isEvent() bool { return true } func (EventV2Transaction) isEvent() bool { return true } func (EventV2ContractResolution) isEvent() bool { return true } +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *Event) UnmarshalJSON(b []byte) error { + var je struct { + ID types.Hash256 `json:"id"` + Index types.ChainIndex `json:"index"` + Timestamp time.Time `json:"timestamp"` + MaturityHeight uint64 `json:"maturityHeight"` + Type string `json:"type"` + Data json.RawMessage `json:"data"` + Relevant []types.Address `json:"relevant,omitempty"` + } + if err := json.Unmarshal(b, &je); err != nil { + return err + } + + e.ID = je.ID + e.Index = je.Index + e.Timestamp = je.Timestamp + e.MaturityHeight = je.MaturityHeight + e.Type = je.Type + e.Relevant = je.Relevant + + var err error + switch je.Type { + case wallet.EventTypeMinerPayout, wallet.EventTypeFoundationSubsidy, wallet.EventTypeSiafundClaim: + var data EventPayout + err = json.Unmarshal(je.Data, &data) + e.Data = data + case wallet.EventTypeV1ContractResolution: + var data EventV1ContractResolution + err = json.Unmarshal(je.Data, &data) + e.Data = data + case wallet.EventTypeV2ContractResolution: + var data EventV2ContractResolution + err = json.Unmarshal(je.Data, &data) + e.Data = data + case wallet.EventTypeV1Transaction: + var data EventV1Transaction + err = json.Unmarshal(je.Data, &data) + e.Data = data + case wallet.EventTypeV2Transaction: + var data EventV2Transaction + err = json.Unmarshal(je.Data, &data) + e.Data = data + default: + return fmt.Errorf("unknown event type: %v", je.Type) + } + return err +} + // A ChainUpdate is a set of changes to the consensus state. type ChainUpdate interface { ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool)) diff --git a/explorer/explorer.go b/explorer/explorer.go index f506ffa..0138e93 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -61,6 +61,7 @@ type Store interface { UnspentSiafundOutputs(address types.Address, offset, limit uint64) ([]SiafundOutput, error) UnconfirmedEvents(index types.ChainIndex, timestamp time.Time, v1 []types.Transaction, v2 []types.V2Transaction) (annotated []Event, err error) AddressEvents(address types.Address, offset, limit uint64) (events []Event, err error) + Events([]types.Hash256) ([]Event, error) Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error) Contracts(ids []types.FileContractID) (result []ExtendedFileContract, err error) ContractsKey(key types.PublicKey) (result []ExtendedFileContract, err error) @@ -326,11 +327,21 @@ func (e *Explorer) AddressUnconfirmedEvents(address types.Address) ([]Event, err return events, nil } +// UnconfirmedEvents annotates a list of unconfirmed transactions. +func (e *Explorer) UnconfirmedEvents(index types.ChainIndex, timestamp time.Time, v1 []types.Transaction, v2 []types.V2Transaction) ([]Event, error) { + return e.s.UnconfirmedEvents(index, timestamp, v1, v2) +} + // AddressEvents returns the events of a single address. func (e *Explorer) AddressEvents(address types.Address, offset, limit uint64) (events []Event, err error) { return e.s.AddressEvents(address, offset, limit) } +// Events returns the events with the specified IDs. +func (e *Explorer) Events(ids []types.Hash256) ([]Event, error) { + return e.s.Events(ids) +} + // Balance returns the balance of an address. func (e *Explorer) Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error) { return e.s.Balance(address)