Skip to content

Commit

Permalink
Problem: there's no compact historical state storage
Browse files Browse the repository at this point in the history
Closes: crypto-org-chain#704
Solution:
- Integration version store and streaming service.
  • Loading branch information
yihuang committed Sep 27, 2022
1 parent d482407 commit 760f02a
Show file tree
Hide file tree
Showing 12 changed files with 609 additions and 3 deletions.
35 changes: 33 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"sync"

"github.com/crypto-org-chain/cronos/x/cronos"
"github.com/crypto-org-chain/cronos/x/cronos/middleware"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/server"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
"github.com/spf13/cast"
Expand Down Expand Up @@ -122,7 +125,7 @@ import (

// this line is used by starport scaffolding # stargate/app/moduleImport
cronosappclient "github.com/crypto-org-chain/cronos/client"
"github.com/crypto-org-chain/cronos/x/cronos"
"github.com/crypto-org-chain/cronos/versiondb"
cronosclient "github.com/crypto-org-chain/cronos/x/cronos/client"
cronoskeeper "github.com/crypto-org-chain/cronos/x/cronos/keeper"
evmhandlers "github.com/crypto-org-chain/cronos/x/cronos/keeper/evmhandlers"
Expand Down Expand Up @@ -350,7 +353,8 @@ func New(
// configure state listening capabilities using AppOptions
// we are doing nothing with the returned streamingServices and waitGroup in this case
// Only support file streamer right now.
if cast.ToString(appOpts.Get(cronosappclient.FlagStreamers)) == "file" {
streamers := cast.ToString(appOpts.Get(cronosappclient.FlagStreamers))
if strings.Contains(streamers, "file") {
streamingDir := filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", FileStreamerDirectory)
if err := os.MkdirAll(streamingDir, os.ModePerm); err != nil {
panic(err)
Expand All @@ -373,6 +377,33 @@ func New(
}
}

if strings.Contains(streamers, "versiondb") {
rootDir := cast.ToString(appOpts.Get(flags.FlagHome))
dataDir := filepath.Join(rootDir, "data", "versiondb")
backendType := server.GetAppDBBackend(appOpts)
plainDB, err := dbm.NewDB("plain.db", backendType, dataDir)
if err != nil {
panic(err)
}
historyDB, err := dbm.NewDB("history.db", backendType, dataDir)
if err != nil {
panic(err)
}
changesetDB, err := dbm.NewDB("changeset.db", backendType, dataDir)
if err != nil {
panic(err)
}
verstore := versiondb.NewStore(plainDB, historyDB, changesetDB)

// default to exposing all
exposeStoreKeys := make([]storetypes.StoreKey, 0, len(keys))
for _, storeKey := range keys {
exposeStoreKeys = append(exposeStoreKeys, storeKey)
}
service := versiondb.NewStreamingService(verstore, exposeStoreKeys)
bApp.SetStreamingService(service)
}

app := &App{
BaseApp: bApp,
cdc: cdc,
Expand Down
1 change: 1 addition & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ buildGoApplication rec {
"!/app/"
"!/cmd/"
"!/client/"
"!/versiondb/"
"!go.mod"
"!go.sum"
"!gomod2nix.toml"
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
cosmossdk.io/math v1.0.0-beta.3
github.com/RoaringBitmap/roaring v1.2.1
github.com/armon/go-metrics v0.4.1
github.com/cosmos/cosmos-sdk v0.46.2-0.20220923192627-95948f6692bb
github.com/cosmos/ibc-go/v5 v5.0.0-rc2
Expand Down Expand Up @@ -45,6 +46,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/btcsuite/btcd v0.22.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
Expand Down Expand Up @@ -135,6 +137,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RoaringBitmap/roaring v1.2.1 h1:58/LJlg/81wfEHd5L9qsHduznOIhyv4qb1yWcSvVq9A=
github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
Expand Down Expand Up @@ -347,6 +349,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI=
Expand Down Expand Up @@ -1631,6 +1634,8 @@ github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
Expand Down
9 changes: 9 additions & 0 deletions gomod2nix.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ schema = 3
[mod."github.com/ChainSafe/go-schnorrkel"]
version = "v0.0.0-20200405005733-88cbf1b4c40d"
hash = "sha256-i8RXZemJGlSjBT35oPm0SawFiBoIU5Pkq5xp4n/rzCY="
[mod."github.com/RoaringBitmap/roaring"]
version = "v1.2.1"
hash = "sha256-0/R956wrCW71eOE36CbxGJJRuQjKwvvIQ/D8QTn2A6w="
[mod."github.com/StackExchange/wmi"]
version = "v1.2.1"
hash = "sha256-1BoEeWAWyebH+1mMuyPhWZut8nWHb6r73MgcqlGuUEY="
Expand Down Expand Up @@ -58,6 +61,9 @@ schema = 3
[mod."github.com/bgentry/speakeasy"]
version = "v0.1.0"
hash = "sha256-Gt1vj6CFovLnO6wX5u2O4UfecY9V2J9WGw1ez4HMrgk="
[mod."github.com/bits-and-blooms/bitset"]
version = "v1.2.0"
hash = "sha256-IxNmtELycM+XVzg4qBv04hAJUT3nSWuyP9R+8zc9LmU="
[mod."github.com/btcsuite/btcd"]
version = "v0.22.1"
hash = "sha256-hBU+roIELcmbW2Gz7eGZzL9qNA1bakq5wNxqCgs4TKc="
Expand Down Expand Up @@ -354,6 +360,9 @@ schema = 3
[mod."github.com/mitchellh/mapstructure"]
version = "v1.5.0"
hash = "sha256-ztVhGQXs67MF8UadVvG72G3ly0ypQW0IRDdOOkjYwoE="
[mod."github.com/mschoch/smat"]
version = "v0.2.0"
hash = "sha256-DZvUJXjIcta3U+zxzgU3wpoGn/V4lpBY7Xme8aQUi+E="
[mod."github.com/mtibben/percent"]
version = "v0.2.1"
hash = "sha256-Zj1lpCP6mKQ0UUTMs2By4LC414ou+iJzKkK+eBHfEcc="
Expand Down
2 changes: 1 addition & 1 deletion scripts/cronos-devnet.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dotenv: .env
cronos_777-1:
cmd: cronosd
start-flags: "--trace"
start-flags: "--trace --streamers versiondb,file"
app-config:
minimum-gas-prices: 0basetcro
index-events:
Expand Down
84 changes: 84 additions & 0 deletions versiondb/dbutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package versiondb

import (
"encoding/binary"
"sort"

"github.com/RoaringBitmap/roaring/roaring64"
)

var ChunkLimit = uint64(1950) // threshold beyond which MDBX overflow pages appear: 4096 / 2 - (keySize + 8)

// CutLeft - cut from bitmap `targetSize` bytes from left
// removing lft part from `bm`
// returns nil on zero cardinality
func CutLeft64(bm *roaring64.Bitmap, sizeLimit uint64) *roaring64.Bitmap {
if bm.GetCardinality() == 0 {
return nil
}

sz := bm.GetSerializedSizeInBytes()
if sz <= sizeLimit {
lft := roaring64.New()
lft.AddRange(bm.Minimum(), bm.Maximum()+1)
lft.And(bm)
lft.RunOptimize()
bm.Clear()
return lft
}

from := bm.Minimum()
minMax := bm.Maximum() - bm.Minimum()
to := sort.Search(int(minMax), func(i int) bool { // can be optimized to avoid "too small steps", but let's leave it for readability
lft := roaring64.New() // bitmap.Clear() method intentionally not used here, because then serialized size of bitmap getting bigger
lft.AddRange(from, from+uint64(i)+1)
lft.And(bm)
lft.RunOptimize()
return lft.GetSerializedSizeInBytes() > sizeLimit
})

lft := roaring64.New()
lft.AddRange(from, from+uint64(to)) // no +1 because sort.Search returns element which is just higher threshold - but we need lower
lft.And(bm)
bm.RemoveRange(from, from+uint64(to))
lft.RunOptimize()
return lft
}

func WalkChunks64(bm *roaring64.Bitmap, sizeLimit uint64, f func(chunk *roaring64.Bitmap, isLast bool) error) error {
for bm.GetCardinality() > 0 {
if err := f(CutLeft64(bm, sizeLimit), bm.GetCardinality() == 0); err != nil {
return err
}
}
return nil
}

func WalkChunkWithKeys64(k []byte, m *roaring64.Bitmap, sizeLimit uint64, f func(chunkKey []byte, chunk *roaring64.Bitmap) error) error {
return WalkChunks64(m, sizeLimit, func(chunk *roaring64.Bitmap, isLast bool) error {
chunkKey := make([]byte, len(k)+8)
copy(chunkKey, k)
if isLast {
binary.BigEndian.PutUint64(chunkKey[len(k):], ^uint64(0))
} else {
binary.BigEndian.PutUint64(chunkKey[len(k):], chunk.Maximum())
}
return f(chunkKey, chunk)
})
}

// SeekInBitmap64 - returns value in bitmap which is >= n
func SeekInBitmap64(m *roaring64.Bitmap, n uint64) (found uint64, ok bool) {
if m == nil || m.IsEmpty() {
return 0, false
}
if n == 0 {
return m.Minimum(), true
}
searchRank := m.Rank(n - 1)
if searchRank >= m.GetCardinality() {
return 0, false
}
found, _ = m.Select(searchRank)
return found, true
}
92 changes: 92 additions & 0 deletions versiondb/history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package versiondb

import (
"bytes"

"github.com/RoaringBitmap/roaring/roaring64"

sdk "github.com/cosmos/cosmos-sdk/types"
dbm "github.com/tendermint/tm-db"
)

const LastChunkId = ^uint64(0)

func HistoryIndexKey(key []byte, height uint64) []byte {
return append(key, sdk.Uint64ToBigEndian(height)...)
}

// GetHistoryIndex returns the history index bitmap chunk which covers the target version.
func GetHistoryIndex(db dbm.DB, key []byte, height uint64) (*roaring64.Bitmap, error) {
// try to seek the first chunk whose maximum is bigger or equal to the target height.
it, err := db.Iterator(
HistoryIndexKey(key, height),
sdk.PrefixEndBytes(key),
)
if err != nil {
return nil, err
}
defer it.Close() // nolint: errcheck

if !it.Valid() {
return nil, nil
}

m := roaring64.New()
_, err = m.ReadFrom(bytes.NewReader(it.Value()))
if err != nil {
return nil, err
}
return m, nil
}

// SeekHistoryIndex locate the minimal version that changed the key and is larger than the target version,
// using the returned version can find the value for the target version in changeset table.
// If not found, return -1
func SeekHistoryIndex(db dbm.DB, key []byte, version uint64) (int64, error) {
// either m.Maximum() >= version + 1, or is the last chunk.
m, err := GetHistoryIndex(db, key, version+1)
if err != nil {
return -1, err
}
found, ok := SeekInBitmap64(m, version+1)
if !ok {
return -1, nil
}
return int64(found), nil
}

// WriteHistoryIndex set the block height to the history bitmap.
// it try to set to the last chunk, if the last chunk exceeds chunk limit, split it.
func WriteHistoryIndex(db dbm.DB, batch dbm.Batch, key []byte, height uint64) error {
lastKey := HistoryIndexKey(key, LastChunkId)
bz, err := db.Get(lastKey)
if err != nil {
return err
}

m := roaring64.New()
if len(bz) > 0 {
_, err = m.ReadFrom(bytes.NewReader(bz))
if err != nil {
return err
}
}
m.Add(height)

// chunking
if err = WalkChunks64(m, ChunkLimit, func(chunk *roaring64.Bitmap, isLast bool) error {
chunkKey := lastKey
if !isLast {
chunkKey = HistoryIndexKey(key, chunk.Maximum())
}
bz, err := chunk.ToBytes()
if err != nil {
return err
}
return batch.Set(chunkKey, bz)
}); err != nil {
return err
}

return nil
}
Loading

0 comments on commit 760f02a

Please sign in to comment.