Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ee9888e
multi thread queue
dahn510 Apr 9, 2025
b1a0d1a
queue thread config
dahn510 Apr 10, 2025
c6218d4
clarify log messages
dahn510 Apr 10, 2025
66dec99
batch send messages
dahn510 Apr 10, 2025
f0cb595
tweaking queue init
TheMarstonConnell Apr 10, 2025
bb2f201
batch queue
dahn510 Apr 14, 2025
c8abaad
clean up
dahn510 Apr 15, 2025
701304f
channel queue
dahn510 Apr 16, 2025
125c590
replace stop channel with msg output channel
dahn510 Apr 21, 2025
27313d3
send msg to free worker channel
dahn510 Apr 22, 2025
bf12c63
clean up compile errors
dahn510 Apr 24, 2025
5ae1615
tx timer and batch size config
dahn510 Apr 24, 2025
bdf7f77
fix build error
dahn510 Apr 24, 2025
c59f39c
Merge branch 'main' of github.com:JackalLabs/sequoia into multi-queue
dahn510 Apr 24, 2025
534f3e4
max retry error
dahn510 Apr 28, 2025
fbfa225
test max retry
dahn510 Apr 28, 2025
2eed453
worker test
dahn510 May 1, 2025
f78e4b8
mock auth query client
dahn510 May 1, 2025
d200bb4
mock auth client
dahn510 May 2, 2025
f5539f1
mock tx and rpc clients
dahn510 May 3, 2025
dcf3ef2
lint cleanup
dahn510 May 3, 2025
d84c967
test batch full send
dahn510 May 5, 2025
bd6c12e
clean up
dahn510 May 6, 2025
5e06959
wait for workers to terminate
dahn510 May 8, 2025
777f7b1
queue and worker tests
dahn510 May 8, 2025
9fdb0c4
bench pool Add
dahn510 May 8, 2025
8bf2b0f
fake and mock query client
dahn510 May 14, 2025
a89e0db
fake clients
dahn510 May 15, 2025
6f98006
fake methods used by the wallet
dahn510 May 15, 2025
529bffc
create new app with options
dahn510 May 15, 2025
d539b51
add test_mode flag to start cmd
dahn510 May 15, 2025
f1179c5
use query client from app
dahn510 May 15, 2025
e5abf22
add query client to api handler
dahn510 May 15, 2025
5b4afbe
fake query responses to start app
dahn510 May 17, 2025
d4f2d41
fix blockstore key unmarshal error
dahn510 May 20, 2025
1b44ad2
pass query client to stray manager
dahn510 May 21, 2025
186dbf9
tx decoder
dahn510 May 27, 2025
d5cd8df
decode tx sent to fake rpc client
dahn510 May 27, 2025
c216924
register feegrant interface
dahn510 May 28, 2025
b1637f7
fake query file
dahn510 Jun 3, 2025
bd31f2d
use passed query client
dahn510 Jun 3, 2025
166706b
fix race condition of file prove counter
dahn510 Jun 3, 2025
641f4ff
Merge branch 'main' into multi-queue
dahn510 Jun 4, 2025
7198fe3
create offset wallet from main wallet
dahn510 Jun 4, 2025
bbf3ada
fix hands and worker wallet collision
dahn510 Jun 4, 2025
52264ba
Merge branch 'main' into multi-queue
TheMarstonConnell Jun 6, 2025
57fa87d
lint
TheMarstonConnell Jun 6, 2025
ebe4c04
gitignore linting
TheMarstonConnell Jun 9, 2025
a199413
mem leaks maybe?
TheMarstonConnell Jun 9, 2025
4547d2e
Merge pull request #127 from JackalLabs/marston/mem-leaks
dahn510 Jun 9, 2025
bd39aa3
remove wallet offset from new hand
dahn510 Jun 12, 2025
a1aa8ac
fix wrong error reference returned
dahn510 Jun 12, 2025
52c60ed
fix account sequence mismatch
dahn510 Jun 12, 2025
a70c565
fix nil pointer dereference
dahn510 Jun 16, 2025
c9a1a0b
fix provider not found init problem
dahn510 Jun 23, 2025
7d35001
return init provider on chain err
dahn510 Jun 23, 2025
fbd533d
fix sequence mismatch error
dahn510 Jun 30, 2025
210c22c
update test
dahn510 Jul 11, 2025
28579bc
lint
dahn510 Jul 11, 2025
9130089
Merge branch 'main' into multi-queue
dahn510 Jul 11, 2025
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
35 changes: 26 additions & 9 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type ChainConfig struct {
}

type Config struct {
QueueInterval int64 `yaml:"queue_interval" mapstructure:"queue_interval"`
ProofInterval int64 `yaml:"proof_interval" mapstructure:"proof_interval"`
QueueConfig QueueConfig `yaml:"queue_config" mapstructure:"queue_config"`
StrayManagerCfg StrayManagerConfig `yaml:"stray_manager" mapstructure:"stray_manager"`
ChainCfg ChainConfig `yaml:"chain_config" mapstructure:"chain_config"`
Ip string `yaml:"domain" mapstructure:"domain"`
Expand All @@ -35,10 +35,6 @@ type Config struct {
BlockStoreConfig BlockStoreConfig `yaml:"block_store_config" mapstructure:"block_store_config"`
}

func DefaultQueueInterval() int64 {
return 10
}

func DefaultProofInterval() int64 {
return 120
}
Expand Down Expand Up @@ -140,10 +136,32 @@ func DefaultChainConfig() ChainConfig {
}
}

type QueueConfig struct {
// seconds
QueueInterval int64 `yaml:"queue_interval" mapstructure:"queue_interval"`
QueueThreads int8 `yaml:"queue_threads" mapstructure:"queue_threads"`
// resend tx if network isn't responding
MaxRetryAttempt int8 `yaml:"max_retry_attempt" mapstructure:"max_retry_attempt"`
// group individual messages into one tx
TxBatchSize int8 `yaml:"tx_batch_size" mapstructure:"tx_batch_size"`
// worker's own message pool, set this value > TxBatchSize
WorkerQueueSize int16 `yaml:"worker_queue_size" mapstructure:"worker_queue_size"`
}

func DefaultQueueConfig() QueueConfig {
return QueueConfig{
QueueInterval: 10,
QueueThreads: 5,
MaxRetryAttempt: 100,
TxBatchSize: 45,
WorkerQueueSize: 100,
Comment thread
dahn510 marked this conversation as resolved.
Outdated
}
}

