Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pending events #156

Merged
merged 12 commits into from
Jan 14, 2025
76 changes: 65 additions & 11 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,25 @@ func TestAPI(t *testing.T) {
t.Fatal(err)
}

// Unconfirmed transaction relevant to addr1
txn3 := types.Transaction{
SiacoinInputs: []types.SiacoinInput{{
ParentID: txn1.SiacoinOutputID(0),
UnlockConditions: unlockConditions,
}},
SiacoinOutputs: []types.SiacoinOutput{{
Address: types.VoidAddress,
Value: txn1.SiacoinOutputs[0].Value,
}},
}
testutil.SignTransaction(cm.TipState(), pk1, &txn3)

// Other unconfirmed transaction
txn4 := types.Transaction{}
if _, err := cm.AddPoolTransactions([]types.Transaction{txn3, txn4}); err != nil {
t.Fatal(err)
}

// Ensure explorer has time to add blocks
time.Sleep(2 * time.Second)

Expand Down Expand Up @@ -412,17 +431,52 @@ func TestAPI(t *testing.T) {
testutil.Equal(t, "immature siacoins", types.ZeroCurrency, resp.ImmatureSiacoins)
testutil.Equal(t, "unspent siafunds", txn2.SiafundOutputs[1].Value, resp.UnspentSiafunds)
}},
// 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")
// }
// }
{"AddressEvents", func(t *testing.T) {
resp, err := client.AddressEvents(addr1, 0, 500)
if err != nil {
t.Fatal(err)
}
testutil.Equal(t, "events", 3, len(resp))

ev0 := resp[0].Data.(explorer.EventV1Transaction)
testutil.CheckTransaction(t, txn2, ev0.Transaction)

ev1 := resp[1].Data.(explorer.EventV1Transaction)
testutil.CheckTransaction(t, txn1, ev1.Transaction)

ev2 := resp[2].Data.(explorer.EventV1Transaction)
testutil.CheckTransaction(t, genesisBlock.Transactions[0], ev2.Transaction)
}},
{"Event", func(t *testing.T) {
resp, err := client.Event(types.Hash256(txn2.ID()))
if err != nil {
t.Fatal(err)
}

ev := resp.Data.(explorer.EventV1Transaction)
testutil.CheckTransaction(t, txn2, ev.Transaction)
}},
{"Event unconfirmed", func(t *testing.T) {
resp, err := client.Event(types.Hash256(txn3.ID()))
if err != nil {
t.Fatal(err)
}

ev := resp.Data.(explorer.EventV1Transaction)
ev.Transaction.SiacoinOutputs[0].Source = explorer.SourceTransaction
testutil.CheckTransaction(t, txn3, ev.Transaction)
}},
{"Address event unconfirmed", func(t *testing.T) {
resp, err := client.AddressUnconfirmedEvents(addr1)
if err != nil {
t.Fatal(err)
}
testutil.Equal(t, "events", 1, len(resp))

ev := resp[0].Data.(explorer.EventV1Transaction)
ev.Transaction.SiacoinOutputs[0].Source = explorer.SourceTransaction
testutil.CheckTransaction(t, txn3, ev.Transaction)
}},
{"Contract", func(t *testing.T) {
resp, err := client.Contract(txn1.FileContractID(0))
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
60 changes: 56 additions & 4 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ type (
UnspentSiacoinOutputs(address types.Address, offset, limit uint64) ([]explorer.SiacoinOutput, error)
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)
Expand Down Expand Up @@ -97,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.
Expand Down Expand Up @@ -468,6 +474,49 @@ func (s *server) addressessAddressEventsHandler(jc jape.Context) {
jc.Encode(events)
}

func (s *server) addressessAddressEventsUnconfirmedHandler(jc jape.Context) {
var address types.Address
if jc.DecodeParam("address", &address) != nil {
return
}

events, err := s.e.AddressUnconfirmedEvents(address)
if jc.Check("failed to get unconfirmed address events", err) != nil {
return
}

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()
events, err = s.e.UnconfirmedEvents(types.ChainIndex{}, types.CurrentTimestamp(), v1, v2)
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 {
Expand Down Expand Up @@ -725,10 +774,13 @@ func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {
"POST /v2/transactions": srv.v2TransactionsBatchHandler,
"GET /v2/transactions/:id/indices": srv.v2TransactionsIDIndicesHandler,

"GET /addresses/:address/utxos/siacoin": srv.addressessAddressUtxosSiacoinHandler,
"GET /addresses/:address/utxos/siafund": srv.addressessAddressUtxosSiafundHandler,
"GET /addresses/:address/events": srv.addressessAddressEventsHandler,
"GET /addresses/:address/balance": srv.addressessAddressBalanceHandler,
"GET /addresses/:address/utxos/siacoin": srv.addressessAddressUtxosSiacoinHandler,
"GET /addresses/:address/utxos/siafund": srv.addressessAddressUtxosSiafundHandler,
"GET /addresses/:address/events": srv.addressessAddressEventsHandler,
"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,
Expand Down
Loading
Loading