mirror of
https://github.com/eosswedenorg/thalos
synced 2026-07-04 12:03:41 +02:00
app/abi: rework manager to use new cache struct.
This commit is contained in:
parent
2eb62db117
commit
424ae2fc40
4 changed files with 182 additions and 223 deletions
|
|
@ -1,46 +0,0 @@
|
||||||
package abi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
eos "github.com/eoscanada/eos-go"
|
|
||||||
redis_cache "github.com/go-redis/cache/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cache represents a abi cache in redis.
|
|
||||||
type Cache struct {
|
|
||||||
c *redis_cache.Cache
|
|
||||||
ctx context.Context
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new cache
|
|
||||||
func NewCache(prefix string, options *redis_cache.Options) *Cache {
|
|
||||||
return &Cache{
|
|
||||||
c: redis_cache.New(options),
|
|
||||||
ctx: context.Background(),
|
|
||||||
prefix: prefix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an ABI from the cache using the contract account name as the key.
|
|
||||||
func (cache *Cache) Get(account string) (*eos.ABI, error) {
|
|
||||||
var v eos.ABI
|
|
||||||
err := cache.c.Get(cache.ctx, cache.key(account), &v)
|
|
||||||
return &v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set an ABI in the cache.
|
|
||||||
func (cache *Cache) Set(account string, abi *eos.ABI, ttl time.Duration) error {
|
|
||||||
return cache.c.Set(&redis_cache.Item{
|
|
||||||
Ctx: cache.ctx,
|
|
||||||
Key: cache.key(account),
|
|
||||||
Value: *abi,
|
|
||||||
TTL: ttl,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *Cache) key(account string) string {
|
|
||||||
return cache.prefix + "::" + account
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
package abi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
eos "github.com/eoscanada/eos-go"
|
|
||||||
redis_cache "github.com/go-redis/cache/v9"
|
|
||||||
"github.com/go-redis/redismock/v9"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var abiString = `
|
|
||||||
{
|
|
||||||
"version": "eosio::abi/1.0",
|
|
||||||
"types": [{
|
|
||||||
"new_type_name": "new_type_name_1",
|
|
||||||
"type": "name"
|
|
||||||
}],
|
|
||||||
"structs": [
|
|
||||||
{
|
|
||||||
"name": "struct_name_1",
|
|
||||||
"base": "struct_name_2",
|
|
||||||
"fields": [
|
|
||||||
{"name":"struct_1_field_1", "type":"new_type_name_1"},
|
|
||||||
{"name":"struct_1_field_2", "type":"struct_name_3"},
|
|
||||||
{"name":"struct_1_field_3", "type":"string?"},
|
|
||||||
{"name":"struct_1_field_4", "type":"string?"},
|
|
||||||
{"name":"struct_1_field_5", "type":"struct_name_4[]"}
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
"name": "struct_name_2",
|
|
||||||
"base": "",
|
|
||||||
"fields": [
|
|
||||||
{"name":"struct_2_field_1", "type":"string"}
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
"name": "struct_name_3",
|
|
||||||
"base": "",
|
|
||||||
"fields": [
|
|
||||||
{"name":"struct_3_field_1", "type":"string"}
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
"name": "struct_name_4",
|
|
||||||
"base": "",
|
|
||||||
"fields": [
|
|
||||||
{"name":"struct_4_field_1", "type":"string"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": [{
|
|
||||||
"name": "action_name_1",
|
|
||||||
"type": "struct_name_1",
|
|
||||||
"ricardian_contract": ""
|
|
||||||
}],
|
|
||||||
"tables": [{
|
|
||||||
"name": "table_name_1",
|
|
||||||
"index_type": "i64",
|
|
||||||
"key_names": [
|
|
||||||
"key_name_1",
|
|
||||||
"key_name_2"
|
|
||||||
],
|
|
||||||
"key_types": [
|
|
||||||
"string",
|
|
||||||
"int"
|
|
||||||
],
|
|
||||||
"type": "struct_name_1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestGetSet(t *testing.T) {
|
|
||||||
client, mock := redismock.NewClientMock()
|
|
||||||
|
|
||||||
c := NewCache("thalos::cache::test", &redis_cache.Options{
|
|
||||||
Redis: client,
|
|
||||||
// Cache 10k keys for 1 minute.
|
|
||||||
LocalCache: redis_cache.NewTinyLFU(10000, time.Minute),
|
|
||||||
})
|
|
||||||
|
|
||||||
abi, err := eos.NewABI(strings.NewReader(abiString))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
bytes, _ := c.c.Marshal(*abi)
|
|
||||||
|
|
||||||
mock.ExpectSet("thalos::cache::test::testaccount", bytes, time.Minute).SetVal("OK")
|
|
||||||
|
|
||||||
err = c.Set("testaccount", abi, time.Minute)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
c_abi, err := c.Get("testaccount")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, c_abi.Version, "eosio::abi/1.0")
|
|
||||||
|
|
||||||
// Types
|
|
||||||
assert.Equal(t, c_abi.Types[0].NewTypeName, "new_type_name_1")
|
|
||||||
assert.Equal(t, c_abi.Types[0].Type, "name")
|
|
||||||
|
|
||||||
// Structs
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Name, "struct_name_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Base, "struct_name_2")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[0].Name, "struct_1_field_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[0].Type, "new_type_name_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[1].Name, "struct_1_field_2")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[1].Type, "struct_name_3")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[2].Name, "struct_1_field_3")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[2].Type, "string?")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[3].Name, "struct_1_field_4")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[3].Type, "string?")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[4].Name, "struct_1_field_5")
|
|
||||||
assert.Equal(t, c_abi.Structs[0].Fields[4].Type, "struct_name_4[]")
|
|
||||||
|
|
||||||
assert.Equal(t, c_abi.Structs[1].Name, "struct_name_2")
|
|
||||||
assert.Equal(t, c_abi.Structs[1].Base, "")
|
|
||||||
assert.Equal(t, c_abi.Structs[1].Fields[0].Name, "struct_2_field_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[1].Fields[0].Type, "string")
|
|
||||||
|
|
||||||
assert.Equal(t, c_abi.Structs[2].Name, "struct_name_3")
|
|
||||||
assert.Equal(t, c_abi.Structs[2].Base, "")
|
|
||||||
assert.Equal(t, c_abi.Structs[2].Fields[0].Name, "struct_3_field_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[2].Fields[0].Type, "string")
|
|
||||||
|
|
||||||
assert.Equal(t, c_abi.Structs[3].Name, "struct_name_4")
|
|
||||||
assert.Equal(t, c_abi.Structs[3].Base, "")
|
|
||||||
assert.Equal(t, c_abi.Structs[3].Fields[0].Name, "struct_4_field_1")
|
|
||||||
assert.Equal(t, c_abi.Structs[3].Fields[0].Type, "string")
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
assert.Equal(t, c_abi.Actions[0].Name, eos.ActN("action_name_1"))
|
|
||||||
assert.Equal(t, c_abi.Actions[0].Type, "struct_name_1")
|
|
||||||
assert.Equal(t, c_abi.Actions[0].RicardianContract, "")
|
|
||||||
|
|
||||||
// Tables
|
|
||||||
assert.Equal(t, c_abi.Tables[0].Name, eos.TableName("table_name_1"))
|
|
||||||
assert.Equal(t, c_abi.Tables[0].Type, "struct_name_1")
|
|
||||||
assert.Equal(t, c_abi.Tables[0].IndexType, "i64")
|
|
||||||
assert.Equal(t, c_abi.Tables[0].KeyNames[0], "key_name_1")
|
|
||||||
assert.Equal(t, c_abi.Tables[0].KeyNames[1], "key_name_2")
|
|
||||||
assert.Equal(t, c_abi.Tables[0].KeyTypes[0], "string")
|
|
||||||
assert.Equal(t, c_abi.Tables[0].KeyTypes[1], "int")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCacheMiss(t *testing.T) {
|
|
||||||
client, _ := redismock.NewClientMock()
|
|
||||||
|
|
||||||
c := NewCache("thalos::cache::test", &redis_cache.Options{
|
|
||||||
Redis: client,
|
|
||||||
// Cache 10k keys for 1 minute.
|
|
||||||
LocalCache: redis_cache.NewTinyLFU(10000, time.Minute),
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err := c.Get("nonexist")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
@ -6,26 +6,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
eos "github.com/eoscanada/eos-go"
|
eos "github.com/eoscanada/eos-go"
|
||||||
redis_cache "github.com/go-redis/cache/v9"
|
"github.com/eosswedenorg/thalos/app/cache"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AbiManager handles an ABI cache that fetches the ABI from an API on cache miss.
|
// AbiManager handles an ABI cache that fetches the ABI from an API on cache miss.
|
||||||
type AbiManager struct {
|
type AbiManager struct {
|
||||||
cache *Cache
|
cache *cache.Cache
|
||||||
api *eos.API
|
api *eos.API
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new ABI Manager
|
// Create a new ABI Manager
|
||||||
func NewAbiManager(rdb *redis.Client, api *eos.API, id string) *AbiManager {
|
func NewAbiManager(cache *cache.Cache, api *eos.API) *AbiManager {
|
||||||
// Init abi cache
|
|
||||||
cache := NewCache("thalos::cache::"+id+"::abi", &redis_cache.Options{
|
|
||||||
Redis: rdb,
|
|
||||||
// Cache 10k keys for 10 minutes.
|
|
||||||
LocalCache: redis_cache.NewTinyLFU(10000, 10*time.Minute),
|
|
||||||
})
|
|
||||||
|
|
||||||
return &AbiManager{
|
return &AbiManager{
|
||||||
cache: cache,
|
cache: cache,
|
||||||
api: api,
|
api: api,
|
||||||
|
|
@ -35,26 +27,24 @@ func NewAbiManager(rdb *redis.Client, api *eos.API, id string) *AbiManager {
|
||||||
|
|
||||||
// Set or update an ABI in the cache.
|
// Set or update an ABI in the cache.
|
||||||
func (mgr *AbiManager) SetAbi(account eos.AccountName, abi *eos.ABI) error {
|
func (mgr *AbiManager) SetAbi(account eos.AccountName, abi *eos.ABI) error {
|
||||||
return mgr.cache.Set(string(account), abi, time.Hour)
|
return mgr.cache.Set(string(account), *abi, time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an ABI from the cache, on cache miss it is fetched from the
|
// Get an ABI from the cache, on cache miss it is fetched from the
|
||||||
// API, gets cached and then returned to the user
|
// API, gets cached and then returned to the user
|
||||||
func (mgr *AbiManager) GetAbi(account eos.AccountName) (*eos.ABI, error) {
|
func (mgr *AbiManager) GetAbi(account eos.AccountName) (*eos.ABI, error) {
|
||||||
key := string(account)
|
var abi eos.ABI
|
||||||
|
if err := mgr.cache.Get(string(account), &abi); err != nil {
|
||||||
abi, err := mgr.cache.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
resp, err := mgr.api.GetABI(mgr.ctx, account)
|
resp, err := mgr.api.GetABI(mgr.ctx, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("api: %s", err)
|
return nil, fmt.Errorf("api: %s", err)
|
||||||
}
|
}
|
||||||
abi = &resp.ABI
|
abi = resp.ABI
|
||||||
|
|
||||||
err = mgr.SetAbi(account, abi)
|
err = mgr.SetAbi(account, &abi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cache: %s", err)
|
return nil, fmt.Errorf("cache: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return abi, nil
|
return &abi, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
173
app/abi/manager_test.go
Normal file
173
app/abi/manager_test.go
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
eos "github.com/eoscanada/eos-go"
|
||||||
|
|
||||||
|
"github.com/eosswedenorg/thalos/app/cache"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var abiString = `
|
||||||
|
{
|
||||||
|
"version": "eosio::abi/1.0",
|
||||||
|
"types": [{
|
||||||
|
"new_type_name": "new_type_name_1",
|
||||||
|
"type": "name"
|
||||||
|
}],
|
||||||
|
"structs": [
|
||||||
|
{
|
||||||
|
"name": "struct_name_1",
|
||||||
|
"base": "struct_name_2",
|
||||||
|
"fields": [
|
||||||
|
{"name":"struct_1_field_1", "type":"new_type_name_1"},
|
||||||
|
{"name":"struct_1_field_2", "type":"struct_name_3"},
|
||||||
|
{"name":"struct_1_field_3", "type":"string?"},
|
||||||
|
{"name":"struct_1_field_4", "type":"string?"},
|
||||||
|
{"name":"struct_1_field_5", "type":"struct_name_4[]"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"name": "struct_name_2",
|
||||||
|
"base": "",
|
||||||
|
"fields": [
|
||||||
|
{"name":"struct_2_field_1", "type":"string"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"name": "struct_name_3",
|
||||||
|
"base": "",
|
||||||
|
"fields": [
|
||||||
|
{"name":"struct_3_field_1", "type":"string"}
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
"name": "struct_name_4",
|
||||||
|
"base": "",
|
||||||
|
"fields": [
|
||||||
|
{"name":"struct_4_field_1", "type":"string"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [{
|
||||||
|
"name": "action_name_1",
|
||||||
|
"type": "struct_name_1",
|
||||||
|
"ricardian_contract": ""
|
||||||
|
}],
|
||||||
|
"tables": [{
|
||||||
|
"name": "table_name_1",
|
||||||
|
"index_type": "i64",
|
||||||
|
"key_names": [
|
||||||
|
"key_name_1",
|
||||||
|
"key_name_2"
|
||||||
|
],
|
||||||
|
"key_types": [
|
||||||
|
"string",
|
||||||
|
"int"
|
||||||
|
],
|
||||||
|
"type": "struct_name_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func assert_abi(t *testing.T, abi *eos.ABI) {
|
||||||
|
assert.Equal(t, abi.Version, "eosio::abi/1.0")
|
||||||
|
|
||||||
|
// Types
|
||||||
|
assert.Equal(t, abi.Types[0].NewTypeName, "new_type_name_1")
|
||||||
|
assert.Equal(t, abi.Types[0].Type, "name")
|
||||||
|
|
||||||
|
// Structs
|
||||||
|
assert.Equal(t, abi.Structs[0].Name, "struct_name_1")
|
||||||
|
assert.Equal(t, abi.Structs[0].Base, "struct_name_2")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[0].Name, "struct_1_field_1")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[0].Type, "new_type_name_1")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[1].Name, "struct_1_field_2")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[1].Type, "struct_name_3")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[2].Name, "struct_1_field_3")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[2].Type, "string?")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[3].Name, "struct_1_field_4")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[3].Type, "string?")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[4].Name, "struct_1_field_5")
|
||||||
|
assert.Equal(t, abi.Structs[0].Fields[4].Type, "struct_name_4[]")
|
||||||
|
|
||||||
|
assert.Equal(t, abi.Structs[1].Name, "struct_name_2")
|
||||||
|
assert.Equal(t, abi.Structs[1].Base, "")
|
||||||
|
assert.Equal(t, abi.Structs[1].Fields[0].Name, "struct_2_field_1")
|
||||||
|
assert.Equal(t, abi.Structs[1].Fields[0].Type, "string")
|
||||||
|
|
||||||
|
assert.Equal(t, abi.Structs[2].Name, "struct_name_3")
|
||||||
|
assert.Equal(t, abi.Structs[2].Base, "")
|
||||||
|
assert.Equal(t, abi.Structs[2].Fields[0].Name, "struct_3_field_1")
|
||||||
|
assert.Equal(t, abi.Structs[2].Fields[0].Type, "string")
|
||||||
|
|
||||||
|
assert.Equal(t, abi.Structs[3].Name, "struct_name_4")
|
||||||
|
assert.Equal(t, abi.Structs[3].Base, "")
|
||||||
|
assert.Equal(t, abi.Structs[3].Fields[0].Name, "struct_4_field_1")
|
||||||
|
assert.Equal(t, abi.Structs[3].Fields[0].Type, "string")
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
assert.Equal(t, abi.Actions[0].Name, eos.ActN("action_name_1"))
|
||||||
|
assert.Equal(t, abi.Actions[0].Type, "struct_name_1")
|
||||||
|
assert.Equal(t, abi.Actions[0].RicardianContract, "")
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
assert.Equal(t, abi.Tables[0].Name, eos.TableName("table_name_1"))
|
||||||
|
assert.Equal(t, abi.Tables[0].Type, "struct_name_1")
|
||||||
|
assert.Equal(t, abi.Tables[0].IndexType, "i64")
|
||||||
|
assert.Equal(t, abi.Tables[0].KeyNames[0], "key_name_1")
|
||||||
|
assert.Equal(t, abi.Tables[0].KeyNames[1], "key_name_2")
|
||||||
|
assert.Equal(t, abi.Tables[0].KeyTypes[0], "string")
|
||||||
|
assert.Equal(t, abi.Tables[0].KeyTypes[1], "int")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockAPI(handler http.HandlerFunc) (*eos.API, *httptest.Server) {
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
|
||||||
|
return &eos.API{
|
||||||
|
HttpClient: server.Client(),
|
||||||
|
BaseURL: strings.TrimRight(server.URL, "/"),
|
||||||
|
Compress: eos.CompressionZlib,
|
||||||
|
Header: make(http.Header),
|
||||||
|
}, server
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_GetAbiFromCache(t *testing.T) {
|
||||||
|
cache := cache.NewCache("thalos::cache::abi::test", cache.NewMemoryStore())
|
||||||
|
|
||||||
|
api, _ := mockAPI(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}))
|
||||||
|
|
||||||
|
mgr := NewAbiManager(cache, api)
|
||||||
|
|
||||||
|
abi, err := eos.NewABI(strings.NewReader(abiString))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.SetAbi("testaccount", abi)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
c_abi, err := mgr.GetAbi("testaccount")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert_abi(t, c_abi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_GetAbiFromAPI(t *testing.T) {
|
||||||
|
cache := cache.NewCache("thalos::cache::abi::test", cache.NewMemoryStore())
|
||||||
|
|
||||||
|
api, _ := mockAPI(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body := fmt.Sprintf(`{"account_name": "testaccount", "abi": %s}`, abiString)
|
||||||
|
|
||||||
|
_, err := w.Write([]byte(body))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}))
|
||||||
|
|
||||||
|
mgr := NewAbiManager(cache, api)
|
||||||
|
|
||||||
|
c_abi, err := mgr.GetAbi("testaccount")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert_abi(t, c_abi)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue