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

feat: implementing pip-15 #843

Merged
merged 36 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4e04454
feat: implement lru cache for account in store
amirvalhalla Dec 9, 2023
13902f9
feat: implement lru cache for public key in store
amirvalhalla Dec 9, 2023
6630b6f
feat: implement lru cache for transactions
amirvalhalla Dec 9, 2023
ec8a222
fix: rename print errors and addr cache name
amirvalhalla Dec 10, 2023
d9c4cdd
feat: implement recent transactions and sortition seeds cache
amirvalhalla Dec 11, 2023
c76d802
feat: implement load sortition seed and transactions and save them in…
amirvalhalla Dec 13, 2023
b323db9
fix: change rlock to lock for sortition seed
amirvalhalla Dec 13, 2023
b45cb1b
fix: load sorition seed from cache for invalid proof
amirvalhalla Dec 14, 2023
c17a629
chore: remove unused packages
amirvalhalla Dec 19, 2023
8ca67d3
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 19, 2023
10a98e1
feat: implement private configs for store
amirvalhalla Dec 21, 2023
ac96375
test: change tests based in new store with cache
amirvalhalla Dec 23, 2023
9d8cef3
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 23, 2023
fd5b778
chore: remove unused variables etc.
amirvalhalla Dec 23, 2023
0260d88
test: fix save txs write buffer
amirvalhalla Dec 23, 2023
7d0ea22
fix: race condition, dead lock and out of range index in sortiotion seed
amirvalhalla Dec 24, 2023
aaaeeb6
perf: change linkedlist to slice for better performance
amirvalhalla Dec 26, 2023
887eeca
test: testing sortition seed cache
amirvalhalla Dec 26, 2023
337f579
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 26, 2023
e86ac46
test: get more coverage for linkedmap
amirvalhalla Dec 28, 2023
cffe24f
fix: zero capacity for test linkedmap
amirvalhalla Dec 28, 2023
4591349
test: get more coverage from config/config.go
amirvalhalla Dec 28, 2023
d297c9a
fix: clone account in store/account iterates
amirvalhalla Dec 29, 2023
4f0b61c
test: refined account deep copy
amirvalhalla Dec 29, 2023
bfb3991
refactor: rename addrCache to accInCache
amirvalhalla Dec 29, 2023
d00831f
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 29, 2023
d6d19b8
feat: implement pair slice
amirvalhalla Dec 29, 2023
ef32853
docs: add comment for pairslice methods
amirvalhalla Dec 29, 2023
fbdc2f0
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 29, 2023
c6c05bc
feat: implement tripleslice
amirvalhalla Dec 29, 2023
9a2a1dc
feat: implement sortition seed
amirvalhalla Dec 30, 2023
c9d300c
test: implement tests for pair slice
amirvalhalla Dec 30, 2023
254ff40
docs: write docs for pairslice
amirvalhalla Dec 30, 2023
296a5b4
fix: lint issue
amirvalhalla Dec 30, 2023
0a4274b
Merge branch 'main' into feat-pip-15
amirvalhalla Dec 30, 2023
2e6d417
fix: clone account for cache
amirvalhalla Dec 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/pactus-project/pactus
go 1.21

