-
Notifications
You must be signed in to change notification settings - Fork 41
/
price.go
143 lines (126 loc) · 4.8 KB
/
price.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package price
import (
"errors"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/bandprotocol/chain/hooks/common"
"github.com/bandprotocol/chain/pkg/obi"
"github.com/bandprotocol/chain/x/oracle/keeper"
"github.com/bandprotocol/chain/x/oracle/types"
)
// Hook uses levelDB to store the latest price of standard price reference.
type Hook struct {
cdc *codec.Codec
stdOs map[types.OracleScriptID]bool
oracleKeeper keeper.Keeper
db *leveldb.DB
}
// NewHook creates a price hook instance that will be added in Band App.
func NewHook(cdc *codec.Codec, oracleKeeper keeper.Keeper, oids []types.OracleScriptID, priceDBDir string) *Hook {
stdOs := make(map[types.OracleScriptID]bool)
for _, oid := range oids {
stdOs[oid] = true
}
db, err := leveldb.OpenFile(priceDBDir, nil)
if err != nil {
panic(err)
}
return &Hook{
cdc: cdc,
stdOs: stdOs,
oracleKeeper: oracleKeeper,
db: db,
}
}
// AfterInitChain specify actions need to do after chain initialization (app.Hook interface).
func (h *Hook) AfterInitChain(ctx sdk.Context, req abci.RequestInitChain, res abci.ResponseInitChain) {
}
// AfterBeginBlock specify actions need to do after begin block period (app.Hook interface).
func (h *Hook) AfterBeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, res abci.ResponseBeginBlock) {
}
// AfterDeliverTx specify actions need to do after transaction has been processed (app.Hook interface).
func (h *Hook) AfterDeliverTx(ctx sdk.Context, req abci.RequestDeliverTx, res abci.ResponseDeliverTx) {
}
// AfterEndBlock specify actions need to do after end block period (app.Hook interface).
func (h *Hook) AfterEndBlock(ctx sdk.Context, req abci.RequestEndBlock, res abci.ResponseEndBlock) {
for _, event := range res.Events {
events := sdk.StringifyEvents([]abci.Event{event})
evMap := common.ParseEvents(events)
switch event.Type {
case types.EventTypeResolve:
reqID := types.RequestID(common.Atoi(evMap[types.EventTypeResolve+"."+types.AttributeKeyID][0]))
result := h.oracleKeeper.MustGetResult(ctx, reqID)
if result.ResponsePacketData.ResolveStatus == types.ResolveStatus_Success {
// Check that we need to store data to db
if h.stdOs[result.RequestPacketData.OracleScriptID] {
var input Input
var output Output
obi.MustDecode(result.RequestPacketData.Calldata, &input)
obi.MustDecode(result.ResponsePacketData.Result, &output)
for idx, symbol := range input.Symbols {
price := NewPrice(symbol, input.Multiplier, output.Pxs[idx], result.ResponsePacketData.RequestID, result.ResponsePacketData.ResolveTime)
err := h.db.Put([]byte(fmt.Sprintf("%d,%d,%s", result.RequestPacketData.AskCount, result.RequestPacketData.MinCount, symbol)),
h.cdc.MustMarshalBinaryBare(price), nil)
if err != nil {
panic(err)
}
}
}
}
default:
break
}
}
}
// ApplyQuery catch the custom query that matches specific paths (app.Hook interface).
func (h *Hook) ApplyQuery(req abci.RequestQuery) (res abci.ResponseQuery, stop bool) {
paths := strings.Split(req.Path, "/")
if paths[0] == "band" {
switch paths[1] {
case "prices":
if len(paths) < 5 {
return common.QueryResultError(errors.New("no route for prices query specified")), true
}
symbol := paths[2]
askCount := common.Atoui(paths[3])
minCount := common.Atoui(paths[4])
bz, err := h.db.Get([]byte(fmt.Sprintf("%d,%d,%s", askCount, minCount, symbol)), nil)
if err != nil {
return common.QueryResultError(fmt.Errorf(
"Cannot get price of %s with %d/%d counts with error: %s",
symbol, minCount, askCount, err.Error(),
)), true
}
return common.QueryResultSuccess(bz, req.Height), true
case "price_symbols":
if len(paths) < 4 {
return common.QueryResultError(errors.New("no route for symbol prices query specified")), true
}
askCount := common.Atoui(paths[2])
minCount := common.Atoui(paths[3])
prefix := []byte(fmt.Sprintf("%d,%d,", askCount, minCount))
it := h.db.NewIterator(util.BytesPrefix(prefix), nil)
symbols := []string{}
for it.Next() {
symbols = append(symbols, string(it.Key()[len(prefix):]))
}
it.Release()
if err := it.Error(); err != nil {
return common.QueryResultError(fmt.Errorf("Error while iterate over prices list: %s", err.Error())), true
}
bz := h.cdc.MustMarshalBinaryBare(symbols)
return common.QueryResultSuccess(bz, req.Height), true
default:
return abci.ResponseQuery{}, false
}
} else {
return abci.ResponseQuery{}, false
}
}
// BeforeCommit specify actions need to do before commit block (app.Hook interface).
func (h *Hook) BeforeCommit() {}