func DefaultConfig() *Config {
return &Config{
QueueInterval: DefaultQueueInterval(),
ProofInterval: DefaultProofInterval(),
QueueConfig: DefaultQueueConfig(),
StrayManagerCfg: DefaultStrayManagerConfig(),
ChainCfg: DefaultChainConfig(),
Ip: DefaultIP(),
Expand All @@ -156,8 +174,7 @@ func DefaultConfig() *Config {
}

func (c Config) MarshalZerologObject(e *zerolog.Event) {
e.Int64("QueueInterval", c.QueueInterval).
Int64("ProofInterval", c.ProofInterval).
e.Int64("ProofInterval", c.ProofInterval).
Int64("StrayCheckInterval", c.StrayManagerCfg.CheckInterval).
Int64("StrayRefreshInterval", c.StrayManagerCfg.RefreshInterval).
Int("StrayHandCount", c.StrayManagerCfg.HandCount).
Expand All @@ -176,7 +193,7 @@ func (c Config) MarshalZerologObject(e *zerolog.Event) {
}

func init() {
viper.SetDefault("QueueInterval", DefaultQueueInterval())
viper.SetDefault("QueueConfig", DefaultQueueConfig())
viper.SetDefault("ProofInterval", DefaultProofInterval())
viper.SetDefault("StrayManagerCfg", DefaultStrayManagerConfig())
viper.SetDefault("ChainCfg", DefaultChainConfig())
Expand Down
8 changes: 7 additions & 1 deletion core/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,13 @@ func (a *App) Start() error {
return err
}

a.q = queue.NewQueue(a.wallet, cfg.QueueInterval)
refreshInterval := time.Second * time.Duration(cfg.QueueInterval)
a.q, err = queue.NewQueue(a.wallet, refreshInterval, cfg.QueueThreads)
if err != nil {
log.Error().Err(err).Msg("failed to initialize Queue module")
return err
}

Comment on lines +284 to +302

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Slice bound uses int8; code will not compile

queueWallets := offsetWallets[:cfg.QueueConfig.QueueThreads] fails when QueueThreads is int8:

invalid slice index cfg.QueueConfig.QueueThreads (type int8)

Cast to int once and reuse:

-threadCount := cfg.QueueConfig.QueueThreads
-queueWallets := offsetWallets[:threadCount]
+threadCount := int(cfg.QueueConfig.QueueThreads)
+queueWallets := offsetWallets[:threadCount]

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In core/app.go around lines 284 to 295, the slice operation uses
cfg.QueueConfig.QueueThreads which is of type int8, causing a compile error
because slice indices must be int. Fix this by casting
cfg.QueueConfig.QueueThreads to int before using it as a slice index, and reuse
the casted int value to avoid repeated conversions.

go a.q.Listen()

prover := proofs.NewProver(a.wallet, a.q, a.fileSystem, cfg.ProofInterval, cfg.ProofThreads, int(params.ChunkSize))
Expand Down
14 changes: 8 additions & 6 deletions proofs/proofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
)

func GenerateMerkleProof(tree *merkletree.MerkleTree, index int, item []byte) (bool, *merkletree.Proof, error) {
log.Debug().Msg(fmt.Sprintf("Generating Merkle proof for %d", index))
log.Debug().Msg(fmt.Sprintf("Generating Merkle proof for index: %d", index))

h := sha256.New()
_, err := fmt.Fprintf(h, "%d%x", index, item)
Expand All @@ -56,15 +56,15 @@ func GenerateMerkleProof(tree *merkletree.MerkleTree, index int, item []byte) (b
//
// returns proof, item and error
func GenProof(io FileSystem, merkle []byte, owner string, start int64, block int, chunkSize int, proofType int64) ([]byte, []byte, error) {
log.Debug().Msg(fmt.Sprintf("About to generate merkle proof for file: %x", merkle))

tree, chunk, err := io.GetFileTreeByChunk(merkle, owner, start, block, chunkSize, proofType)
if err != nil {
e := fmt.Errorf("cannot get chunk for %x at %d | %w", merkle, block, err)
log.Error().Err(e)
return nil, nil, e
}

log.Debug().Msg(fmt.Sprintf("About to generate merkle proof for %x", merkle))

valid, proof, err := GenerateMerkleProof(tree, block, chunk)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -220,11 +220,12 @@ func (p *Prover) Start() {
}

time.Sleep(time.Millisecond * 1000) // pauses for one third of a second
// sleep until next proving cycle
if !p.processed.Add(time.Second * time.Duration(p.interval)).Before(time.Now()) {
continue
}

log.Debug().Msg("Starting proof cycle...")
log.Debug().Time("start at", p.processed).Msg("Starting proof cycle...")

abciInfo, err := p.wallet.Client.RPCClient.ABCIInfo(context.Background())
if err != nil {
Expand All @@ -241,15 +242,16 @@ func (p *Prover) Start() {

time.Sleep(time.Second * 5)
}
log.Debug().Msg(fmt.Sprintf("proving: %x", merkle))
log.Debug().Hex("merkle", merkle).Str("owner", owner).Int64("start", start).Msg("proving file")
filesProving.Inc()
p.Inc()
go p.wrapPostProof(merkle, owner, start, height, t)
})
if err != nil {
log.Error().Err(err)
log.Error().Err(err).Msg("something went wrong while processing files in this proving cycle")
}

log.Debug().Time("finish at", time.Now()).TimeDiff("duration", time.Now(), p.processed).Msg("End of proof cycle")
p.processed = time.Now()
}
log.Info().Msg("Prover module stopped")
Expand Down
2 changes: 1 addition & 1 deletion proofs/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type Prover struct {
running bool
wallet *wallet.Wallet
q *queue.Queue
q queue.Queue
processed time.Time
interval int64
io FileSystem
Expand Down
182 changes: 182 additions & 0 deletions queue/pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package queue

import (
"context"
"errors"
"reflect"
"slices"
"sync"

"github.com/JackalLabs/sequoia/config"
"github.com/cosmos/cosmos-sdk/types"

walletTypes "github.com/desmos-labs/cosmos-go-wallet/types"
"github.com/desmos-labs/cosmos-go-wallet/wallet"

storageTypes "github.com/jackalLabs/canine-chain/v4/x/storage/types"

"github.com/rs/zerolog/log"
)

var _ Queue = &Pool{}

type Pool struct {
workers []*worker
workerChannels []chan *Message // worker id should correspond to index of this
wallet *wallet.Wallet
}

func NewPool(wallet *wallet.Wallet, config config.QueueConfig) (*Pool, error) {
workerWallets, err := initAuthClaimers(wallet, config.QueueThreads)
if err != nil {
return nil, errors.Join(errors.New("failed to initialize auth claimers"), err)
}

workers, workerChannels := createWorkers(workerWallets, config.MaxRetryAttempt)
if workers == nil {
panic("no workers created")
}
if workerChannels == nil {
panic("no worker channels created")
}
if len(workerChannels) != len(workers) {
panic("size of workers does not match size of worker channels")
}

pool := &Pool{
wallet: wallet,
workers: workers,
workerChannels: workerChannels,
}

return pool, nil
}
Comment on lines +37 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove error return or add error handling.

The function always returns nil for the error. Either remove the error return from the signature or add proper error handling for potential failures (e.g., validate inputs, handle errors from worker creation).

-func NewPool(main *wallet.Wallet, queryClient storageTypes.QueryClient, workerWallets []*wallet.Wallet, config config.QueueConfig) (*Pool, error) {
+func NewPool(main *wallet.Wallet, queryClient storageTypes.QueryClient, workerWallets []*wallet.Wallet, config config.QueueConfig) *Pool {
    // ... existing code ...
-   return pool, nil
+   return pool
}
🤖 Prompt for AI Agents
In queue/pool.go around lines 37 to 38, the function returns an error value that
is always nil, which is unnecessary. Either remove the error return type from
the function signature if no errors can occur, or add proper error handling by
validating inputs and handling any errors from worker creation before returning.
Adjust the return statements accordingly to match the updated signature or error
handling logic.

Comment on lines +26 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused queryClient parameter or implement its usage.

The queryClient parameter is passed to the constructor but never used. Either remove it if it's not needed, or implement its intended usage within the Pool or pass it to the workers.

If the parameter is not needed:

-func NewPool(main *wallet.Wallet, queryClient storageTypes.QueryClient, workerWallets []*wallet.Wallet, config config.QueueConfig) (*Pool, error) {
+func NewPool(main *wallet.Wallet, workerWallets []*wallet.Wallet, config config.QueueConfig) (*Pool, error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func NewPool(main *wallet.Wallet, queryClient storageTypes.QueryClient, workerWallets []*wallet.Wallet, config config.QueueConfig) (*Pool, error) {
root, rootQueue := createWorkers([]*wallet.Wallet{main}, int(config.TxTimer), int(config.TxBatchSize), config.MaxRetryAttempt)
workers, workerChannels := createWorkers(workerWallets, int(config.TxTimer), int(config.TxBatchSize), config.MaxRetryAttempt)
pool := &Pool{
root: root[0],
rootQueue: rootQueue[0],
offsets: workers,
offsetQueue: workerChannels,
}
return pool, nil
}
func NewPool(main *wallet.Wallet, workerWallets []*wallet.Wallet, config config.QueueConfig) (*Pool, error) {
root, rootQueue := createWorkers([]*wallet.Wallet{main}, int(config.TxTimer), int(config.TxBatchSize), config.MaxRetryAttempt)
workers, workerChannels := createWorkers(workerWallets, int(config.TxTimer), int(config.TxBatchSize), config.MaxRetryAttempt)
pool := &Pool{
root: root[0],
rootQueue: rootQueue[0],
offsets: workers,
offsetQueue: workerChannels,
}
return pool, nil
}
🤖 Prompt for AI Agents
In queue/pool.go between lines 26 and 38, the queryClient parameter is declared
in the NewPool function signature but never used inside the function. To fix
this, either remove the queryClient parameter from the NewPool function
signature if it is not required, or if it is intended to be used, integrate it
properly by passing it to the Pool struct or to the workers during their
creation. Ensure the function signature and implementation are consistent.


func (p *Pool) Stop() {
for _, c := range p.workerChannels {
close(c)
}
}

func (p *Pool) Listen() {
for _, w := range p.workers {
go w.start()
}
}

func (p *Pool) Add(msg types.Msg) (*Message, *sync.WaitGroup) {
var wg sync.WaitGroup
wg.Add(1)
m := &Message{
msg: msg,
wg: &wg,
err: nil,
res: nil,
msgIndex: -1, // no longer relevant(?)
}

_ = p.sendToAny(m)

return m, &wg
}

func (p *Pool) sendToAny(msg *Message) (workerId int) {
set := make([]reflect.SelectCase, 0, len(p.workerChannels))
for _, ch := range p.workerChannels {
set = append(set, reflect.SelectCase{
Dir: reflect.SelectSend,
Chan: reflect.ValueOf(ch),
Send: reflect.ValueOf(msg),
})
}

// blocks until a worker is free
to, _, _ := reflect.Select(set)
return to
}

func createWorkers(workerWallets []*wallet.Wallet, maxRetryAttempt int8) ([]*worker, []chan *Message) {
wChannels := make([]chan *Message, 0, len(workerWallets))
for _ = range len(workerWallets) {
wChannels = append(wChannels, make(chan *Message))
}

workers := make([]*worker, 0, len(workerWallets))
for i, w := range workerWallets {
worker := newWorker(int8(i), w, maxRetryAttempt, wChannels[i])
workers = append(workers, worker)
}

return workers, wChannels
}

func initAuthClaimers(wallet *wallet.Wallet, count int8) (workerWallets []*wallet.Wallet, err error) {
query := &storageTypes.QueryProvider{
Address: wallet.AccAddress(),
}

cl := storageTypes.NewQueryClient(wallet.Client.GRPCConn)
res, err := cl.Provider(context.Background(), query)
if err != nil {
return nil, errors.Join(errors.New("unable to query provider auth claimers"), err)
}
claimers := res.Provider.AuthClaimers

for i := range count {
workerWallet := newOffsetWallet(wallet, int(i))
if !slices.Contains(claimers, workerWallet.AccAddress()) {
err := addClaimer(wallet, workerWallet)
if err != nil {
return nil, errors.Join(errors.New("failed to add claimer on chain"), err)
}
}
workerWallets = append(workerWallets, workerWallet)
}

return workerWallets, nil
}

func addClaimer(main *wallet.Wallet, claimer *wallet.Wallet) error {
msg := storageTypes.NewMsgAddClaimer(main.AccAddress(), claimer.AccAddress())
txData := walletTypes.NewTransactionData(msg).WithFeeAuto().WithGasAuto()

res, err := main.BroadcastTxCommit(txData)
if err != nil {
return errors.Join(errors.New("unable to broadcast MsgAddClaimer"), err)
}

log.Info().Msg(res.TxHash)

return nil
}

func newOffsetWallet(main *wallet.Wallet, index int) *wallet.Wallet {
w, err := main.CloneWalletOffset(byte(index + 1))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential byte overflow in wallet offset calculation.

Casting index + 1 to byte could overflow if index >= 255, resulting in unexpected wallet offsets.

-w, err := main.CloneWalletOffset(byte(index + 1))
+if index >= 255 {
+    panic("wallet index too large: maximum 254 supported")
+}
+w, err := main.CloneWalletOffset(byte(index + 1))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
w, err := main.CloneWalletOffset(byte(index + 1))
if index >= 255 {
panic("wallet index too large: maximum 254 supported")
}
w, err := main.CloneWalletOffset(byte(index + 1))
🤖 Prompt for AI Agents
In queue/pool.go at line 178, casting the expression `index + 1` directly to
byte risks overflow when index is 255 or greater, causing incorrect wallet
offsets. To fix this, ensure the value passed to CloneWalletOffset is safely
bounded within the byte range or use a larger integer type if supported. Add
checks or constraints to prevent overflow before casting or refactor the
function to accept an integer type that can handle larger values.

if err != nil {
panic(err)
}
return w
}

func newBuffer(in <-chan *Message) <-chan *Message {
// From: https://blogtitle.github.io/go-advanced-concurrency-patterns-part-4-unlimited-buffer-channels/
var buf []*Message
out := make(chan *Message)

go func() {
defer close(out)
for msg := range in {
select {
case out <- msg:
default:
buf = append(buf, msg)
}
}

for _, v := range buf {
out <- v
}
}()

return out
}
Loading