require (
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
github.com/fxamacker/cbor/v2 v2.5.0
github.com/google/uuid v1.4.0
github.com/gorilla/handlers v1.5.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg=
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds=
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
Expand Down
3 changes: 2 additions & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func NewNode(genDoc *genesis.Genesis, conf *config.Config,
txPool := txpool.NewTxPool(conf.TxPool, messageCh)

// TODO implement dequeue for recent transaction
str, err := store.NewStore(conf.Store)
str, err := store.NewStore(conf.Store,
genDoc.Params().TransactionToLiveInterval, genDoc.Params().SortitionInterval)
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
Expand Down
16 changes: 6 additions & 10 deletions sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,18 +277,14 @@ func (sb *sandbox) PowerDelta() int64 {

// VerifyProof verifies proof of a sortition transaction.
func (sb *sandbox) VerifyProof(blockHeight uint32, proof sortition.Proof, val *validator.Validator) bool {
committedBlock, err := sb.store.Block(blockHeight)
if err != nil {
return false
}
// TODO: improvement:
// We can get the sortition seed without parsing the block
blk, err := committedBlock.ToBlock()
if err != nil {
sb.lk.RLock()
defer sb.lk.RUnlock()

seed := sb.store.SortitionSeed(blockHeight, sb.height+1)
if seed == nil {
return false
}
seed := blk.Header().SortitionSeed()
return sortition.VerifyProof(seed, proof, val.PublicKey(), sb.totalPower, val.Power())
return sortition.VerifyProof(*seed, proof, val.PublicKey(), sb.totalPower, val.Power())
}

func (sb *sandbox) CommitTransaction(trx *tx.Tx) {
Expand Down
75 changes: 49 additions & 26 deletions store/account.go
Original file line number Diff line number Diff line change
@@ -1,73 +1,96 @@
package store

import (
lru "github.com/hashicorp/golang-lru/v2"
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/types/account"
"github.com/pactus-project/pactus/util/logger"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)

const (
accLruCacheSize = 1024
)

type accountStore struct {
db *leveldb.DB
addressMap map[crypto.Address]*account.Account
total int32
db *leveldb.DB
addrCache *lru.Cache[crypto.Address, *account.Account]
total int32
}

func accountKey(addr crypto.Address) []byte { return append(accountPrefix, addr.Bytes()...) }

func newAccountStore(db *leveldb.DB) *accountStore {
total := int32(0)
numberMap := make(map[int32]*account.Account)
addressMap := make(map[crypto.Address]*account.Account)
addrLruCache, err := lru.New[crypto.Address, *account.Account](accLruCacheSize)
if err != nil {
logger.Panic("unable to create new instance of lru cache", "error", err)
}

r := util.BytesPrefix(accountPrefix)
iter := db.NewIterator(r, nil)
for iter.Next() {
key := iter.Key()
value := iter.Value()

acc, err := account.FromBytes(value)
if err != nil {
logger.Panic("unable to decode account", "error", err)
}

var addr crypto.Address
copy(addr[:], key[1:])

numberMap[acc.Number()] = acc
addressMap[addr] = acc
total++
}
iter.Release()

return &accountStore{
db: db,
total: total,
addressMap: addressMap,
db: db,
total: total,
addrCache: addrLruCache,
}
}

func (as *accountStore) hasAccount(addr crypto.Address) bool {
_, ok := as.addressMap[addr]
ok := as.addrCache.Contains(addr)
if !ok {
ok = tryHas(as.db, accountKey(addr))
}
return ok
}

func (as *accountStore) account(addr crypto.Address) (*account.Account, error) {
acc, ok := as.addressMap[addr]
acc, ok := as.addrCache.Get(addr)
if ok {
return acc.Clone(), nil
}

return nil, ErrNotFound
rawData, err := tryGet(as.db, accountKey(addr))
if err != nil {
return nil, err
}

acc, err = account.FromBytes(rawData)
if err != nil {
return nil, err
}

as.addrCache.Add(addr, acc)
return acc, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks dangerous to me. Why not clone it? Perhaps the tests should be refined.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I still feel that we need to review it again. We are returning an account pointer here, and it can be changed, which could potentially manipulate the cache.

}

func (as *accountStore) iterateAccounts(consumer func(crypto.Address, *account.Account) (stop bool)) {
for addr, acc := range as.addressMap {
r := util.BytesPrefix(accountPrefix)
iter := as.db.NewIterator(r, nil)
for iter.Next() {
key := iter.Key()
value := iter.Value()

acc, err := account.FromBytes(value)
if err != nil {
logger.Panic("unable to decode account", "error", err)
}

var addr crypto.Address
copy(addr[:], key[1:])

stopped := consumer(addr, acc.Clone())
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
if stopped {
return
}
}
iter.Release()
}

// This function takes ownership of the account pointer.
Expand All @@ -81,7 +104,7 @@ func (as *accountStore) updateAccount(batch *leveldb.Batch, addr crypto.Address,
if !as.hasAccount(addr) {
as.total++
}
as.addressMap[addr] = acc
as.addrCache.Add(addr, acc)

batch.Put(accountKey(addr), data)
}
8 changes: 5 additions & 3 deletions store/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestAccountBatchSaving(t *testing.T) {

t.Run("Close and load db", func(t *testing.T) {
td.store.Close()
store, _ := NewStore(td.store.config)
store, _ := NewStore(td.store.config, 8640, 17)
assert.Equal(t, store.TotalAccounts(), total)
})
}
Expand Down Expand Up @@ -93,7 +93,7 @@ func TestAccountByAddress(t *testing.T) {

t.Run("Reopen the store", func(t *testing.T) {
td.store.Close()
store, _ := NewStore(td.store.config)
store, _ := NewStore(td.store.config, 8640, 17)

acc, err := store.Account(lastAddr)
assert.NoError(t, err)
Expand Down Expand Up @@ -140,5 +140,7 @@ func TestAccountDeepCopy(t *testing.T) {

acc2, _ := td.store.Account(addr)
acc2.AddToBalance(1)
assert.NotEqual(t, td.store.accountStore.addressMap[addr].Hash(), acc2.Hash())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this line removed?

We need to make sure accInCache is equal to accInStore.


expectedAcc, _ := td.store.accountStore.addrCache.Get(addr)
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
assert.NotEqual(t, expectedAcc.Hash(), acc2.Hash())
}
42 changes: 30 additions & 12 deletions store/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (

"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/sortition"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/util"
"github.com/pactus-project/pactus/util/encoding"
"github.com/pactus-project/pactus/util/linkedlist"
"github.com/pactus-project/pactus/util/logger"
"github.com/syndtr/goleveldb/leveldb"
)
Expand All @@ -22,12 +24,16 @@ func blockHashKey(h hash.Hash) []byte {
}

type blockStore struct {
db *leveldb.DB
db *leveldb.DB
sortitionSeedCache *linkedlist.LinkedList[*sortition.VerifiableSeed]
sortitionInterval uint32
}

func newBlockStore(db *leveldb.DB) *blockStore {
func newBlockStore(db *leveldb.DB, sortitionInterval uint32) *blockStore {
return &blockStore{
db: db,
db: db,
sortitionSeedCache: linkedlist.New[*sortition.VerifiableSeed](),
sortitionInterval: sortitionInterval,
}
}

Expand Down Expand Up @@ -93,6 +99,9 @@ func (bs *blockStore) saveBlock(batch *leveldb.Batch, height uint32, blk *block.
batch.Put(blockKey, w.Bytes())
batch.Put(blockHashKey, util.Uint32ToSlice(height))

sortitionSeed := blk.Header().SortitionSeed()
bs.saveToCache(sortitionSeed)

return regs
}

Expand All @@ -113,18 +122,27 @@ func (bs *blockStore) blockHeight(h hash.Hash) uint32 {
return util.SliceToUint32(data)
}

func (bs *blockStore) hasBlock(height uint32) bool {
has, err := bs.db.Has(blockKey(height), nil)
if err != nil {
return false
func (bs *blockStore) sortitionSeed(blockHeight, currentHeight uint32) *sortition.VerifiableSeed {
index := currentHeight - blockHeight
if index > bs.sortitionInterval {
return nil
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
}
return has

index = uint32(bs.sortitionSeedCache.Length()) - index
return bs.sortitionSeedCache.Get(int(index))
}

func (bs *blockStore) hasBlock(height uint32) bool {
return tryHas(bs.db, blockKey(height))
}

func (bs *blockStore) hasPublicKey(addr crypto.Address) bool {
amirvalhalla marked this conversation as resolved.
Show resolved Hide resolved
has, err := bs.db.Has(publicKeyKey(addr), nil)
if err != nil {
return false
return tryHas(bs.db, publicKeyKey(addr))
}

func (bs *blockStore) saveToCache(sortitionSeed sortition.VerifiableSeed) {
bs.sortitionSeedCache.InsertAtTail(&sortitionSeed)
if bs.sortitionSeedCache.Length() > int(bs.sortitionInterval) {
bs.sortitionSeedCache.DeleteAtHead()
}
return has
}
2 changes: 2 additions & 0 deletions store/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/bls"
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/sortition"
"github.com/pactus-project/pactus/types/account"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/types/certificate"
Expand Down Expand Up @@ -81,6 +82,7 @@ type Reader interface {
Block(height uint32) (*CommittedBlock, error)
BlockHeight(h hash.Hash) uint32
BlockHash(height uint32) hash.Hash
SortitionSeed(currentHeight, height uint32) *sortition.VerifiableSeed
Transaction(id tx.ID) (*CommittedTx, error)
AnyRecentTransaction(id tx.ID) bool
PublicKey(addr crypto.Address) (*bls.PublicKey, error)
Expand Down
6 changes: 6 additions & 0 deletions store/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/bls"
"github.com/pactus-project/pactus/crypto/hash"
"github.com/pactus-project/pactus/sortition"
"github.com/pactus-project/pactus/types/account"
"github.com/pactus-project/pactus/types/block"
"github.com/pactus-project/pactus/types/certificate"
Expand Down Expand Up @@ -65,6 +66,11 @@ func (m *MockStore) BlockHeight(h hash.Hash) uint32 {
return 0
}

func (m *MockStore) SortitionSeed(_, _ uint32) *sortition.VerifiableSeed {
// TODO implement me
panic("implement me")
}

func (m *MockStore) PublicKey(addr crypto.Address) (*bls.PublicKey, error) {
for _, block := range m.Blocks {
for _, trx := range block.Transactions() {
Expand Down
Loading
Loading