diff --git a/mocks/constructor/coordinator/handler.go b/mocks/constructor/coordinator/handler.go deleted file mode 100644 index 90f0b3d11..000000000 --- a/mocks/constructor/coordinator/handler.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package coordinator - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - types "github.com/coinbase/rosetta-sdk-go/types" -) - -// Handler is an autogenerated mock type for the Handler type -type Handler struct { - mock.Mock -} - -// TransactionCreated provides a mock function with given fields: _a0, _a1, _a2 -func (_m *Handler) TransactionCreated(_a0 context.Context, _a1 string, _a2 *types.TransactionIdentifier) error { - ret := _m.Called(_a0, _a1, _a2) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, *types.TransactionIdentifier) error); ok { - r0 = rf(_a0, _a1, _a2) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/mocks/constructor/coordinator/helper.go b/mocks/constructor/coordinator/helper.go deleted file mode 100644 index c8eedd8d1..000000000 --- a/mocks/constructor/coordinator/helper.go +++ /dev/null @@ -1,483 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package coordinator - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - keys "github.com/coinbase/rosetta-sdk-go/keys" - database "github.com/coinbase/rosetta-sdk-go/storage/database" - types "github.com/coinbase/rosetta-sdk-go/types" -) - -// Helper is an autogenerated mock type for the Helper type -type Helper struct { - mock.Mock -} - -// AllAccounts provides a mock function with given fields: _a0, _a1 -func (_m *Helper) AllAccounts(_a0 context.Context, _a1 database.Transaction) ([]*types.AccountIdentifier, error) { - ret := _m.Called(_a0, _a1) - - var r0 []*types.AccountIdentifier - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction) []*types.AccountIdentifier); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.AccountIdentifier) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Balance provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Balance(_a0 context.Context, _a1 database.Transaction, _a2 *types.AccountIdentifier, _a3 *types.Currency) (*types.Amount, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 *types.Amount - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, *types.AccountIdentifier, *types.Currency) *types.Amount); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.Amount) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, *types.AccountIdentifier, *types.Currency) error); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Broadcast provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7 -func (_m *Helper) Broadcast(_a0 context.Context, _a1 database.Transaction, _a2 string, _a3 *types.NetworkIdentifier, _a4 []*types.Operation, _a5 *types.TransactionIdentifier, _a6 string, _a7 int64) error { - ret := _m.Called(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, string, *types.NetworkIdentifier, []*types.Operation, *types.TransactionIdentifier, string, int64) error); ok { - r0 = rf(_a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// BroadcastAll provides a mock function with given fields: _a0 -func (_m *Helper) BroadcastAll(_a0 context.Context) error { - ret := _m.Called(_a0) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Coins provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Coins(_a0 context.Context, _a1 database.Transaction, _a2 *types.AccountIdentifier, _a3 *types.Currency) ([]*types.Coin, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 []*types.Coin - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, *types.AccountIdentifier, *types.Currency) []*types.Coin); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.Coin) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, *types.AccountIdentifier, *types.Currency) error); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Combine provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Combine(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 string, _a3 []*types.Signature) (string, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 string - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, string, []*types.Signature) string); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, string, []*types.Signature) error); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DatabaseTransaction provides a mock function with given fields: _a0 -func (_m *Helper) DatabaseTransaction(_a0 context.Context) database.Transaction { - ret := _m.Called(_a0) - - var r0 database.Transaction - if rf, ok := ret.Get(0).(func(context.Context) database.Transaction); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(database.Transaction) - } - } - - return r0 -} - -// Derive provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Derive(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 *types.PublicKey, _a3 map[string]interface{}) (*types.AccountIdentifier, map[string]interface{}, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 *types.AccountIdentifier - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, *types.PublicKey, map[string]interface{}) *types.AccountIdentifier); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.AccountIdentifier) - } - } - - var r1 map[string]interface{} - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, *types.PublicKey, map[string]interface{}) map[string]interface{}); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(map[string]interface{}) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, *types.NetworkIdentifier, *types.PublicKey, map[string]interface{}) error); ok { - r2 = rf(_a0, _a1, _a2, _a3) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetBlob provides a mock function with given fields: ctx, dbTx, key -func (_m *Helper) GetBlob(ctx context.Context, dbTx database.Transaction, key string) (bool, []byte, error) { - ret := _m.Called(ctx, dbTx, key) - - var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, string) bool); ok { - r0 = rf(ctx, dbTx, key) - } else { - r0 = ret.Get(0).(bool) - } - - var r1 []byte - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, string) []byte); ok { - r1 = rf(ctx, dbTx, key) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]byte) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, database.Transaction, string) error); ok { - r2 = rf(ctx, dbTx, key) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetKey provides a mock function with given fields: _a0, _a1, _a2 -func (_m *Helper) GetKey(_a0 context.Context, _a1 database.Transaction, _a2 *types.AccountIdentifier) (*keys.KeyPair, error) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *keys.KeyPair - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, *types.AccountIdentifier) *keys.KeyPair); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*keys.KeyPair) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, *types.AccountIdentifier) error); ok { - r1 = rf(_a0, _a1, _a2) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Hash provides a mock function with given fields: _a0, _a1, _a2 -func (_m *Helper) Hash(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 string) (*types.TransactionIdentifier, error) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *types.TransactionIdentifier - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, string) *types.TransactionIdentifier); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.TransactionIdentifier) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, string) error); ok { - r1 = rf(_a0, _a1, _a2) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// HeadBlockExists provides a mock function with given fields: _a0 -func (_m *Helper) HeadBlockExists(_a0 context.Context) bool { - ret := _m.Called(_a0) - - var r0 bool - if rf, ok := ret.Get(0).(func(context.Context) bool); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// LockedAccounts provides a mock function with given fields: _a0, _a1 -func (_m *Helper) LockedAccounts(_a0 context.Context, _a1 database.Transaction) ([]*types.AccountIdentifier, error) { - ret := _m.Called(_a0, _a1) - - var r0 []*types.AccountIdentifier - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction) []*types.AccountIdentifier); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.AccountIdentifier) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Metadata provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Metadata(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 map[string]interface{}, _a3 []*types.PublicKey) (map[string]interface{}, []*types.Amount, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 map[string]interface{} - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, map[string]interface{}, []*types.PublicKey) map[string]interface{}); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]interface{}) - } - } - - var r1 []*types.Amount - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, map[string]interface{}, []*types.PublicKey) []*types.Amount); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]*types.Amount) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, *types.NetworkIdentifier, map[string]interface{}, []*types.PublicKey) error); ok { - r2 = rf(_a0, _a1, _a2, _a3) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// Parse provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Parse(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 bool, _a3 string) ([]*types.Operation, []*types.AccountIdentifier, map[string]interface{}, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 []*types.Operation - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, bool, string) []*types.Operation); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.Operation) - } - } - - var r1 []*types.AccountIdentifier - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, bool, string) []*types.AccountIdentifier); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]*types.AccountIdentifier) - } - } - - var r2 map[string]interface{} - if rf, ok := ret.Get(2).(func(context.Context, *types.NetworkIdentifier, bool, string) map[string]interface{}); ok { - r2 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(2) != nil { - r2 = ret.Get(2).(map[string]interface{}) - } - } - - var r3 error - if rf, ok := ret.Get(3).(func(context.Context, *types.NetworkIdentifier, bool, string) error); ok { - r3 = rf(_a0, _a1, _a2, _a3) - } else { - r3 = ret.Error(3) - } - - return r0, r1, r2, r3 -} - -// Payloads provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 -func (_m *Helper) Payloads(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 []*types.Operation, _a3 map[string]interface{}, _a4 []*types.PublicKey) (string, []*types.SigningPayload, error) { - ret := _m.Called(_a0, _a1, _a2, _a3, _a4) - - var r0 string - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}, []*types.PublicKey) string); ok { - r0 = rf(_a0, _a1, _a2, _a3, _a4) - } else { - r0 = ret.Get(0).(string) - } - - var r1 []*types.SigningPayload - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}, []*types.PublicKey) []*types.SigningPayload); ok { - r1 = rf(_a0, _a1, _a2, _a3, _a4) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]*types.SigningPayload) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}, []*types.PublicKey) error); ok { - r2 = rf(_a0, _a1, _a2, _a3, _a4) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// Preprocess provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) Preprocess(_a0 context.Context, _a1 *types.NetworkIdentifier, _a2 []*types.Operation, _a3 map[string]interface{}) (map[string]interface{}, []*types.AccountIdentifier, error) { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 map[string]interface{} - if rf, ok := ret.Get(0).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}) map[string]interface{}); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]interface{}) - } - } - - var r1 []*types.AccountIdentifier - if rf, ok := ret.Get(1).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}) []*types.AccountIdentifier); ok { - r1 = rf(_a0, _a1, _a2, _a3) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).([]*types.AccountIdentifier) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, *types.NetworkIdentifier, []*types.Operation, map[string]interface{}) error); ok { - r2 = rf(_a0, _a1, _a2, _a3) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// SetBlob provides a mock function with given fields: ctx, dbTx, key, value -func (_m *Helper) SetBlob(ctx context.Context, dbTx database.Transaction, key string, value []byte) error { - ret := _m.Called(ctx, dbTx, key, value) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, string, []byte) error); ok { - r0 = rf(ctx, dbTx, key, value) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Sign provides a mock function with given fields: _a0, _a1 -func (_m *Helper) Sign(_a0 context.Context, _a1 []*types.SigningPayload) ([]*types.Signature, error) { - ret := _m.Called(_a0, _a1) - - var r0 []*types.Signature - if rf, ok := ret.Get(0).(func(context.Context, []*types.SigningPayload) []*types.Signature); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*types.Signature) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, []*types.SigningPayload) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// StoreKey provides a mock function with given fields: _a0, _a1, _a2, _a3 -func (_m *Helper) StoreKey(_a0 context.Context, _a1 database.Transaction, _a2 *types.AccountIdentifier, _a3 *keys.KeyPair) error { - ret := _m.Called(_a0, _a1, _a2, _a3) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, *types.AccountIdentifier, *keys.KeyPair) error); ok { - r0 = rf(_a0, _a1, _a2, _a3) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/mocks/constructor/coordinator/job_storage.go b/mocks/constructor/coordinator/job_storage.go deleted file mode 100644 index 9ce60d4f3..000000000 --- a/mocks/constructor/coordinator/job_storage.go +++ /dev/null @@ -1,130 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package coordinator - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - job "github.com/coinbase/rosetta-sdk-go/constructor/job" - database "github.com/coinbase/rosetta-sdk-go/storage/database" -) - -// JobStorage is an autogenerated mock type for the JobStorage type -type JobStorage struct { - mock.Mock -} - -// Broadcasting provides a mock function with given fields: _a0, _a1 -func (_m *JobStorage) Broadcasting(_a0 context.Context, _a1 database.Transaction) ([]*job.Job, error) { - ret := _m.Called(_a0, _a1) - - var r0 []*job.Job - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction) []*job.Job); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*job.Job) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Get provides a mock function with given fields: _a0, _a1, _a2 -func (_m *JobStorage) Get(_a0 context.Context, _a1 database.Transaction, _a2 string) (*job.Job, error) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *job.Job - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, string) *job.Job); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*job.Job) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, string) error); ok { - r1 = rf(_a0, _a1, _a2) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Processing provides a mock function with given fields: _a0, _a1, _a2 -func (_m *JobStorage) Processing(_a0 context.Context, _a1 database.Transaction, _a2 string) ([]*job.Job, error) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 []*job.Job - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, string) []*job.Job); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*job.Job) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, string) error); ok { - r1 = rf(_a0, _a1, _a2) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Ready provides a mock function with given fields: _a0, _a1 -func (_m *JobStorage) Ready(_a0 context.Context, _a1 database.Transaction) ([]*job.Job, error) { - ret := _m.Called(_a0, _a1) - - var r0 []*job.Job - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction) []*job.Job); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*job.Job) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Update provides a mock function with given fields: _a0, _a1, _a2 -func (_m *JobStorage) Update(_a0 context.Context, _a1 database.Transaction, _a2 *job.Job) (string, error) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 string - if rf, ok := ret.Get(0).(func(context.Context, database.Transaction, *job.Job) string); ok { - r0 = rf(_a0, _a1, _a2) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, database.Transaction, *job.Job) error); ok { - r1 = rf(_a0, _a1, _a2) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/mocks/constructor/worker/helper.go b/mocks/constructor/worker/helper.go index 30f5361f0..c1adc2729 100644 --- a/mocks/constructor/worker/helper.go +++ b/mocks/constructor/worker/helper.go @@ -1,14 +1,15 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package worker import ( context "context" - mock "github.com/stretchr/testify/mock" - keys "github.com/coinbase/rosetta-sdk-go/keys" database "github.com/coinbase/rosetta-sdk-go/storage/database" + + mock "github.com/stretchr/testify/mock" + types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/reconciler/handler.go b/mocks/reconciler/handler.go index 8699da453..b20a58516 100644 --- a/mocks/reconciler/handler.go +++ b/mocks/reconciler/handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package reconciler diff --git a/mocks/reconciler/helper.go b/mocks/reconciler/helper.go index fcf72d67c..b149a208e 100644 --- a/mocks/reconciler/helper.go +++ b/mocks/reconciler/helper.go @@ -1,13 +1,13 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package reconciler import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/reconciler/option.go b/mocks/reconciler/option.go new file mode 100644 index 000000000..da1c9a9da --- /dev/null +++ b/mocks/reconciler/option.go @@ -0,0 +1,18 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package reconciler + +import ( + reconciler "github.com/coinbase/rosetta-sdk-go/reconciler" + mock "github.com/stretchr/testify/mock" +) + +// Option is an autogenerated mock type for the Option type +type Option struct { + mock.Mock +} + +// Execute provides a mock function with given fields: r +func (_m *Option) Execute(r *reconciler.Reconciler) { + _m.Called(r) +} diff --git a/mocks/storage/database/badger_option.go b/mocks/storage/database/badger_option.go new file mode 100644 index 000000000..37b0638bf --- /dev/null +++ b/mocks/storage/database/badger_option.go @@ -0,0 +1,18 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package database + +import ( + database "github.com/coinbase/rosetta-sdk-go/storage/database" + mock "github.com/stretchr/testify/mock" +) + +// BadgerOption is an autogenerated mock type for the BadgerOption type +type BadgerOption struct { + mock.Mock +} + +// Execute provides a mock function with given fields: b +func (_m *BadgerOption) Execute(b *database.BadgerDatabase) { + _m.Called(b) +} diff --git a/mocks/storage/database/commit_worker.go b/mocks/storage/database/commit_worker.go new file mode 100644 index 000000000..cd5eb3718 --- /dev/null +++ b/mocks/storage/database/commit_worker.go @@ -0,0 +1,28 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package database + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// CommitWorker is an autogenerated mock type for the CommitWorker type +type CommitWorker struct { + mock.Mock +} + +// Execute provides a mock function with given fields: _a0 +func (_m *CommitWorker) Execute(_a0 context.Context) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/mocks/storage/database/database.go b/mocks/storage/database/database.go index 5b1b61854..af71114b4 100644 --- a/mocks/storage/database/database.go +++ b/mocks/storage/database/database.go @@ -1,14 +1,14 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package database import ( context "context" - mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" encoder "github.com/coinbase/rosetta-sdk-go/storage/encoder" + + mock "github.com/stretchr/testify/mock" ) // Database is an autogenerated mock type for the Database type diff --git a/mocks/storage/database/transaction.go b/mocks/storage/database/transaction.go index 269f55c48..a874ffcc1 100644 --- a/mocks/storage/database/transaction.go +++ b/mocks/storage/database/transaction.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package database diff --git a/mocks/storage/modules/balance_storage_handler.go b/mocks/storage/modules/balance_storage_handler.go index ec9d41183..903789bee 100644 --- a/mocks/storage/modules/balance_storage_handler.go +++ b/mocks/storage/modules/balance_storage_handler.go @@ -1,14 +1,15 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" mock "github.com/stretchr/testify/mock" parser "github.com/coinbase/rosetta-sdk-go/parser" - database "github.com/coinbase/rosetta-sdk-go/storage/database" + types "github.com/coinbase/rosetta-sdk-go/types" ) @@ -46,11 +47,11 @@ func (_m *BalanceStorageHandler) AccountsSeen(ctx context.Context, dbTx database } // BlockAdded provides a mock function with given fields: ctx, block, changes -func (_m *BalanceStorageHandler) BlockAdded(ctx context.Context, block *types.Block, changes []*parser.BalanceChange) error { +func (_m *BalanceStorageHandler) BlockAdded(ctx context.Context, block *types.Block, changes []*parser.BalanceSeqChange) error { ret := _m.Called(ctx, block, changes) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Block, []*parser.BalanceChange) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.Block, []*parser.BalanceSeqChange) error); ok { r0 = rf(ctx, block, changes) } else { r0 = ret.Error(0) @@ -60,11 +61,11 @@ func (_m *BalanceStorageHandler) BlockAdded(ctx context.Context, block *types.Bl } // BlockRemoved provides a mock function with given fields: ctx, block, changes -func (_m *BalanceStorageHandler) BlockRemoved(ctx context.Context, block *types.Block, changes []*parser.BalanceChange) error { +func (_m *BalanceStorageHandler) BlockRemoved(ctx context.Context, block *types.Block, changes []*parser.BalanceSeqChange) error { ret := _m.Called(ctx, block, changes) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Block, []*parser.BalanceChange) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.Block, []*parser.BalanceSeqChange) error); ok { r0 = rf(ctx, block, changes) } else { r0 = ret.Error(0) diff --git a/mocks/storage/modules/balance_storage_helper.go b/mocks/storage/modules/balance_storage_helper.go index b65e50d2f..80c9ffbe3 100644 --- a/mocks/storage/modules/balance_storage_helper.go +++ b/mocks/storage/modules/balance_storage_helper.go @@ -1,16 +1,20 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( - context "context" big "math/big" + asserter "github.com/coinbase/rosetta-sdk-go/asserter" + + context "context" + + database "github.com/coinbase/rosetta-sdk-go/storage/database" + mock "github.com/stretchr/testify/mock" - asserter "github.com/coinbase/rosetta-sdk-go/asserter" parser "github.com/coinbase/rosetta-sdk-go/parser" - database "github.com/coinbase/rosetta-sdk-go/storage/database" + types "github.com/coinbase/rosetta-sdk-go/types" ) @@ -42,6 +46,27 @@ func (_m *BalanceStorageHelper) AccountBalance(ctx context.Context, account *typ return r0, r1 } +// AccountSeqNum provides a mock function with given fields: ctx, account, currency, block +func (_m *BalanceStorageHelper) AccountSeqNum(ctx context.Context, account *types.AccountIdentifier, currency *types.Currency, block *types.BlockIdentifier) (int32, error) { + ret := _m.Called(ctx, account, currency, block) + + var r0 int32 + if rf, ok := ret.Get(0).(func(context.Context, *types.AccountIdentifier, *types.Currency, *types.BlockIdentifier) int32); ok { + r0 = rf(ctx, account, currency, block) + } else { + r0 = ret.Get(0).(int32) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.AccountIdentifier, *types.Currency, *types.BlockIdentifier) error); ok { + r1 = rf(ctx, account, currency, block) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // AccountsReconciled provides a mock function with given fields: ctx, dbTx func (_m *BalanceStorageHelper) AccountsReconciled(ctx context.Context, dbTx database.Transaction) (*big.Int, error) { ret := _m.Called(ctx, dbTx) @@ -135,3 +160,19 @@ func (_m *BalanceStorageHelper) ExemptFunc() parser.ExemptOperation { return r0 } + +// SequenceNumSupport provides a mock function with given fields: +func (_m *BalanceStorageHelper) SequenceNumSupport() *types.SequenceNumSupport { + ret := _m.Called() + + var r0 *types.SequenceNumSupport + if rf, ok := ret.Get(0).(func() *types.SequenceNumSupport); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.SequenceNumSupport) + } + } + + return r0 +} diff --git a/mocks/storage/modules/block_worker.go b/mocks/storage/modules/block_worker.go index 8bea40a1c..46dcb3c12 100644 --- a/mocks/storage/modules/block_worker.go +++ b/mocks/storage/modules/block_worker.go @@ -1,14 +1,15 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" errgroup "github.com/neilotoole/errgroup" + mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/storage/modules/broadcast_storage_handler.go b/mocks/storage/modules/broadcast_storage_handler.go index 6ac0a60cc..c79044ce4 100644 --- a/mocks/storage/modules/broadcast_storage_handler.go +++ b/mocks/storage/modules/broadcast_storage_handler.go @@ -1,13 +1,13 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/storage/modules/broadcast_storage_helper.go b/mocks/storage/modules/broadcast_storage_helper.go index be0c19749..61a540202 100644 --- a/mocks/storage/modules/broadcast_storage_helper.go +++ b/mocks/storage/modules/broadcast_storage_helper.go @@ -1,13 +1,13 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/storage/modules/coin_storage_helper.go b/mocks/storage/modules/coin_storage_helper.go index c4c8fa6dc..ebd5ec53d 100644 --- a/mocks/storage/modules/coin_storage_helper.go +++ b/mocks/storage/modules/coin_storage_helper.go @@ -1,13 +1,13 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package modules import ( context "context" + database "github.com/coinbase/rosetta-sdk-go/storage/database" mock "github.com/stretchr/testify/mock" - database "github.com/coinbase/rosetta-sdk-go/storage/database" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/mocks/syncer/handler.go b/mocks/syncer/handler.go index d435c6df9..45eaffb9a 100644 --- a/mocks/syncer/handler.go +++ b/mocks/syncer/handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package syncer diff --git a/mocks/syncer/helper.go b/mocks/syncer/helper.go index 99bdb8585..20a981cf4 100644 --- a/mocks/syncer/helper.go +++ b/mocks/syncer/helper.go @@ -1,4 +1,4 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package syncer diff --git a/mocks/syncer/option.go b/mocks/syncer/option.go new file mode 100644 index 000000000..f92f98d44 --- /dev/null +++ b/mocks/syncer/option.go @@ -0,0 +1,18 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package syncer + +import ( + syncer "github.com/coinbase/rosetta-sdk-go/syncer" + mock "github.com/stretchr/testify/mock" +) + +// Option is an autogenerated mock type for the Option type +type Option struct { + mock.Mock +} + +// Execute provides a mock function with given fields: s +func (_m *Option) Execute(s *syncer.Syncer) { + _m.Called(s) +} diff --git a/mocks/utils/block_storage_helper.go b/mocks/utils/block_storage_helper.go index b84367bcf..7bd6183a0 100644 --- a/mocks/utils/block_storage_helper.go +++ b/mocks/utils/block_storage_helper.go @@ -1,13 +1,12 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package utils import ( context "context" - mock "github.com/stretchr/testify/mock" - types "github.com/coinbase/rosetta-sdk-go/types" + mock "github.com/stretchr/testify/mock" ) // BlockStorageHelper is an autogenerated mock type for the BlockStorageHelper type diff --git a/mocks/utils/fetcher_helper.go b/mocks/utils/fetcher_helper.go index 02fbf73dd..23b8a9f5b 100644 --- a/mocks/utils/fetcher_helper.go +++ b/mocks/utils/fetcher_helper.go @@ -1,13 +1,13 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package utils import ( context "context" + fetcher "github.com/coinbase/rosetta-sdk-go/fetcher" mock "github.com/stretchr/testify/mock" - fetcher "github.com/coinbase/rosetta-sdk-go/fetcher" types "github.com/coinbase/rosetta-sdk-go/types" ) diff --git a/parser/balance_changes.go b/parser/balance_changes.go index 69d23c469..a06ef75cc 100644 --- a/parser/balance_changes.go +++ b/parser/balance_changes.go @@ -17,7 +17,6 @@ package parser import ( "context" "fmt" - "github.com/coinbase/rosetta-sdk-go/types" ) @@ -28,6 +27,16 @@ type BalanceChange struct { Currency *types.Currency `json:"currency,omitempty"` Block *types.BlockIdentifier `json:"block_identifier,omitempty"` Difference string `json:"difference,omitempty"` + +} + +type BalanceSeqChange struct { + Account *types.AccountIdentifier + Currency *types.Currency + Block *types.BlockIdentifier + BalanceDifference string + SeqNumDifference int32 + SeqNumAvailable bool } // ExemptOperation is a function that returns a boolean indicating @@ -76,8 +85,9 @@ func (p *Parser) BalanceChanges( ctx context.Context, block *types.Block, blockRemoved bool, -) ([]*BalanceChange, error) { - balanceChanges := map[string]*BalanceChange{} +) ([]*BalanceSeqChange, error) { + balanceChanges := map[string]*BalanceSeqChange{} + for _, tx := range block.Transactions { for _, op := range tx.Operations { skip, err := p.skipOperation(op) @@ -87,12 +97,18 @@ func (p *Parser) BalanceChanges( if skip { continue } + valueInt, err := types.AmountValue(op.Amount) + if err != nil { + return nil, err + } // We create a copy of Amount.Value // here to ensure we don't accidentally overwrite // the value of op.Amount. amountValue := op.Amount.Value blockIdentifier := block.BlockIdentifier + seqNumChange := int32(0) + if blockRemoved { negatedValue, err := types.NegateValue(amountValue) if err != nil { @@ -101,6 +117,14 @@ func (p *Parser) BalanceChanges( amountValue = negatedValue } + // Make sure we only update sequence number of the sender + if p.SequenceNumSupport.SeqOpType == op.Type && valueInt.Sign() == -1 { + if blockRemoved{ + seqNumChange = -1 + }else{ + seqNumChange = 1 + } + } // Merge values by account and currency key := fmt.Sprintf( "%s/%s", @@ -110,26 +134,28 @@ func (p *Parser) BalanceChanges( val, ok := balanceChanges[key] if !ok { - balanceChanges[key] = &BalanceChange{ + balanceChanges[key] = &BalanceSeqChange{ Account: op.Account, Currency: op.Amount.Currency, - Difference: amountValue, + BalanceDifference: amountValue, Block: blockIdentifier, + SeqNumDifference: seqNumChange, } continue } - newDifference, err := types.AddValues(val.Difference, amountValue) + newDifference, err := types.AddValues(val.BalanceDifference, amountValue) if err != nil { return nil, err } - val.Difference = newDifference + val.BalanceDifference = newDifference balanceChanges[key] = val + balanceChanges[key].SeqNumDifference += seqNumChange } } i := 0 - allChanges := make([]*BalanceChange, len(balanceChanges)) + allChanges := make([]*BalanceSeqChange, len(balanceChanges)) for _, change := range balanceChanges { allChanges[i] = change i++ diff --git a/parser/balance_changes_test.go b/parser/balance_changes_test.go index 3087aa850..2e411c162 100644 --- a/parser/balance_changes_test.go +++ b/parser/balance_changes_test.go @@ -35,11 +35,19 @@ func TestBalanceChanges(t *testing.T) { Address: "acct1", } + sender = &types.AccountIdentifier{ + Address: "acct2", + } + recipientAmount = &types.Amount{ Value: "100", Currency: currency, } + senderAmount = &types.Amount{ + Value: "-100", + Currency: currency, + } emptyAccountAndAmount = &types.Operation{ OperationIdentifier: &types.OperationIdentifier{ Index: 0, @@ -57,6 +65,15 @@ func TestBalanceChanges(t *testing.T) { Account: recipient, } + senderOperation = &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + Type: "Transfer", + Status: types.String("Success"), + Account: sender, + Amount: senderAmount, + } recipientOperation = &types.Operation{ OperationIdentifier: &types.OperationIdentifier{ Index: 0, @@ -89,6 +106,15 @@ func TestBalanceChanges(t *testing.T) { }, } + transferTransaction = &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: "tx2", + }, + Operations: []*types.Operation{ + senderOperation, + recipientOperation, + }, + } defaultStatus = []*types.OperationStatus{ { Status: "Success", @@ -104,7 +130,7 @@ func TestBalanceChanges(t *testing.T) { var tests = map[string]struct { block *types.Block orphan bool - changes []*BalanceChange + changes []*BalanceSeqChange allowedStatus []*types.OperationStatus exemptFunc ExemptOperation err error @@ -125,7 +151,7 @@ func TestBalanceChanges(t *testing.T) { Timestamp: asserter.MinUnixEpoch + 1, }, orphan: false, - changes: []*BalanceChange{ + changes: []*BalanceSeqChange{ { Account: recipient, Currency: currency, @@ -133,7 +159,8 @@ func TestBalanceChanges(t *testing.T) { Hash: "1", Index: 1, }, - Difference: "100", + BalanceDifference: "100", + SeqNumDifference: 0, }, }, allowedStatus: defaultStatus, @@ -155,7 +182,7 @@ func TestBalanceChanges(t *testing.T) { Timestamp: asserter.MinUnixEpoch + 1, }, orphan: false, - changes: []*BalanceChange{}, + changes: []*BalanceSeqChange{}, allowedStatus: defaultStatus, exemptFunc: func(op *types.Operation) bool { return types.Hash(op.Account) == @@ -181,7 +208,7 @@ func TestBalanceChanges(t *testing.T) { Timestamp: asserter.MinUnixEpoch + 1, }, orphan: false, - changes: []*BalanceChange{ + changes: []*BalanceSeqChange{ { Account: &types.AccountIdentifier{ Address: "addr1", @@ -191,7 +218,8 @@ func TestBalanceChanges(t *testing.T) { Hash: "1", Index: 1, }, - Difference: "250", + BalanceDifference: "250", + SeqNumDifference: 0, }, { Account: &types.AccountIdentifier{ @@ -202,7 +230,8 @@ func TestBalanceChanges(t *testing.T) { Hash: "1", Index: 1, }, - Difference: "150", + BalanceDifference: "150", + SeqNumDifference: 0, }, }, allowedStatus: defaultStatus, @@ -226,7 +255,7 @@ func TestBalanceChanges(t *testing.T) { Timestamp: asserter.MinUnixEpoch + 1, }, orphan: true, - changes: []*BalanceChange{ + changes: []*BalanceSeqChange{ { Account: &types.AccountIdentifier{ Address: "addr1", @@ -236,7 +265,8 @@ func TestBalanceChanges(t *testing.T) { Hash: "1", Index: 1, }, - Difference: "-250", + BalanceDifference: "-250", + SeqNumDifference: 0, }, { Account: &types.AccountIdentifier{ @@ -247,7 +277,92 @@ func TestBalanceChanges(t *testing.T) { Hash: "1", Index: 1, }, - Difference: "-150", + BalanceDifference: "-150", + SeqNumDifference: 0, + }, + }, + allowedStatus: defaultStatus, + err: nil, + }, + "simple block with transfer transaction":{ + block: &types.Block{ + BlockIdentifier: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + ParentBlockIdentifier: &types.BlockIdentifier{ + Hash: "0", + Index: 0, + }, + Transactions: []*types.Transaction{ + transferTransaction, + transferTransaction, + }, + Timestamp: asserter.MinUnixEpoch + 1, + }, + orphan: false, + changes: []*BalanceSeqChange{ + { + Account: sender, + Currency: currency, + Block: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + BalanceDifference: "-200", + SeqNumDifference: 2, + }, + { + Account: recipient, + Currency: currency, + Block: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + BalanceDifference: "200", + SeqNumDifference: 0, + }, + }, + allowedStatus: defaultStatus, + err: nil, + }, + "multiple transfers in orphan block": { + block: &types.Block{ + BlockIdentifier: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + ParentBlockIdentifier: &types.BlockIdentifier{ + Hash: "0", + Index: 0, + }, + Transactions: []*types.Transaction{ + transferTransaction, + transferTransaction, + }, + Timestamp: asserter.MinUnixEpoch + 1, + }, + orphan: true, + changes: []*BalanceSeqChange{ + { + Account: sender, + Currency: currency, + Block: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + BalanceDifference: "200", + SeqNumDifference: -2, + }, + { + Account: recipient, + Currency: currency, + Block: &types.BlockIdentifier{ + Hash: "1", + Index: 1, + }, + BalanceDifference: "-200", + SeqNumDifference: 0, }, }, allowedStatus: defaultStatus, @@ -261,10 +376,15 @@ func TestBalanceChanges(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, asserter) + sequenceNum := &types.SequenceNum{ + SupportSeq: true, + SeqOpType: "Transfer", + } parser := New( asserter, test.exemptFunc, nil, + sequenceNum, ) changes, err := parser.BalanceChanges( diff --git a/parser/exemptions_test.go b/parser/exemptions_test.go index b83b1b4c2..44ed874e6 100644 --- a/parser/exemptions_test.go +++ b/parser/exemptions_test.go @@ -195,7 +195,7 @@ func TestFindExemptions(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - parser := New(nil, nil, test.balanceExemptions) + parser := New(nil, nil, test.balanceExemptions,types.SequenceNum{}) assert.Equal(t, test.expected, parser.FindExemptions(test.account, test.currency)) }) } @@ -456,7 +456,7 @@ func TestMatchBalanceExemption(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - parser := New(nil, nil, test.balanceExemptions) + parser := New(nil, nil, test.balanceExemptions, types.SequenceNum{}) exemptions := parser.FindExemptions(test.account, test.currency) assert.Equal(t, test.expected, MatchBalanceExemption(exemptions, test.difference)) }) diff --git a/parser/intent_test.go b/parser/intent_test.go index 6842a92ae..2fee1b5d0 100644 --- a/parser/intent_test.go +++ b/parser/intent_test.go @@ -544,7 +544,7 @@ func TestExpectedOperations(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, asserter) - parser := New(asserter, nil, nil) + parser := New(asserter, nil, nil, types.SequenceNum{}) t.Run(name, func(t *testing.T) { err := parser.ExpectedOperations( diff --git a/parser/parser.go b/parser/parser.go index ce1b94ff0..545ea670f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -24,6 +24,7 @@ type Parser struct { Asserter *asserter.Asserter ExemptFunc ExemptOperation BalanceExemptions []*types.BalanceExemption + SequenceNumSupport *types.SequenceNumSupport } // New creates a new Parser. @@ -31,10 +32,12 @@ func New( asserter *asserter.Asserter, exemptFunc ExemptOperation, balanceExemptions []*types.BalanceExemption, + sequenceNumSupport *types.SequenceNumSupport, ) *Parser { return &Parser{ Asserter: asserter, ExemptFunc: exemptFunc, BalanceExemptions: balanceExemptions, + SequenceNumSupport: sequenceNumSupport, } } diff --git a/storage/encoder/encoder.go b/storage/encoder/encoder.go index d8470c628..c076ebb5b 100644 --- a/storage/encoder/encoder.go +++ b/storage/encoder/encoder.go @@ -664,3 +664,88 @@ func (e *Encoder) DecodeAccountCurrency( // nolint:gocognit return nil } + + +func (e *Encoder) EncodeBalanceSeq( // nolint:gocognit + balanceSeq *types.BalanceSeq, +) ([]byte, error) { + output := e.pool.Get() + if _, err := output.WriteString(balanceSeq.Amount.Value); err != nil { + return nil, fmt.Errorf("%w: %s", errors.ErrObjectEncodeFailed, err.Error()) + } + if balanceSeq.SeqNumSupport == nil || !balanceSeq.SeqNumSupport.SupportSeq { + return output.Bytes(), nil + } + + if _, err := output.WriteRune(unicodeRecordSeparator); err != nil { + return nil, fmt.Errorf("%w: %s", errors.ErrObjectEncodeFailed, err.Error()) + } + if _, err := output.WriteString(strconv.FormatInt(int64(balanceSeq.Seq), 10),); err != nil { + return nil, fmt.Errorf("%w: %s", errors.ErrObjectEncodeFailed, err.Error()) + } + + return output.Bytes(), nil +} + +func (e *Encoder) DecodeBalanceSeq( + b []byte, + balanceSeq *types.BalanceSeq, + reclaimInput bool, +) error { + // Indices of encoded BalanceSeq struct + const ( + balanceValue = iota + seq + ) + + count := 0 + currentBytes := b + for { + nextRune := bytes.IndexRune(currentBytes, unicodeRecordSeparator) + if nextRune == -1 { + if count != balanceValue && count != seq { + return fmt.Errorf("%w: next rune is -1 at %d", errors.ErrRawDecodeFailed, count) + } + + nextRune = len(currentBytes) + } + + val := currentBytes[:nextRune] + if len(val) == 0 { + goto handleNext + } + + switch count { + case balanceValue: + balanceSeq.Amount = &types.Amount{Value: string(val)} + case seq: + strVal := string(val) + i, err := strconv.ParseInt(strVal, 10, 32) + if err != nil { + return fmt.Errorf("%w: %s", errors.ErrRawDecodeFailed, err.Error()) + } + + balanceSeq.Seq = int32(i) + balanceSeq.SeqNumSupport = &types.SequenceNumSupport{ + SupportSeq: true, + } + default: + return fmt.Errorf("%w: count %d > end", errors.ErrRawDecodeFailed, count) + } + + handleNext: + if nextRune == len(currentBytes) && + (count == balanceValue || count == seq) { + break + } + + currentBytes = currentBytes[nextRune+1:] + count++ + } + + if reclaimInput { + e.pool.PutByteSlice(b) + } + + return nil +} diff --git a/storage/modules/balance_storage.go b/storage/modules/balance_storage.go index 122bcadd0..cd13b102d 100644 --- a/storage/modules/balance_storage.go +++ b/storage/modules/balance_storage.go @@ -112,8 +112,8 @@ func GetHistoricalBalancePrefix(account *types.AccountIdentifier, currency *type // BalanceStorageHandler is invoked after balance changes are committed to the database. type BalanceStorageHandler interface { - BlockAdded(ctx context.Context, block *types.Block, changes []*parser.BalanceChange) error - BlockRemoved(ctx context.Context, block *types.Block, changes []*parser.BalanceChange) error + BlockAdded(ctx context.Context, block *types.Block, changes []*parser.BalanceSeqChange) error + BlockRemoved(ctx context.Context, block *types.Block, changes []*parser.BalanceSeqChange) error AccountsReconciled(ctx context.Context, dbTx database.Transaction, count int) error AccountsSeen(ctx context.Context, dbTx database.Transaction, count int) error @@ -129,10 +129,17 @@ type BalanceStorageHelper interface { currency *types.Currency, block *types.BlockIdentifier, ) (*types.Amount, error) + AccountSeqNum( + ctx context.Context, + account *types.AccountIdentifier, + currency *types.Currency, + block *types.BlockIdentifier, + ) (int32, error) ExemptFunc() parser.ExemptOperation BalanceExemptions() []*types.BalanceExemption Asserter() *asserter.Asserter + SequenceNumSupport() *types.SequenceNumSupport AccountsReconciled(ctx context.Context, dbTx database.Transaction) (*big.Int, error) AccountsSeen(ctx context.Context, dbTx database.Transaction) (*big.Int, error) @@ -178,6 +185,7 @@ func (b *BalanceStorage) Initialize( helper.Asserter(), helper.ExemptFunc(), helper.BalanceExemptions(), + helper.SequenceNumSupport(), ) } @@ -321,7 +329,8 @@ func (b *BalanceStorage) SetBalance( ctx context.Context, dbTransaction database.Transaction, account *types.AccountIdentifier, - amount *types.Amount, + balanceSeq *types.BalanceSeq, + //amount *types.Amount, block *types.BlockIdentifier, ) error { if b.handler == nil { @@ -333,7 +342,7 @@ func (b *BalanceStorage) SetBalance( ctx, dbTransaction, account, - amount.Currency, + balanceSeq.Amount.Currency, ); err != nil { return err } @@ -346,32 +355,31 @@ func (b *BalanceStorage) SetBalance( // Serialize account entry serialAcc, err := b.db.Encoder().EncodeAccountCurrency(&types.AccountCurrency{ Account: account, - Currency: amount.Currency, + Currency: balanceSeq.Amount.Currency, }) if err != nil { return err } // Set account - key := GetAccountKey(accountNamespace, account, amount.Currency) + key := GetAccountKey(accountNamespace, account, balanceSeq.Amount.Currency) if err := dbTransaction.Set(ctx, key, serialAcc, true); err != nil { return err } // Set current balance - key = GetAccountKey(balanceNamespace, account, amount.Currency) - value, ok := new(big.Int).SetString(amount.Value, 10) - if !ok { - return storageErrs.ErrInvalidValue + key = GetAccountKey(balanceNamespace, account, balanceSeq.Amount.Currency) + valueBytes, err := b.db.Encoder().EncodeBalanceSeq(balanceSeq) + if err != nil { + return err } - valueBytes := value.Bytes() if err := dbTransaction.Set(ctx, key, valueBytes, false); err != nil { return err } // Set historical balance - key = GetHistoricalBalanceKey(account, amount.Currency, block.Index) + key = GetHistoricalBalanceKey(account, balanceSeq.Amount.Currency, block.Index) if err := dbTransaction.Set(ctx, key, valueBytes, true); err != nil { return err } @@ -507,15 +515,16 @@ func (b *BalanceStorage) ReconciliationCoverage( // // If there are matching balance exemptions, // we update the passed in existing value. +// TODO: update comment func (b *BalanceStorage) existingValue( ctx context.Context, exists bool, - change *parser.BalanceChange, + change *parser.BalanceSeqChange, parentBlock *types.BlockIdentifier, - existingValue string, -) (string, error) { + existingValue *types.BalanceSeq, +) (*types.BalanceSeq, error) { if b.helper == nil { - return "", storageErrs.ErrHelperHandlerMissing + return nil, storageErrs.ErrHelperHandlerMissing } if exists { @@ -527,8 +536,14 @@ func (b *BalanceStorage) existingValue( // // We also ensure we don't exit with 0 if the value already exists, // which could be true if balances are bootstrapped. + // TODO: why this? if parentBlock != nil && change.Block.Hash == parentBlock.Hash { - return "0", nil + //return "0", nil + return &types.BalanceSeq{ + Amount: &types.Amount{Value: "0", Currency: change.Currency}, + SeqNumSupport: &types.SequenceNumSupport{SupportSeq: true}, + Seq: int32(0), + }, nil } // Use helper to fetch existing balance. @@ -539,7 +554,7 @@ func (b *BalanceStorage) existingValue( parentBlock, ) if err != nil { - return "", fmt.Errorf( + return nil, fmt.Errorf( "%w: unable to get previous account balance for %s %s at %s", err, types.PrintStruct(change.Account), @@ -547,8 +562,33 @@ func (b *BalanceStorage) existingValue( types.PrintStruct(parentBlock), ) } + if existingValue.SeqNumSupport.SupportSeq { + seqNum, err := b.helper.AccountSeqNum( + ctx, + change.Account, + change.Currency, + parentBlock, + ) + if err != nil { + return nil, fmt.Errorf( + "%w: unable to get previous account sequence number for %s %s at %s", + err, + types.PrintStruct(change.Account), + types.PrintStruct(change.Currency), + types.PrintStruct(parentBlock), + ) + } + return &types.BalanceSeq{ + Amount: &types.Amount{Value: amount.Value, Currency: change.Currency}, + SeqNumSupport: &types.SequenceNumSupport{SupportSeq: true}, + Seq: seqNum, + }, nil + } - return amount.Value, nil + return &types.BalanceSeq{ + Amount: &types.Amount{Value: amount.Value, Currency: change.Currency}, + SeqNumSupport: &types.SequenceNumSupport{SupportSeq: false}, + }, nil } // applyExemptions compares the computed balance of an account @@ -562,13 +602,14 @@ func (b *BalanceStorage) existingValue( // means it is possible for an account balance to go negative // if the balance change is applied to the balance of the account // at the parent block. +// TODO: do we need to return sequence number from live as well? func (b *BalanceStorage) applyExemptions( ctx context.Context, - change *parser.BalanceChange, - newVal string, -) (string, error) { + change *parser.BalanceSeqChange, + newVal *types.BalanceSeq, +) (*types.BalanceSeq, error) { if b.helper == nil { - return "", storageErrs.ErrHelperHandlerMissing + return nil, storageErrs.ErrHelperHandlerMissing } // Find exemptions that are applicable to the *parser.BalanceChange @@ -585,7 +626,7 @@ func (b *BalanceStorage) applyExemptions( change.Block, ) if err != nil { - return "", fmt.Errorf( + return nil, fmt.Errorf( "%w: unable to get current account balance for %s %s at %s", err, types.PrintStruct(change.Account), @@ -596,9 +637,9 @@ func (b *BalanceStorage) applyExemptions( // Determine if new live balance complies // with any balance exemption. - difference, err := types.SubtractValues(liveAmount.Value, newVal) + difference, err := types.SubtractValues(liveAmount.Value, newVal.Amount.Value) if err != nil { - return "", fmt.Errorf( + return nil, fmt.Errorf( "%w: unable to calculate difference between live and computed balances", err, ) @@ -609,7 +650,7 @@ func (b *BalanceStorage) applyExemptions( difference, ) if exemption == nil { - return "", fmt.Errorf( + return nil, fmt.Errorf( "%w: account %s balance difference (live - computed) %s at %s is not allowed by any balance exemption", storageErrs.ErrInvalidLiveBalance, types.PrintStruct(change.Account), @@ -617,8 +658,26 @@ func (b *BalanceStorage) applyExemptions( types.PrintStruct(change.Block), ) } + newVal.Amount.Value = liveAmount.Value + if newVal.SeqNumSupport.SupportSeq { + liveSeqNum, err := b.helper.AccountSeqNum(ctx, + change.Account, + change.Currency, + change.Block, + ) + if err != nil { + return nil, fmt.Errorf( + "%w: unable to get current sequence number for %s %s at %s", + err, + types.PrintStruct(change.Account), + types.PrintStruct(change.Currency), + types.PrintStruct(change.Block), + ) + } + newVal.Seq = liveSeqNum + } - return liveAmount.Value, nil + return newVal, nil } // deleteAccountRecords is a convenience method that deletes @@ -703,7 +762,7 @@ func (b *BalanceStorage) deleteAccountRecords( func (b *BalanceStorage) OrphanBalance( ctx context.Context, dbTransaction database.Transaction, - change *parser.BalanceChange, + change *parser.BalanceSeqChange, ) (bool, error) { err := b.removeHistoricalBalances( ctx, @@ -744,7 +803,7 @@ func (b *BalanceStorage) OrphanBalance( return false, storageErrs.ErrAccountMissing } - difference, ok := new(big.Int).SetString(change.Difference, 10) + difference, ok := new(big.Int).SetString(change.BalanceDifference, 10) if !ok { return false, storageErrs.ErrInvalidChangeValue } @@ -791,26 +850,32 @@ func (b *BalanceStorage) PruneBalances( } // UpdateBalance updates a types.AccountIdentifer -// by a types.Amount and sets the account's most +// by a types.BalanceSeq and sets the account's most // recent accessed block. func (b *BalanceStorage) UpdateBalance( ctx context.Context, dbTransaction database.Transaction, - change *parser.BalanceChange, + change *parser.BalanceSeqChange, parentBlock *types.BlockIdentifier, ) (bool, error) { if change.Currency == nil { return false, errors.New("invalid currency") } - // If the balance key does not exist, the account // does not exist. key := GetAccountKey(balanceNamespace, change.Account, change.Currency) - exists, currentBalance, err := BigIntGet(ctx, key, dbTransaction) + exists, val, err := dbTransaction.Get(ctx, key) if err != nil { return false, err } + var currentBalanceSeq types.BalanceSeq + if err:= b.db.Encoder().DecodeBalanceSeq(val, ¤tBalanceSeq, false); err != nil { + return false, fmt.Errorf("%w: %v", storageErrs.ErrObjectDecodeFailed, err) + } + + currentBalanceSeq.SeqNumSupport = b.helper.SequenceNumSupport() + // Find account existing value whether the account is new, has an // existing balance, or is subject to additional accounting from // a balance exemption. @@ -822,26 +887,35 @@ func (b *BalanceStorage) UpdateBalance( exists, change, parentBlock, - currentBalance.String(), + ¤tBalanceSeq, ) if err != nil { return false, err } - newVal, err := types.AddValues(change.Difference, existingValue) + newVal, err := types.AddValues(change.BalanceDifference, existingValue.Amount.Value) if err != nil { return false, err } + newBalanceSeq := &types.BalanceSeq{ + Amount: &types.Amount{Value: newVal, Currency: change.Currency}, + SeqNumSupport: existingValue.SeqNumSupport, + } + + if existingValue.SeqNumSupport.SupportSeq { + newSeqNum := change.SeqNumDifference + existingValue.Seq + newBalanceSeq.Seq = newSeqNum + } // If any exemptions apply, the returned new value will // reflect the live balance for the *types.AccountIdentifier // and *types.Currency. - newVal, err = b.applyExemptions(ctx, change, newVal) + newBalanceSeq, err = b.applyExemptions(ctx, change, newBalanceSeq) if err != nil { return false, err } - bigNewVal, ok := new(big.Int).SetString(newVal, 10) + bigNewVal, ok := new(big.Int).SetString(newBalanceSeq.Amount.Value, 10) if !ok { return false, fmt.Errorf("%s is not an integer", newVal) } @@ -857,6 +931,18 @@ func (b *BalanceStorage) UpdateBalance( ) } + if newBalanceSeq.SeqNumSupport.SupportSeq && newBalanceSeq.Seq < 0{ + // TODO: create a new error - negativeSequenceNumber + return false, fmt.Errorf( + "%w %s:%+v for %+v at %+v", + storageErrs.ErrNegativeBalance, + newBalanceSeq.Amount.Value, + change.Currency, + change.Account, + change.Block, + ) + } + // Add account entry if doesn't exist var newAccount bool if !exists { @@ -875,7 +961,12 @@ func (b *BalanceStorage) UpdateBalance( } // Update current balance - if err := dbTransaction.Set(ctx, key, bigNewVal.Bytes(), true); err != nil { + balanceSeqBytes, err := b.db.Encoder().EncodeBalanceSeq(newBalanceSeq) + if err != nil { + // TODO: format error + return false, err + } + if err := dbTransaction.Set(ctx, key, balanceSeqBytes, true); err != nil { return false, err } @@ -885,7 +976,7 @@ func (b *BalanceStorage) UpdateBalance( change.Currency, change.Block.Index, ) - if err := dbTransaction.Set(ctx, historicalKey, bigNewVal.Bytes(), true); err != nil { + if err := dbTransaction.Set(ctx, historicalKey, balanceSeqBytes, true); err != nil { return false, err } @@ -899,11 +990,11 @@ func (b *BalanceStorage) GetBalance( account *types.AccountIdentifier, currency *types.Currency, index int64, -) (*types.Amount, error) { +) (*types.BalanceSeq, error) { dbTx := b.db.ReadTransaction(ctx) defer dbTx.Discard(ctx) - amount, err := b.GetBalanceTransactional( + balanceSeq, err := b.GetBalanceTransactional( ctx, dbTx, account, @@ -911,10 +1002,10 @@ func (b *BalanceStorage) GetBalance( index, ) if err != nil { - return nil, fmt.Errorf("%w: unable to get balance", err) + return nil, fmt.Errorf("%w: unable to get balance and sequence number", err) } - return amount, nil + return balanceSeq, nil } // GetBalanceTransactional returns all the balances of a types.AccountIdentifier @@ -925,7 +1016,7 @@ func (b *BalanceStorage) GetBalanceTransactional( account *types.AccountIdentifier, currency *types.Currency, index int64, -) (*types.Amount, error) { +) (*types.BalanceSeq, error) { key := GetAccountKey(balanceNamespace, account, currency) exists, _, err := dbTx.Get(ctx, key) if err != nil { @@ -951,7 +1042,7 @@ func (b *BalanceStorage) GetBalanceTransactional( ) } - amount, err := b.getHistoricalBalance( + balanceSeq, err := b.getHistoricalBalance( ctx, dbTx, account, @@ -963,17 +1054,23 @@ func (b *BalanceStorage) GetBalanceTransactional( // the balance to be 0 (i.e. before any balance // changes applied). If syncing starts after // genesis, this behavior could cause issues. + // TODO: what to return? + // TODO: Test: orphan balance correctly if errors.Is(err, storageErrs.ErrAccountMissing) { - return &types.Amount{ - Value: "0", - Currency: currency, - }, nil + return &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "0", + Currency: currency, + }, + SeqNumSupport: b.helper.SequenceNumSupport(), + Seq: int32(0)}, nil + //return nil, nil } if err != nil { return nil, err } - return amount, nil + return balanceSeq, nil } func (b *BalanceStorage) fetchAndSetBalance( @@ -982,24 +1079,33 @@ func (b *BalanceStorage) fetchAndSetBalance( account *types.AccountIdentifier, currency *types.Currency, block *types.BlockIdentifier, -) (*types.Amount, error) { +) (*types.BalanceSeq, error) { amount, err := b.helper.AccountBalance(ctx, account, currency, block) if err != nil { return nil, fmt.Errorf("%w: unable to get account balance from helper", err) } + balanceSeq := &types.BalanceSeq{Amount: amount, SeqNumSupport: b.helper.SequenceNumSupport()} + if balanceSeq.SeqNumSupport.SupportSeq { + seqNum, err := b.helper.AccountSeqNum(ctx, account, currency, block) + if err != nil { + return nil, fmt.Errorf("%w: unable to get account sequence number from helper", err) + } + balanceSeq.Seq = seqNum + } + err = b.SetBalance( ctx, dbTx, account, - amount, + balanceSeq, block, ) if err != nil { return nil, fmt.Errorf("%w: unable to set account balance", err) } - return amount, nil + return balanceSeq, nil } // GetOrSetBalance returns the balance of a types.AccountIdentifier @@ -1010,11 +1116,11 @@ func (b *BalanceStorage) GetOrSetBalance( account *types.AccountIdentifier, currency *types.Currency, block *types.BlockIdentifier, -) (*types.Amount, error) { +) (*types.BalanceSeq, error) { dbTx := b.db.Transaction(ctx) defer dbTx.Discard(ctx) - amount, err := b.GetOrSetBalanceTransactional( + balanceSeq, err := b.GetOrSetBalanceTransactional( ctx, dbTx, account, @@ -1030,7 +1136,7 @@ func (b *BalanceStorage) GetOrSetBalance( return nil, fmt.Errorf("%w: unable to commit account balance transaction", err) } - return amount, nil + return balanceSeq, nil } // GetOrSetBalanceTransactional returns the balance of a types.AccountIdentifier @@ -1042,12 +1148,12 @@ func (b *BalanceStorage) GetOrSetBalanceTransactional( account *types.AccountIdentifier, currency *types.Currency, block *types.BlockIdentifier, -) (*types.Amount, error) { +) (*types.BalanceSeq, error) { if block == nil { return nil, storageErrs.ErrBlockNil } - amount, err := b.GetBalanceTransactional( + balanceSeq, err := b.GetBalanceTransactional( ctx, dbTx, account, @@ -1055,7 +1161,8 @@ func (b *BalanceStorage) GetOrSetBalanceTransactional( block.Index, ) if errors.Is(err, storageErrs.ErrAccountMissing) { - amount, err = b.fetchAndSetBalance(ctx, dbTx, account, currency, block) + // TODO + amount, err := b.fetchAndSetBalance(ctx, dbTx, account, currency, block) if err != nil { return nil, fmt.Errorf("%w: unable to set balance", err) } @@ -1066,7 +1173,7 @@ func (b *BalanceStorage) GetOrSetBalanceTransactional( return nil, fmt.Errorf("%w: unable to get balance", err) } - return amount, nil + return balanceSeq, nil } // BootstrapBalance represents a balance of @@ -1082,6 +1189,7 @@ type BootstrapBalance struct { // any number of AccountIdentifiers at the genesis blocks. // This is particularly useful for setting the value of // accounts that received an allocation in the genesis block. +// TODO: should also bootstrap seqnum if supported? func (b *BalanceStorage) BootstrapBalances( ctx context.Context, bootstrapBalancesFile string, @@ -1128,9 +1236,12 @@ func (b *BalanceStorage) BootstrapBalances( ctx, dbTransaction, balance.Account, - &types.Amount{ - Value: balance.Value, - Currency: balance.Currency, + &types.BalanceSeq{ + Amount: &types.Amount{ + Value: balance.Value, + Currency: balance.Currency, + }, + SeqNumSupport: b.helper.SequenceNumSupport(), }, genesisBlockIdentifier, ) @@ -1203,6 +1314,7 @@ func (b *BalanceStorage) GetAllAccountCurrency( // SetBalanceImported sets the balances of a set of addresses by // getting their balances from the tip block, and populating the database. // This is used when importing prefunded addresses. +// TODO: should we have set seqNum imported as well? func (b *BalanceStorage) SetBalanceImported( ctx context.Context, helper BalanceStorageHelper, @@ -1224,7 +1336,7 @@ func (b *BalanceStorage) SetBalanceImported( ctx, transaction, accountBalance.Account, - accountBalance.Amount, + &types.BalanceSeq{SeqNumSupport: b.helper.SequenceNumSupport(), Amount: accountBalance.Amount}, accountBalance.Block, ) if err != nil { @@ -1240,32 +1352,34 @@ func (b *BalanceStorage) SetBalanceImported( return nil } -// getHistoricalBalance returns the balance of an account -// at a particular *types.BlockIdentifier. +// getHistoricalBalance returns the balance and sequence number (if supported) +// of an account at a particular *types.BlockIdentifier. func (b *BalanceStorage) getHistoricalBalance( ctx context.Context, dbTx database.Transaction, account *types.AccountIdentifier, currency *types.Currency, index int64, -) (*types.Amount, error) { - var foundValue string +) (*types.BalanceSeq, error) { + var balanceSeq types.BalanceSeq _, err := dbTx.Scan( ctx, GetHistoricalBalancePrefix(account, currency), GetHistoricalBalanceKey(account, currency, index), func(k []byte, v []byte) error { - foundValue = new(big.Int).SetBytes(v).String() + if err:= b.db.Encoder().DecodeBalanceSeq(v, &balanceSeq, false); err != nil { + return fmt.Errorf("%w: %v", storageErrs.ErrObjectDecodeFailed, err) + } return errAccountFound }, false, true, ) if errors.Is(err, errAccountFound) { - return &types.Amount{ - Value: foundValue, - Currency: currency, - }, nil + balanceSeq.Amount.Currency = currency + // TODO: how to do it simpler? + balanceSeq.SeqNumSupport = b.helper.SequenceNumSupport() + return &balanceSeq, nil } if err != nil { return nil, fmt.Errorf("%w: database scan failed", err) @@ -1277,6 +1391,7 @@ func (b *BalanceStorage) getHistoricalBalance( // removeHistoricalBalances deletes all historical balances // >= (used during reorg) or <= (used during pruning) a particular // index. +// TODO: q: if orphan, does it mean we remove from genesis to orphan block? why? func (b *BalanceStorage) removeHistoricalBalances( ctx context.Context, dbTx database.Transaction, diff --git a/storage/modules/balance_storage_test.go b/storage/modules/balance_storage_test.go index 23c4b3cc4..621f53ece 100644 --- a/storage/modules/balance_storage_test.go +++ b/storage/modules/balance_storage_test.go @@ -16,16 +16,19 @@ package modules import ( "context" - "encoding/json" - "errors" - "io/ioutil" + "github.com/stretchr/testify/mock" "math/big" - "path" + + //"encoding/json" + "errors" + //"io/ioutil" + //"math/big" + //"path" "testing" - "github.com/neilotoole/errgroup" + //"github.com/neilotoole/errgroup" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + //"github.com/stretchr/testify/mock" "github.com/coinbase/rosetta-sdk-go/asserter" mocks "github.com/coinbase/rosetta-sdk-go/mocks/storage/modules" @@ -67,6 +70,14 @@ func exemptFunc() parser.ExemptOperation { } } +func getMockBalanceStorageHelper(exemptions []*types.BalanceExemption, seqNumSupport *types.SequenceNumSupport) *mocks.BalanceStorageHelper{ + mockHelper := &mocks.BalanceStorageHelper{} + mockHelper.On("Asserter").Return(baseAsserter()) + mockHelper.On("ExemptFunc").Return(exemptFunc()) + mockHelper.On("BalanceExemptions").Return(exemptions) + mockHelper.On("SequenceNumSupport").Return(seqNumSupport) + return mockHelper +} func TestBalance(t *testing.T) { var ( genesisAccount = &types.AccountIdentifier{ @@ -75,9 +86,9 @@ func TestBalance(t *testing.T) { account = &types.AccountIdentifier{ Address: "blah", } - account2 = &types.AccountIdentifier{ - Address: "blah2", - } + //account2 = &types.AccountIdentifier{ + // Address: "blah2", + //} account3 = &types.AccountIdentifier{ Address: "blah3", } @@ -169,10 +180,6 @@ func TestBalance(t *testing.T) { Hash: "pkdasdj", Index: 123891, } - result = &types.Amount{ - Value: "200", - Currency: currency, - } newBlock3 = &types.BlockIdentifier{ Hash: "pkdgdj", Index: 123892, @@ -185,6 +192,13 @@ func TestBalance(t *testing.T) { Value: "-1000", Currency: currency, } + seqNumSupported = &types.SequenceNumSupport{ + SupportSeq: true, + SeqOpType: "Transfer", + } + //seqNumNotSupported = &types.SequenceNumSupport{ + // SupportSeq: false, + //} ) ctx := context.Background() @@ -198,10 +212,7 @@ func TestBalance(t *testing.T) { defer database.Close(ctx) storage := NewBalanceStorage(database) - mockHelper := &mocks.BalanceStorageHelper{} - mockHelper.On("Asserter").Return(baseAsserter()) - mockHelper.On("ExemptFunc").Return(exemptFunc()) - mockHelper.On("BalanceExemptions").Return(exemptions) + mockHelper := getMockBalanceStorageHelper(exemptions, seqNumSupported) mockHandler := &mocks.BalanceStorageHandler{} storage.Initialize(mockHelper, mockHandler) @@ -228,44 +239,68 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "10", Currency: currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + account, + currency, + newBlock, + ).Return( + int32(2), + nil, + ).Once() mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Once() - amount, err := storage.GetOrSetBalance(ctx, account, currency, newBlock) + + actualBalanceSeq, err := storage.GetOrSetBalance(ctx, account, currency, newBlock) + expectedBalanceSeq := &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "10", + Currency: currency, + }, + SeqNumSupport: seqNumSupported, + Seq: int32(2), + } assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "10", - Currency: currency, - }, amount) + assert.Equal(t, expectedBalanceSeq, actualBalanceSeq) - amount, err = storage.GetOrSetBalance(ctx, account, currency, newBlock) + actualBalanceSeq, err = storage.GetOrSetBalance(ctx, account, currency, newBlock) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "10", - Currency: currency, - }, amount) + expectedBalanceSeq = &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "10", + Currency: currency, + }, + SeqNumSupport: seqNumSupported, + Seq: int32(2), + } + assert.Equal(t, expectedBalanceSeq, actualBalanceSeq) }) t.Run("Set and get genesis balance", func(t *testing.T) { txn := storage.db.Transaction(ctx) mockHandler.On("AccountsSeen", ctx, txn, 1).Return(nil).Once() + expectedBalanceSeq := &types.BalanceSeq{ + SeqNumSupport: seqNumSupported, + Amount: &types.Amount{ + Value: amount.Value, + Currency: currency, + }, + Seq: int32(0), + } err := storage.SetBalance( ctx, txn, genesisAccount, - &types.Amount{ - Value: amount.Value, - Currency: currency, - }, + expectedBalanceSeq, genesisBlock, ) assert.NoError(t, err) assert.NoError(t, txn.Commit(ctx)) - amount, err := storage.GetOrSetBalance(ctx, genesisAccount, currency, genesisBlock) + actualBalanceSeq, err := storage.GetOrSetBalance(ctx, genesisAccount, currency, genesisBlock) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "100", - Currency: currency, - }, amount) + assert.Equal(t, expectedBalanceSeq, actualBalanceSeq) }) t.Run("Set and get balance", func(t *testing.T) { @@ -277,23 +312,31 @@ func TestBalance(t *testing.T) { // When adding the account, we increment the account seen count. mockHandler.On("AccountsSeen", ctx, txn, 1).Return(nil).Once() + expectedBalanceSeq := &types.BalanceSeq{ + SeqNumSupport: seqNumSupported, + Amount: &types.Amount{ + Value: amount.Value, + Currency: currency, + }, + Seq: int32(3), + } err := storage.SetBalance( ctx, txn, account, - amount, + expectedBalanceSeq, newBlock, ) assert.NoError(t, err) assert.NoError(t, txn.Commit(ctx)) - retrievedAmount, err := storage.GetOrSetBalance(ctx, account, currency, newBlock) + actualBalanceSeq, err := storage.GetOrSetBalance(ctx, account, currency, newBlock) assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, actualBalanceSeq) - retrievedAmount, err = storage.GetOrSetBalance(ctx, account, currency, newBlock2) + actualBalanceSeq, err = storage.GetOrSetBalance(ctx, account, currency, newBlock2) assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, actualBalanceSeq) }) t.Run("Set and get balance with storage helper", func(t *testing.T) { @@ -307,25 +350,42 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "10", Currency: currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + account3, + currency, + (*types.BlockIdentifier)(nil), + ).Return( + int32(0), + nil, + ).Once() txn := storage.db.Transaction(ctx) newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account3, Currency: currency, Block: newBlock, - Difference: amount.Value, + BalanceDifference: amount.Value, + SeqNumDifference: int32(0), }, nil, ) + expectedBalanceSeq := &types.BalanceSeq{ + Amount: amountWithPrevious, + SeqNumSupport: seqNumSupported, + Seq: int32(0), + } assert.True(t, newAccount) assert.NoError(t, err) assert.NoError(t, txn.Commit(ctx)) - retrievedAmount, err := storage.GetOrSetBalance(ctx, account3, currency, newBlock) + retrievedBalanceSeq, err := storage.GetOrSetBalance(ctx, account3, currency, newBlock) assert.NoError(t, err) - assert.Equal(t, amountWithPrevious, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedBalanceSeq) }) t.Run("Set balance with nil currency", func(t *testing.T) { @@ -333,11 +393,12 @@ func TestBalance(t *testing.T) { newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: nil, Block: newBlock, - Difference: amountNilCurrency.Value, + BalanceDifference: amountNilCurrency.Value, + SeqNumDifference: int32(0), }, nil, ) @@ -345,9 +406,14 @@ func TestBalance(t *testing.T) { assert.EqualError(t, err, "invalid currency") txn.Discard(ctx) + expectedBalanceSeq := &types.BalanceSeq{ + Amount: amount, + SeqNumSupport: seqNumSupported, + Seq: int32(3), + } retrievedAmount, err := storage.GetOrSetBalance(ctx, account, currency, newBlock) assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) }) t.Run("Modify existing balance", func(t *testing.T) { @@ -355,11 +421,12 @@ func TestBalance(t *testing.T) { newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: currency, Block: newBlock2, - Difference: amount.Value, + BalanceDifference: "-50", + SeqNumDifference: int32(1), }, nil, ) @@ -367,9 +434,17 @@ func TestBalance(t *testing.T) { assert.NoError(t, err) assert.NoError(t, txn.Commit(ctx)) + expectedBalanceSeq := &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "50", + Currency: currency, + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4), + } retrievedAmount, err := storage.GetOrSetBalance(ctx, account, currency, newBlock2) assert.NoError(t, err) - assert.Equal(t, result, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) }) t.Run("Discard transaction", func(t *testing.T) { @@ -377,11 +452,12 @@ func TestBalance(t *testing.T) { newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: currency, Block: newBlock3, - Difference: amount.Value, + BalanceDifference: amount.Value, + SeqNumDifference: int32(0), }, nil, ) @@ -398,8 +474,16 @@ func TestBalance(t *testing.T) { currency, newBlock2.Index, ) + expectedBalanceSeq := &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "50", + Currency: currency, + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4), + } assert.NoError(t, err) - assert.Equal(t, result, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) txn.Discard(ctx) }) @@ -409,11 +493,12 @@ func TestBalance(t *testing.T) { newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: largeDeduction.Currency, Block: newBlock3, - Difference: largeDeduction.Value, + BalanceDifference: largeDeduction.Value, + SeqNumDifference: int32(1), }, nil, ) @@ -421,7 +506,8 @@ func TestBalance(t *testing.T) { assert.False(t, newAccount) txn.Discard(ctx) }) - + // TODO: Attempt modification to push sequence num negative on new acct + // TODO: Attempt modification to push sequence num negative on existing acct t.Run("Attempt modification to push balance negative on new acct", func(t *testing.T) { txn := storage.db.Transaction(ctx) mockHelper.On( @@ -434,14 +520,26 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "0", Currency: largeDeduction.Currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + account2, + largeDeduction.Currency, + (*types.BlockIdentifier)(nil), + ).Return( + int32(0), + nil, + ).Once() newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account2, Currency: largeDeduction.Currency, Block: newBlock2, - Difference: largeDeduction.Value, + BalanceDifference: largeDeduction.Value, + SeqNumDifference: int32(1), }, nil, ) @@ -463,14 +561,26 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "0", Currency: amount.Currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + subAccount, + amount.Currency, + (*types.BlockIdentifier)(nil), + ).Return( + int32(0), + nil, + ).Once() newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: subAccount, Currency: amount.Currency, Block: newBlock, - Difference: amount.Value, + BalanceDifference: amount.Value, + SeqNumDifference: int32(0), }, nil, ) @@ -484,8 +594,14 @@ func TestBalance(t *testing.T) { amount.Currency, newBlock, ) + + expectedBalanceSeq := &types.BalanceSeq{ + Amount: amount, + SeqNumSupport: seqNumSupported, + Seq: int32(0), + } assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) }) t.Run("sub account metadata set and get balance", func(t *testing.T) { @@ -500,14 +616,26 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "0", Currency: amount.Currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + subAccountMetadata, + amount.Currency, + (*types.BlockIdentifier)(nil), + ).Return( + int32(1), + nil, + ).Once() + newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: subAccountMetadata, Currency: amount.Currency, Block: newBlock, - Difference: amount.Value, + BalanceDifference: amount.Value, }, nil, ) @@ -521,8 +649,15 @@ func TestBalance(t *testing.T) { amount.Currency, newBlock, ) + + expectedBalanceSeq := &types.BalanceSeq{ + Amount: amount, + SeqNumSupport: seqNumSupported, + Seq: int32(1), + } + assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) }) t.Run("sub account unique metadata set and get balance", func(t *testing.T) { @@ -537,14 +672,26 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "0", Currency: amount.Currency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + subAccountMetadata2, + amount.Currency, + (*types.BlockIdentifier)(nil), + ).Return( + int32(1), + nil, + ).Once() + newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: subAccountMetadata2, Currency: amount.Currency, Block: newBlock, - Difference: amount.Value, + BalanceDifference: amount.Value, }, nil, ) @@ -558,8 +705,15 @@ func TestBalance(t *testing.T) { amount.Currency, newBlock, ) + + expectedBalanceSeq := &types.BalanceSeq{ + Amount: amount, + SeqNumSupport: seqNumSupported, + Seq: int32(1), + } + assert.NoError(t, err) - assert.Equal(t, amount, retrievedAmount) + assert.Equal(t, expectedBalanceSeq, retrievedAmount) }) t.Run("balance exemption update", func(t *testing.T) { @@ -569,9 +723,13 @@ func TestBalance(t *testing.T) { ctx, txn, exemptionAccount, - &types.Amount{ - Value: "0", - Currency: exemptionCurrency, + &types.BalanceSeq{ + Amount: &types.Amount{ + Value: "0", + Currency: exemptionCurrency, + }, + SeqNumSupport: seqNumSupported, + Seq: int32(0), }, genesisBlock, ) @@ -589,15 +747,28 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "150", Currency: exemptionCurrency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + exemptionAccount, + exemptionCurrency, + newBlock, + ).Return( + int32(2), + nil, + ).Once() + txn = storage.db.Transaction(ctx) newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: exemptionAccount, Currency: exemptionCurrency, Block: newBlock, - Difference: "-10", + BalanceDifference: "-10", + SeqNumDifference: int32(1), }, nil, ) @@ -612,7 +783,8 @@ func TestBalance(t *testing.T) { newBlock, ) assert.NoError(t, err) - assert.Equal(t, "150", retrievedAmount.Value) + assert.Equal(t, "150", retrievedAmount.Amount.Value) + assert.Equal(t, int32(2), retrievedAmount.Seq) // Successful (balance == computed) mockHelper.On( @@ -625,15 +797,28 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "200", Currency: exemptionCurrency}, nil, ).Once() + + mockHelper.On( + "AccountSeqNum", + ctx, + exemptionAccount, + exemptionCurrency, + newBlock3, + ).Return( + int32(2), + nil, + ).Once() + txn = storage.db.Transaction(ctx) newAccount, err = storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: exemptionAccount, Currency: exemptionCurrency, Block: newBlock3, - Difference: "50", + BalanceDifference: "50", + SeqNumDifference: int32(0), }, nil, ) @@ -648,7 +833,8 @@ func TestBalance(t *testing.T) { newBlock3, ) assert.NoError(t, err) - assert.Equal(t, "200", retrievedAmount.Value) + assert.Equal(t, "200", retrievedAmount.Amount.Value) + assert.Equal(t, int32(2), retrievedAmount.Seq) // Unsuccessful (balance < computed) mockHelper.On( @@ -661,15 +847,17 @@ func TestBalance(t *testing.T) { &types.Amount{Value: "10", Currency: exemptionCurrency}, nil, ).Once() + txn = storage.db.Transaction(ctx) newAccount, err = storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: exemptionAccount, Currency: exemptionCurrency, Block: newBlock4, - Difference: "50", + BalanceDifference: "50", + SeqNumDifference: int32(0), }, nil, ) @@ -684,7 +872,8 @@ func TestBalance(t *testing.T) { newBlock4, ) assert.NoError(t, err) - assert.Equal(t, "200", retrievedAmount.Value) + assert.Equal(t, "200", retrievedAmount.Amount.Value) + assert.Equal(t, int32(2), retrievedAmount.Seq) }) t.Run("get all set AccountCurrency", func(t *testing.T) { @@ -738,10 +927,12 @@ func TestBalance(t *testing.T) { newBlock3, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "200", + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ + Value: "50", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4)}, retrievedAmount) retrievedAmount, err = storage.GetOrSetBalance( ctx, account, @@ -749,10 +940,12 @@ func TestBalance(t *testing.T) { newBlock2, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "200", + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ + Value: "50", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4)}, retrievedAmount) retrievedAmount, err = storage.GetOrSetBalance( ctx, account, @@ -760,23 +953,27 @@ func TestBalance(t *testing.T) { newBlock, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ Value: "100", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(3)}, retrievedAmount) }) + // After this test, account at newBlock3 will be: balance 50, seqNum: 4 t.Run("update existing balance", func(t *testing.T) { txn := storage.db.Transaction(ctx) orphanValue, _ := new(big.Int).SetString(largeDeduction.Value, 10) newAccount, err := storage.UpdateBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: largeDeduction.Currency, Block: newBlock2, - Difference: new(big.Int).Neg(orphanValue).String(), + BalanceDifference: new(big.Int).Neg(orphanValue).String(), + SeqNumDifference: int32(0), }, nil, ) @@ -790,10 +987,12 @@ func TestBalance(t *testing.T) { newBlock3.Index, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "1200", + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ + Value: "1050", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4)}, retrievedAmount) txn.Discard(ctx) retrievedAmount, err = storage.GetOrSetBalance( @@ -803,10 +1002,12 @@ func TestBalance(t *testing.T) { newBlock3, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "200", + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ + Value: "50", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(4)}, retrievedAmount) }) t.Run("orphan balance correctly", func(t *testing.T) { @@ -817,16 +1018,18 @@ func TestBalance(t *testing.T) { newBlock, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ Value: "100", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(3)}, retrievedAmount) txn := storage.db.Transaction(ctx) shouldRemove, err := storage.OrphanBalance( ctx, txn, - &parser.BalanceChange{ + &parser.BalanceSeqChange{ Account: account, Currency: largeDeduction.Currency, Block: newBlock, @@ -843,10 +1046,12 @@ func TestBalance(t *testing.T) { newBlock, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ Value: "0", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(0)}, retrievedAmount) }) t.Run("prune index", func(t *testing.T) { @@ -865,10 +1070,13 @@ func TestBalance(t *testing.T) { newBlock3, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ + + assert.Equal(t,&types.BalanceSeq{Amount: &types.Amount{ Value: "0", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(0)}, retrievedAmount) retrievedAmount, err = storage.GetOrSetBalance( ctx, account, @@ -876,10 +1084,12 @@ func TestBalance(t *testing.T) { newBlock2, ) assert.NoError(t, err) - assert.Equal(t, &types.Amount{ + assert.Equal(t, &types.BalanceSeq{Amount: &types.Amount{ Value: "0", Currency: largeDeduction.Currency, - }, retrievedAmount) + }, + SeqNumSupport: seqNumSupported, + Seq: int32(0)}, retrievedAmount) retrievedAmount, err = storage.GetOrSetBalance( ctx, account, @@ -894,998 +1104,1071 @@ func TestBalance(t *testing.T) { mockHandler.AssertExpectations(t) } -func TestSetBalanceImported(t *testing.T) { - var ( - blockIdentifier = &types.BlockIdentifier{ - Hash: "block", - Index: 1, - } - - accountCoin = &types.AccountIdentifier{ - Address: "test", - } - - currency = &types.Currency{ - Symbol: "BLAH", - Decimals: 2, - } - - amountCoins = &types.Amount{ - Value: "60", - Currency: currency, - } - - accountCoins = []*types.Coin{ - { - CoinIdentifier: &types.CoinIdentifier{Identifier: "coin1"}, - Amount: &types.Amount{ - Value: "30", - Currency: currency, - }, - }, - { - CoinIdentifier: &types.CoinIdentifier{Identifier: "coin2"}, - Amount: &types.Amount{ - Value: "30", - Currency: currency, - }, - }, - } - - accountBalance = &types.AccountIdentifier{ - Address: "test2", - } - - amountBalance = &types.Amount{ - Value: "100", - Currency: currency, - } - - accBalance1 = &utils.AccountBalance{ - Account: accountCoin, - Amount: amountCoins, - Coins: accountCoins, - Block: blockIdentifier, - } - - accBalance2 = &utils.AccountBalance{ - Account: accountBalance, - Amount: amountBalance, - Block: blockIdentifier, - } - ) - - ctx := context.Background() - - newDir, err := utils.CreateTempDir() - assert.NoError(t, err) - defer utils.RemoveTempDir(newDir) - - database, err := newTestBadgerDatabase(ctx, newDir) - assert.NoError(t, err) - defer database.Close(ctx) - - storage := NewBalanceStorage(database) - mockHelper := &mocks.BalanceStorageHelper{} - mockHandler := &mocks.BalanceStorageHandler{} - mockHelper.On("Asserter").Return(baseAsserter()) - mockHelper.On("ExemptFunc").Return(exemptFunc()) - mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) - storage.Initialize(mockHelper, mockHandler) - - t.Run("Set balance successfully", func(t *testing.T) { - mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Twice() - err = storage.SetBalanceImported( - ctx, - nil, - []*utils.AccountBalance{accBalance1, accBalance2}, - ) - assert.NoError(t, err) - - amount1, err := storage.GetOrSetBalance( - ctx, - accountCoin, - currency, - blockIdentifier, - ) - assert.NoError(t, err) - assert.Equal(t, amount1.Value, amountCoins.Value) - - amount2, err := storage.GetOrSetBalance( - ctx, - accountBalance, - currency, - blockIdentifier, - ) - assert.NoError(t, err) - assert.Equal(t, amount2.Value, amountBalance.Value) - }) - - mockHelper.AssertExpectations(t) - mockHandler.AssertExpectations(t) -} - -func TestBootstrapBalances(t *testing.T) { - var ( - genesisBlockIdentifier = &types.BlockIdentifier{ - Index: 0, - Hash: "0", - } - - newBlock = &types.BlockIdentifier{ - Index: 1, - Hash: "1", - } - - account = &types.AccountIdentifier{ - Address: "hello", - } - - account2 = &types.AccountIdentifier{ - Address: "hello world", - } - - account3 = &types.AccountIdentifier{ - Address: "hello world new", - } - ) - - ctx := context.Background() - - newDir, err := utils.CreateTempDir() - assert.NoError(t, err) - defer utils.RemoveTempDir(newDir) - - database, err := newTestBadgerDatabase(ctx, newDir) - assert.NoError(t, err) - defer database.Close(ctx) - - storage := NewBalanceStorage(database) - mockHelper := &mocks.BalanceStorageHelper{} - mockHandler := &mocks.BalanceStorageHandler{} - mockHelper.On("Asserter").Return(baseAsserter()) - mockHelper.On("ExemptFunc").Return(exemptFunc()) - mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) - bootstrapBalancesFile := path.Join(newDir, "balances.csv") - - t.Run("File doesn't exist", func(t *testing.T) { - err = storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.Contains(t, err.Error(), "no such file or directory") - }) - - // Initialize file - amount := &types.Amount{ - Value: "10", - Currency: &types.Currency{ - Symbol: "BTC", - Decimals: 8, - }, - } - - file, err := json.MarshalIndent([]*BootstrapBalance{ - { - Account: account, - Value: amount.Value, - Currency: amount.Currency, - }, - { - Account: account2, - Value: amount.Value, - Currency: amount.Currency, - }, - { - Account: account3, - Value: amount.Value, - Currency: amount.Currency, - }, - }, "", " ") - assert.NoError(t, err) - - assert.NoError( - t, - ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), - ) - - t.Run("run before initializing helper/handler", func(t *testing.T) { - err = storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.True(t, errors.Is(err, storageErrs.ErrHelperHandlerMissing)) - }) - - storage.Initialize(mockHelper, mockHandler) - t.Run("Set balance successfully", func(t *testing.T) { - mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Times(3) - err = storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.NoError(t, err) - - retrievedAmount, err := storage.GetOrSetBalance( - ctx, - account, - amount.Currency, - genesisBlockIdentifier, - ) - - assert.Equal(t, amount, retrievedAmount) - assert.NoError(t, err) - - retrievedAmount2, err := storage.GetOrSetBalance( - ctx, - account2, - amount.Currency, - genesisBlockIdentifier, - ) - - assert.Equal(t, amount, retrievedAmount2) - assert.NoError(t, err) - - retrievedAmount3, err := storage.GetOrSetBalance( - ctx, - account3, - amount.Currency, - genesisBlockIdentifier, - ) - - assert.Equal(t, amount, retrievedAmount3) - assert.NoError(t, err) - - // Attempt to update balance - txn := storage.db.Transaction(ctx) - newAccount, err := storage.UpdateBalance( - ctx, - txn, - &parser.BalanceChange{ - Account: account, - Currency: amount.Currency, - Block: newBlock, - Difference: "100", - }, - newBlock, - ) - assert.False(t, newAccount) - assert.NoError(t, err) - assert.NoError(t, txn.Commit(ctx)) - - retrievedAmount, err = storage.GetOrSetBalance( - ctx, - account, - amount.Currency, - newBlock, - ) - - assert.Equal(t, "110", retrievedAmount.Value) - assert.NoError(t, err) - }) - - t.Run("Invalid file contents", func(t *testing.T) { - assert.NoError( - t, - ioutil.WriteFile( - bootstrapBalancesFile, - []byte("bad file"), - utils.DefaultFilePermissions, - ), - ) - - err := storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.Error(t, err) - }) - - t.Run("Invalid account balance", func(t *testing.T) { - amount := &types.Amount{ - Value: "-10", - Currency: &types.Currency{ - Symbol: "BTC", - Decimals: 8, - }, - } - - file, err := json.MarshalIndent([]*BootstrapBalance{ - { - Account: account, - Value: amount.Value, - Currency: amount.Currency, - }, - }, "", " ") - assert.NoError(t, err) - - assert.NoError( - t, - ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), - ) - - err = storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.EqualError(t, err, "cannot bootstrap zero or negative balance -10") - }) - - t.Run("Invalid account value", func(t *testing.T) { - amount := &types.Amount{ - Value: "goodbye", - Currency: &types.Currency{ - Symbol: "BTC", - Decimals: 8, - }, - } - - file, err := json.MarshalIndent([]*BootstrapBalance{ - { - Account: account, - Value: amount.Value, - Currency: amount.Currency, - }, - }, "", " ") - assert.NoError(t, err) - - assert.NoError( - t, - ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), - ) - - err = storage.BootstrapBalances( - ctx, - bootstrapBalancesFile, - genesisBlockIdentifier, - ) - assert.EqualError(t, err, "goodbye is not an integer") - }) - - mockHelper.AssertExpectations(t) - mockHandler.AssertExpectations(t) -} - -func TestBalanceReconciliation(t *testing.T) { - var ( - account = &types.AccountIdentifier{ - Address: "blah", - } - subAccountMetadata2 = &types.AccountIdentifier{ - Address: "blah", - SubAccount: &types.SubAccountIdentifier{ - Address: "stake", - Metadata: map[string]interface{}{ - "cool": float64(10), - }, - }, - } - currency = &types.Currency{ - Symbol: "BLAH", - Decimals: 2, - } - currency2 = &types.Currency{ - Symbol: "BLAH2", - Decimals: 4, - } - genesisBlock = &types.BlockIdentifier{ - Hash: "0", - Index: 0, - } - newBlock = &types.BlockIdentifier{ - Hash: "kdasdj", - Index: 123890, - } - ) - - ctx := context.Background() - - newDir, err := utils.CreateTempDir() - assert.NoError(t, err) - defer utils.RemoveTempDir(newDir) - - database, err := newTestBadgerDatabase(ctx, newDir) - assert.NoError(t, err) - defer database.Close(ctx) - - storage := NewBalanceStorage(database) - mockHelper := &mocks.BalanceStorageHelper{} - mockHandler := &mocks.BalanceStorageHandler{} - mockHelper.On("Asserter").Return(baseAsserter()) - mockHelper.On("ExemptFunc").Return(exemptFunc()) - mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) - - t.Run("test estimated before helper/handler", func(t *testing.T) { - coverage, err := storage.EstimatedReconciliationCoverage(ctx) - assert.Equal(t, float64(-1), coverage) - assert.True(t, errors.Is(err, storageErrs.ErrHelperHandlerMissing)) - }) - - storage.Initialize(mockHelper, mockHandler) - t.Run("attempt to store reconciliation for non-existent account", func(t *testing.T) { - err := storage.Reconciled(ctx, account, currency, genesisBlock) - assert.NoError(t, err) - - coverage, err := storage.ReconciliationCoverage(ctx, 0) - assert.NoError(t, err) - assert.Equal(t, 0.0, coverage) - }) - - t.Run("set balance", func(t *testing.T) { - txn := storage.db.Transaction(ctx) - newAccount, err := storage.UpdateBalance( - ctx, - txn, - &parser.BalanceChange{ - Account: account, - Currency: currency, - Block: genesisBlock, - Difference: "100", - }, - genesisBlock, - ) - assert.True(t, newAccount) - assert.NoError(t, err) - assert.NoError(t, txn.Commit(ctx)) - - coverage, err := storage.ReconciliationCoverage(ctx, 0) - assert.NoError(t, err) - assert.Equal(t, 0.0, coverage) - }) - - t.Run("store reconciliation", func(t *testing.T) { - err := storage.Reconciled(ctx, account, currency, genesisBlock) - assert.NoError(t, err) - - txn := storage.db.Transaction(ctx) - newAccount, err := storage.UpdateBalance( - ctx, - txn, - &parser.BalanceChange{ - Account: account, - Currency: currency2, - Block: genesisBlock, - Difference: "200", - }, - genesisBlock, - ) - assert.True(t, newAccount) - assert.NoError(t, err) - assert.NoError(t, txn.Commit(ctx)) - - coverage, err := storage.ReconciliationCoverage(ctx, 0) - assert.NoError(t, err) - assert.Equal(t, 0.5, coverage) - - coverage, err = storage.ReconciliationCoverage(ctx, 1) - assert.NoError(t, err) - assert.Equal(t, 0.0, coverage) - }) - - t.Run("update reconciliation", func(t *testing.T) { - err := storage.Reconciled(ctx, account, currency, newBlock) - assert.NoError(t, err) - - coverage, err := storage.ReconciliationCoverage(ctx, 0) - assert.NoError(t, err) - assert.Equal(t, 0.5, coverage) - - coverage, err = storage.ReconciliationCoverage(ctx, 1) - assert.NoError(t, err) - assert.Equal(t, 0.5, coverage) - }) - - t.Run("update reconciliation to old block", func(t *testing.T) { - err := storage.Reconciled(ctx, account, currency, genesisBlock) - assert.NoError(t, err) - - coverage, err := storage.ReconciliationCoverage(ctx, 0) - assert.NoError(t, err) - assert.Equal(t, 0.5, coverage) - - // We should skip update so this stays 0.5 - coverage, err = storage.ReconciliationCoverage(ctx, 1) - assert.NoError(t, err) - assert.Equal(t, 0.5, coverage) - }) - - t.Run("add unreconciled", func(t *testing.T) { - txn := storage.db.Transaction(ctx) - newAccount, err := storage.UpdateBalance( - ctx, - txn, - &parser.BalanceChange{ - Account: subAccountMetadata2, - Currency: currency2, - Block: newBlock, - Difference: "200", - }, - newBlock, - ) - assert.True(t, newAccount) - assert.NoError(t, err) - assert.NoError(t, txn.Commit(ctx)) - - coverage, err := storage.ReconciliationCoverage(ctx, 1) - assert.NoError(t, err) - assert.Equal(t, float64(1)/float64(3), coverage) - }) - - t.Run("test estimated no reconciliations", func(t *testing.T) { - mockHelper.On("AccountsReconciled", ctx, mock.Anything).Return(big.NewInt(0), nil).Once() - mockHelper.On("AccountsSeen", ctx, mock.Anything).Return(big.NewInt(0), nil).Once() - coverage, err := storage.EstimatedReconciliationCoverage(ctx) - assert.Equal(t, float64(0), coverage) - assert.NoError(t, err) - }) - - t.Run("test estimated some reconciliations", func(t *testing.T) { - mockHelper.On("AccountsReconciled", ctx, mock.Anything).Return(big.NewInt(1), nil).Once() - mockHelper.On("AccountsSeen", ctx, mock.Anything).Return(big.NewInt(2), nil).Once() - coverage, err := storage.EstimatedReconciliationCoverage(ctx) - assert.Equal(t, float64(0.5), coverage) - assert.NoError(t, err) - }) - - mockHelper.AssertExpectations(t) - mockHandler.AssertExpectations(t) -} - -func TestBlockSyncing(t *testing.T) { - ctx := context.Background() - - newDir, err := utils.CreateTempDir() - assert.NoError(t, err) - defer utils.RemoveTempDir(newDir) - - database, err := newTestBadgerDatabase(ctx, newDir) - assert.NoError(t, err) - defer database.Close(ctx) - - storage := NewBalanceStorage(database) - mockHelper := &mocks.BalanceStorageHelper{} - mockHandler := &mocks.BalanceStorageHandler{} - mockHelper.On("Asserter").Return(baseAsserter()) - mockHelper.On("ExemptFunc").Return(exemptFunc()) - mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) - storage.Initialize(mockHelper, mockHandler) - - // Genesis block with no transactions - b0 := &types.Block{ - BlockIdentifier: &types.BlockIdentifier{ - Index: 0, - Hash: "0", - }, - ParentBlockIdentifier: &types.BlockIdentifier{ - Index: 0, - Hash: "0", - }, - } - - addr1 := &types.AccountIdentifier{ - Address: "addr1", - } - addr2 := &types.AccountIdentifier{ - Address: "addr2", - } - curr := &types.Currency{ - Symbol: "ETH", - Decimals: 18, - } - - // Block 1 with transaction - b1 := &types.Block{ - BlockIdentifier: &types.BlockIdentifier{ - Index: 1, - Hash: "1", - }, - ParentBlockIdentifier: &types.BlockIdentifier{ - Index: 0, - Hash: "0", - }, - Transactions: []*types.Transaction{ - { - TransactionIdentifier: &types.TransactionIdentifier{ - Hash: "1_0", - }, - Operations: []*types.Operation{ - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 0, - }, - Account: addr1, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "100", - Currency: curr, - }, - }, - }, - }, - }, - } - - // Another Transaction for some acocunt in Block 1 - b2 := &types.Block{ - BlockIdentifier: &types.BlockIdentifier{ - Index: 2, - Hash: "2", - }, - ParentBlockIdentifier: &types.BlockIdentifier{ - Index: 1, - Hash: "1", - }, - Transactions: []*types.Transaction{ - { - TransactionIdentifier: &types.TransactionIdentifier{ - Hash: "2_0", - }, - Operations: []*types.Operation{ - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 0, - }, - Account: addr1, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "-50", - Currency: curr, - }, - }, - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 1, - }, - Account: addr2, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "50", - Currency: curr, - }, - }, - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 2, - }, - Account: addr1, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "-1", - Currency: curr, - }, - }, - }, - }, - }, - } - - // Orphaned block with slightly different tx - b2a := &types.Block{ - BlockIdentifier: &types.BlockIdentifier{ - Index: 2, - Hash: "2a", - }, - ParentBlockIdentifier: &types.BlockIdentifier{ - Index: 1, - Hash: "1", - }, - Transactions: []*types.Transaction{ - { - TransactionIdentifier: &types.TransactionIdentifier{ - Hash: "2_0", - }, - Operations: []*types.Operation{ - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 0, - }, - Account: addr1, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "-100", - Currency: curr, - }, - }, - { - OperationIdentifier: &types.OperationIdentifier{ - Index: 1, - }, - Account: addr2, - Status: types.String("Success"), - Type: "Transfer", - Amount: &types.Amount{ - Value: "100", - Currency: curr, - }, - }, - }, - }, - }, - } - - t.Run("add genesis block", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - _, err = storage.AddingBlock(gctx, g, b0, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b0.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - }) - - t.Run("add block 1", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - mockHelper.On( - "AccountBalance", - gctx, - addr1, - curr, - b0.BlockIdentifier, - ).Return( - &types.Amount{Value: "1", Currency: curr}, - nil, - ).Once() - mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() - _, err = storage.AddingBlock(gctx, g, b1, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - }) - - t.Run("add block 2", func(t *testing.T) { - // Reconcile a previously seen account - err := storage.Reconciled(ctx, addr1, curr, b1.BlockIdentifier) - assert.NoError(t, err) - - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - mockHelper.On( - "AccountBalance", - gctx, - addr2, - curr, - b1.BlockIdentifier, - ).Return( - &types.Amount{Value: "0", Currency: curr}, - nil, - ).Once() - mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() - mockHandler.On("AccountsReconciled", gctx, dbTx, 1).Return(nil).Once() - _, err = storage.AddingBlock(gctx, g, b2, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "50", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "50", - Currency: curr, - }, amount) - }) - - t.Run("orphan block 2", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - commitWorker, err := storage.RemovingBlock(gctx, g, b2, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - mockHandler.On("BlockRemoved", ctx, b2, mock.Anything).Return(nil).Once() - mockHandler.On("AccountsSeen", ctx, mock.Anything, -1).Return(nil).Once() - assert.NoError(t, commitWorker(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - }) - - t.Run("orphan block 1", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - commitWorker, err := storage.RemovingBlock(gctx, g, b1, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - mockHandler.On("BlockRemoved", ctx, b1, mock.Anything).Return(nil).Once() - mockHandler.On("AccountsSeen", ctx, mock.Anything, -1).Return(nil).Once() - mockHandler.On("AccountsReconciled", ctx, mock.Anything, -1).Return(nil).Once() - assert.NoError(t, commitWorker(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b0.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - }) - - t.Run("add block 1", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - mockHelper.On( - "AccountBalance", - gctx, - addr1, - curr, - b0.BlockIdentifier, - ).Return( - &types.Amount{Value: "1", Currency: curr}, - nil, - ).Once() - mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() - _, err = storage.AddingBlock(gctx, g, b1, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) - assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) - assert.Nil(t, amount) - }) - - t.Run("add block 2a", func(t *testing.T) { - dbTx := database.Transaction(ctx) - g, gctx := errgroup.WithContext(ctx) - mockHelper.On( - "AccountBalance", - gctx, - addr2, - curr, - b1.BlockIdentifier, - ).Return( - &types.Amount{Value: "0", Currency: curr}, - nil, - ).Once() - mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() - _, err = storage.AddingBlock(gctx, g, b2a, dbTx) - assert.NoError(t, err) - assert.NoError(t, g.Wait()) - assert.NoError(t, dbTx.Commit(ctx)) - - amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "101", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "0", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "1", - Currency: curr, - }, amount) - amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) - assert.NoError(t, err) - assert.Equal(t, &types.Amount{ - Value: "100", - Currency: curr, - }, amount) - }) - - mockHelper.AssertExpectations(t) - mockHandler.AssertExpectations(t) -} +//func TestSetBalanceImported(t *testing.T) { +// var ( +// blockIdentifier = &types.BlockIdentifier{ +// Hash: "block", +// Index: 1, +// } +// +// accountCoin = &types.AccountIdentifier{ +// Address: "test", +// } +// +// currency = &types.Currency{ +// Symbol: "BLAH", +// Decimals: 2, +// } +// +// amountCoins = &types.Amount{ +// Value: "60", +// Currency: currency, +// } +// +// accountCoins = []*types.Coin{ +// { +// CoinIdentifier: &types.CoinIdentifier{Identifier: "coin1"}, +// Amount: &types.Amount{ +// Value: "30", +// Currency: currency, +// }, +// }, +// { +// CoinIdentifier: &types.CoinIdentifier{Identifier: "coin2"}, +// Amount: &types.Amount{ +// Value: "30", +// Currency: currency, +// }, +// }, +// } +// +// accountBalance = &types.AccountIdentifier{ +// Address: "test2", +// } +// +// amountBalance = &types.Amount{ +// Value: "100", +// Currency: currency, +// } +// +// accBalance1 = &utils.AccountBalance{ +// Account: accountCoin, +// Amount: amountCoins, +// Coins: accountCoins, +// Block: blockIdentifier, +// } +// +// accBalance2 = &utils.AccountBalance{ +// Account: accountBalance, +// Amount: amountBalance, +// Block: blockIdentifier, +// } +// ) +// +// ctx := context.Background() +// +// newDir, err := utils.CreateTempDir() +// assert.NoError(t, err) +// defer utils.RemoveTempDir(newDir) +// +// database, err := newTestBadgerDatabase(ctx, newDir) +// assert.NoError(t, err) +// defer database.Close(ctx) +// +// storage := NewBalanceStorage(database) +// mockHelper := &mocks.BalanceStorageHelper{} +// mockHandler := &mocks.BalanceStorageHandler{} +// mockHelper.On("Asserter").Return(baseAsserter()) +// mockHelper.On("ExemptFunc").Return(exemptFunc()) +// mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) +// storage.Initialize(mockHelper, mockHandler) +// +// t.Run("Set balance successfully", func(t *testing.T) { +// mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Twice() +// err = storage.SetBalanceImported( +// ctx, +// nil, +// []*utils.AccountBalance{accBalance1, accBalance2}, +// ) +// assert.NoError(t, err) +// +// amount1, err := storage.GetOrSetBalance( +// ctx, +// accountCoin, +// currency, +// blockIdentifier, +// ) +// assert.NoError(t, err) +// assert.Equal(t, amount1.Value, amountCoins.Value) +// +// amount2, err := storage.GetOrSetBalance( +// ctx, +// accountBalance, +// currency, +// blockIdentifier, +// ) +// assert.NoError(t, err) +// assert.Equal(t, amount2.Value, amountBalance.Value) +// }) +// +// mockHelper.AssertExpectations(t) +// mockHandler.AssertExpectations(t) +//} + +//func TestBootstrapBalances(t *testing.T) { +// var ( +// blockIdentifier = &types.BlockIdentifier{ +// Hash: "block", +// Index: 1, +// } +// +// accountCoin = &types.AccountIdentifier{ +// Address: "test", +// } +// +// currency = &types.Currency{ +// Symbol: "BLAH", +// Decimals: 2, +// } +// +// amountCoins = &types.Amount{ +// Value: "60", +// Currency: currency, +// } +// +// accountCoins = []*types.Coin{ +// { +// CoinIdentifier: &types.CoinIdentifier{Identifier: "coin1"}, +// Amount: &types.Amount{ +// Value: "30", +// Currency: currency, +// }, +// }, +// { +// CoinIdentifier: &types.CoinIdentifier{Identifier: "coin2"}, +// Amount: &types.Amount{ +// Value: "30", +// Currency: currency, +// }, +// }, +// } +// +// accountBalance = &types.AccountIdentifier{ +// Address: "test2", +// } +// +// amountBalance = &types.Amount{ +// Value: "100", +// Currency: currency, +// } +// +// accBalance1 = &utils.AccountBalance{ +// Account: accountCoin, +// Amount: amountCoins, +// Coins: accountCoins, +// Block: blockIdentifier, +// } +// +// accBalance2 = &utils.AccountBalance{ +// Account: accountBalance, +// Amount: amountBalance, +// Block: blockIdentifier, +// } +// ) +// +// ctx := context.Background() +// +// newDir, err := utils.CreateTempDir() +// assert.NoError(t, err) +// defer utils.RemoveTempDir(newDir) +// +// database, err := newTestBadgerDatabase(ctx, newDir) +// assert.NoError(t, err) +// defer database.Close(ctx) +// +// storage := NewBalanceStorage(database) +// mockHelper := &mocks.BalanceStorageHelper{} +// mockHandler := &mocks.BalanceStorageHandler{} +// mockHelper.On("Asserter").Return(baseAsserter()) +// mockHelper.On("ExemptFunc").Return(exemptFunc()) +// mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) +// storage.Initialize(mockHelper, mockHandler) +// +// t.Run("Set balance successfully", func(t *testing.T) { +// mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Twice() +// err = storage.SetBalanceImported( +// ctx, +// nil, +// []*utils.AccountBalance{accBalance1, accBalance2}, +// ) +// assert.NoError(t, err) +// +// amount1, err := storage.GetOrSetBalance( +// ctx, +// accountCoin, +// currency, +// blockIdentifier, +// ) +// assert.NoError(t, err) +// assert.Equal(t, amount1.Value, amountCoins.Value) +// +// amount2, err := storage.GetOrSetBalance( +// ctx, +// accountBalance, +// currency, +// blockIdentifier, +// ) +// assert.NoError(t, err) +// assert.Equal(t, amount2.Value, amountBalance.Value) +// }) +// +// mockHelper.AssertExpectations(t) +// mockHandler.AssertExpectations(t) +//} +// +//func TestBootstrapBalances(t *testing.T) { +// var ( +// genesisBlockIdentifier = &types.BlockIdentifier{ +// Index: 0, +// Hash: "0", +// } +// +// newBlock = &types.BlockIdentifier{ +// Index: 1, +// Hash: "1", +// } +// +// account = &types.AccountIdentifier{ +// Address: "hello", +// } +// ) +// +// ctx := context.Background() +// +// newDir, err := utils.CreateTempDir() +// assert.NoError(t, err) +// defer utils.RemoveTempDir(newDir) +// +// database, err := newTestBadgerDatabase(ctx, newDir) +// assert.NoError(t, err) +// defer database.Close(ctx) +// +// storage := NewBalanceStorage(database) +// mockHelper := &mocks.BalanceStorageHelper{} +// mockHandler := &mocks.BalanceStorageHandler{} +// mockHelper.On("Asserter").Return(baseAsserter()) +// mockHelper.On("ExemptFunc").Return(exemptFunc()) +// mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) +// bootstrapBalancesFile := path.Join(newDir, "balances.csv") +// +// t.Run("File doesn't exist", func(t *testing.T) { +// err = storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.Contains(t, err.Error(), "no such file or directory") +// }) +// +// // Initialize file +// amount := &types.Amount{ +// Value: "10", +// Currency: &types.Currency{ +// Symbol: "BTC", +// Decimals: 8, +// }, +// } +// +// file, err := json.MarshalIndent([]*BootstrapBalance{ +// { +// Account: account, +// Value: amount.Value, +// Currency: amount.Currency, +// }, +// }, "", " ") +// assert.NoError(t, err) +// +// assert.NoError( +// t, +// ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), +// ) +// +// t.Run("run before initializing helper/handler", func(t *testing.T) { +// err = storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.True(t, errors.Is(err, storageErrs.ErrHelperHandlerMissing)) +// }) +// +// storage.Initialize(mockHelper, mockHandler) +// t.Run("Set balance successfully", func(t *testing.T) { +// mockHandler.On("AccountsSeen", ctx, mock.Anything, 1).Return(nil).Once() +// err = storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.NoError(t, err) +// +// retrievedAmount, err := storage.GetOrSetBalance( +// ctx, +// account, +// amount.Currency, +// genesisBlockIdentifier, +// ) +// +// assert.Equal(t, amount, retrievedAmount) +// assert.NoError(t, err) +// +// // Attempt to update balance +// txn := storage.db.Transaction(ctx) +// newAccount, err := storage.UpdateBalance( +// ctx, +// txn, +// &parser.BalanceChange{ +// Account: account, +// Currency: amount.Currency, +// Block: newBlock, +// Difference: "100", +// }, +// newBlock, +// ) +// assert.False(t, newAccount) +// assert.NoError(t, err) +// assert.NoError(t, txn.Commit(ctx)) +// +// retrievedAmount, err = storage.GetOrSetBalance( +// ctx, +// account, +// amount.Currency, +// newBlock, +// ) +// +// assert.Equal(t, "110", retrievedAmount.Value) +// assert.NoError(t, err) +// }) +// +// t.Run("Invalid file contents", func(t *testing.T) { +// assert.NoError( +// t, +// ioutil.WriteFile( +// bootstrapBalancesFile, +// []byte("bad file"), +// utils.DefaultFilePermissions, +// ), +// ) +// +// err := storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.Error(t, err) +// }) +// +// t.Run("Invalid account balance", func(t *testing.T) { +// amount := &types.Amount{ +// Value: "-10", +// Currency: &types.Currency{ +// Symbol: "BTC", +// Decimals: 8, +// }, +// } +// +// file, err := json.MarshalIndent([]*BootstrapBalance{ +// { +// Account: account, +// Value: amount.Value, +// Currency: amount.Currency, +// }, +// }, "", " ") +// assert.NoError(t, err) +// +// assert.NoError( +// t, +// ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), +// ) +// +// err = storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.EqualError(t, err, "cannot bootstrap zero or negative balance -10") +// }) +// +// t.Run("Invalid account value", func(t *testing.T) { +// amount := &types.Amount{ +// Value: "goodbye", +// Currency: &types.Currency{ +// Symbol: "BTC", +// Decimals: 8, +// }, +// } +// +// file, err := json.MarshalIndent([]*BootstrapBalance{ +// { +// Account: account, +// Value: amount.Value, +// Currency: amount.Currency, +// }, +// }, "", " ") +// assert.NoError(t, err) +// +// assert.NoError( +// t, +// ioutil.WriteFile(bootstrapBalancesFile, file, utils.DefaultFilePermissions), +// ) +// +// err = storage.BootstrapBalances( +// ctx, +// bootstrapBalancesFile, +// genesisBlockIdentifier, +// ) +// assert.EqualError(t, err, "goodbye is not an integer") +// }) +// +// mockHelper.AssertExpectations(t) +// mockHandler.AssertExpectations(t) +//} +// +//func TestBalanceReconciliation(t *testing.T) { +// var ( +// account = &types.AccountIdentifier{ +// Address: "blah", +// } +// subAccountMetadata2 = &types.AccountIdentifier{ +// Address: "blah", +// SubAccount: &types.SubAccountIdentifier{ +// Address: "stake", +// Metadata: map[string]interface{}{ +// "cool": float64(10), +// }, +// }, +// } +// currency = &types.Currency{ +// Symbol: "BLAH", +// Decimals: 2, +// } +// currency2 = &types.Currency{ +// Symbol: "BLAH2", +// Decimals: 4, +// } +// genesisBlock = &types.BlockIdentifier{ +// Hash: "0", +// Index: 0, +// } +// newBlock = &types.BlockIdentifier{ +// Hash: "kdasdj", +// Index: 123890, +// } +// ) +// +// ctx := context.Background() +// +// newDir, err := utils.CreateTempDir() +// assert.NoError(t, err) +// defer utils.RemoveTempDir(newDir) +// +// database, err := newTestBadgerDatabase(ctx, newDir) +// assert.NoError(t, err) +// defer database.Close(ctx) +// +// storage := NewBalanceStorage(database) +// mockHelper := &mocks.BalanceStorageHelper{} +// mockHandler := &mocks.BalanceStorageHandler{} +// mockHelper.On("Asserter").Return(baseAsserter()) +// mockHelper.On("ExemptFunc").Return(exemptFunc()) +// mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) +// +// t.Run("test estimated before helper/handler", func(t *testing.T) { +// coverage, err := storage.EstimatedReconciliationCoverage(ctx) +// assert.Equal(t, float64(-1), coverage) +// assert.True(t, errors.Is(err, storageErrs.ErrHelperHandlerMissing)) +// }) +// +// storage.Initialize(mockHelper, mockHandler) +// t.Run("attempt to store reconciliation for non-existent account", func(t *testing.T) { +// err := storage.Reconciled(ctx, account, currency, genesisBlock) +// assert.NoError(t, err) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 0) +// assert.NoError(t, err) +// assert.Equal(t, 0.0, coverage) +// }) +// +// t.Run("set balance", func(t *testing.T) { +// txn := storage.db.Transaction(ctx) +// newAccount, err := storage.UpdateBalance( +// ctx, +// txn, +// &parser.BalanceChange{ +// Account: account, +// Currency: currency, +// Block: genesisBlock, +// Difference: "100", +// }, +// genesisBlock, +// ) +// assert.True(t, newAccount) +// assert.NoError(t, err) +// assert.NoError(t, txn.Commit(ctx)) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 0) +// assert.NoError(t, err) +// assert.Equal(t, 0.0, coverage) +// }) +// +// t.Run("store reconciliation", func(t *testing.T) { +// err := storage.Reconciled(ctx, account, currency, genesisBlock) +// assert.NoError(t, err) +// +// txn := storage.db.Transaction(ctx) +// newAccount, err := storage.UpdateBalance( +// ctx, +// txn, +// &parser.BalanceChange{ +// Account: account, +// Currency: currency2, +// Block: genesisBlock, +// Difference: "200", +// }, +// genesisBlock, +// ) +// assert.True(t, newAccount) +// assert.NoError(t, err) +// assert.NoError(t, txn.Commit(ctx)) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 0) +// assert.NoError(t, err) +// assert.Equal(t, 0.5, coverage) +// +// coverage, err = storage.ReconciliationCoverage(ctx, 1) +// assert.NoError(t, err) +// assert.Equal(t, 0.0, coverage) +// }) +// +// t.Run("update reconciliation", func(t *testing.T) { +// err := storage.Reconciled(ctx, account, currency, newBlock) +// assert.NoError(t, err) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 0) +// assert.NoError(t, err) +// assert.Equal(t, 0.5, coverage) +// +// coverage, err = storage.ReconciliationCoverage(ctx, 1) +// assert.NoError(t, err) +// assert.Equal(t, 0.5, coverage) +// }) +// +// t.Run("update reconciliation to old block", func(t *testing.T) { +// err := storage.Reconciled(ctx, account, currency, genesisBlock) +// assert.NoError(t, err) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 0) +// assert.NoError(t, err) +// assert.Equal(t, 0.5, coverage) +// +// // We should skip update so this stays 0.5 +// coverage, err = storage.ReconciliationCoverage(ctx, 1) +// assert.NoError(t, err) +// assert.Equal(t, 0.5, coverage) +// }) +// +// t.Run("add unreconciled", func(t *testing.T) { +// txn := storage.db.Transaction(ctx) +// newAccount, err := storage.UpdateBalance( +// ctx, +// txn, +// &parser.BalanceChange{ +// Account: subAccountMetadata2, +// Currency: currency2, +// Block: newBlock, +// Difference: "200", +// }, +// newBlock, +// ) +// assert.True(t, newAccount) +// assert.NoError(t, err) +// assert.NoError(t, txn.Commit(ctx)) +// +// coverage, err := storage.ReconciliationCoverage(ctx, 1) +// assert.NoError(t, err) +// assert.Equal(t, float64(1)/float64(3), coverage) +// }) +// +// t.Run("test estimated no reconciliations", func(t *testing.T) { +// mockHelper.On("AccountsReconciled", ctx, mock.Anything).Return(big.NewInt(0), nil).Once() +// mockHelper.On("AccountsSeen", ctx, mock.Anything).Return(big.NewInt(0), nil).Once() +// coverage, err := storage.EstimatedReconciliationCoverage(ctx) +// assert.Equal(t, float64(0), coverage) +// assert.NoError(t, err) +// }) +// +// t.Run("test estimated some reconciliations", func(t *testing.T) { +// mockHelper.On("AccountsReconciled", ctx, mock.Anything).Return(big.NewInt(1), nil).Once() +// mockHelper.On("AccountsSeen", ctx, mock.Anything).Return(big.NewInt(2), nil).Once() +// coverage, err := storage.EstimatedReconciliationCoverage(ctx) +// assert.Equal(t, float64(0.5), coverage) +// assert.NoError(t, err) +// }) +// +// mockHelper.AssertExpectations(t) +// mockHandler.AssertExpectations(t) +//} +// +//func TestBlockSyncing(t *testing.T) { +// ctx := context.Background() +// +// newDir, err := utils.CreateTempDir() +// assert.NoError(t, err) +// defer utils.RemoveTempDir(newDir) +// +// database, err := newTestBadgerDatabase(ctx, newDir) +// assert.NoError(t, err) +// defer database.Close(ctx) +// +// storage := NewBalanceStorage(database) +// mockHelper := &mocks.BalanceStorageHelper{} +// mockHandler := &mocks.BalanceStorageHandler{} +// mockHelper.On("Asserter").Return(baseAsserter()) +// mockHelper.On("ExemptFunc").Return(exemptFunc()) +// mockHelper.On("BalanceExemptions").Return([]*types.BalanceExemption{}) +// storage.Initialize(mockHelper, mockHandler) +// +// // Genesis block with no transactions +// b0 := &types.Block{ +// BlockIdentifier: &types.BlockIdentifier{ +// Index: 0, +// Hash: "0", +// }, +// ParentBlockIdentifier: &types.BlockIdentifier{ +// Index: 0, +// Hash: "0", +// }, +// } +// +// addr1 := &types.AccountIdentifier{ +// Address: "addr1", +// } +// addr2 := &types.AccountIdentifier{ +// Address: "addr2", +// } +// curr := &types.Currency{ +// Symbol: "ETH", +// Decimals: 18, +// } +// +// // Block 1 with transaction +// b1 := &types.Block{ +// BlockIdentifier: &types.BlockIdentifier{ +// Index: 1, +// Hash: "1", +// }, +// ParentBlockIdentifier: &types.BlockIdentifier{ +// Index: 0, +// Hash: "0", +// }, +// Transactions: []*types.Transaction{ +// { +// TransactionIdentifier: &types.TransactionIdentifier{ +// Hash: "1_0", +// }, +// Operations: []*types.Operation{ +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 0, +// }, +// Account: addr1, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "100", +// Currency: curr, +// }, +// }, +// }, +// }, +// }, +// } +// +// // Another Transaction for some acocunt in Block 1 +// b2 := &types.Block{ +// BlockIdentifier: &types.BlockIdentifier{ +// Index: 2, +// Hash: "2", +// }, +// ParentBlockIdentifier: &types.BlockIdentifier{ +// Index: 1, +// Hash: "1", +// }, +// Transactions: []*types.Transaction{ +// { +// TransactionIdentifier: &types.TransactionIdentifier{ +// Hash: "2_0", +// }, +// Operations: []*types.Operation{ +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 0, +// }, +// Account: addr1, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "-50", +// Currency: curr, +// }, +// }, +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 1, +// }, +// Account: addr2, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "50", +// Currency: curr, +// }, +// }, +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 2, +// }, +// Account: addr1, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "-1", +// Currency: curr, +// }, +// }, +// }, +// }, +// }, +// } +// +// // Orphaned block with slightly different tx +// b2a := &types.Block{ +// BlockIdentifier: &types.BlockIdentifier{ +// Index: 2, +// Hash: "2a", +// }, +// ParentBlockIdentifier: &types.BlockIdentifier{ +// Index: 1, +// Hash: "1", +// }, +// Transactions: []*types.Transaction{ +// { +// TransactionIdentifier: &types.TransactionIdentifier{ +// Hash: "2_0", +// }, +// Operations: []*types.Operation{ +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 0, +// }, +// Account: addr1, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "-100", +// Currency: curr, +// }, +// }, +// { +// OperationIdentifier: &types.OperationIdentifier{ +// Index: 1, +// }, +// Account: addr2, +// Status: types.String("Success"), +// Type: "Transfer", +// Amount: &types.Amount{ +// Value: "100", +// Currency: curr, +// }, +// }, +// }, +// }, +// }, +// } +// +// t.Run("add genesis block", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// _, err = storage.AddingBlock(gctx, g, b0, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b0.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// }) +// +// t.Run("add block 1", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// mockHelper.On( +// "AccountBalance", +// gctx, +// addr1, +// curr, +// b0.BlockIdentifier, +// ).Return( +// &types.Amount{Value: "1", Currency: curr}, +// nil, +// ).Once() +// mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() +// _, err = storage.AddingBlock(gctx, g, b1, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// }) +// +// t.Run("add block 2", func(t *testing.T) { +// // Reconcile a previously seen account +// err := storage.Reconciled(ctx, addr1, curr, b1.BlockIdentifier) +// assert.NoError(t, err) +// +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// mockHelper.On( +// "AccountBalance", +// gctx, +// addr2, +// curr, +// b1.BlockIdentifier, +// ).Return( +// &types.Amount{Value: "0", Currency: curr}, +// nil, +// ).Once() +// mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() +// mockHandler.On("AccountsReconciled", gctx, dbTx, 1).Return(nil).Once() +// _, err = storage.AddingBlock(gctx, g, b2, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "50", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "50", +// Currency: curr, +// }, amount) +// }) +// +// t.Run("orphan block 2", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// commitWorker, err := storage.RemovingBlock(gctx, g, b2, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// mockHandler.On("BlockRemoved", ctx, b2, mock.Anything).Return(nil).Once() +// mockHandler.On("AccountsSeen", ctx, mock.Anything, -1).Return(nil).Once() +// assert.NoError(t, commitWorker(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// }) +// +// t.Run("orphan block 1", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// commitWorker, err := storage.RemovingBlock(gctx, g, b1, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// mockHandler.On("BlockRemoved", ctx, b1, mock.Anything).Return(nil).Once() +// mockHandler.On("AccountsSeen", ctx, mock.Anything, -1).Return(nil).Once() +// mockHandler.On("AccountsReconciled", ctx, mock.Anything, -1).Return(nil).Once() +// assert.NoError(t, commitWorker(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b0.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// }) +// +// t.Run("add block 1", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// mockHelper.On( +// "AccountBalance", +// gctx, +// addr1, +// curr, +// b0.BlockIdentifier, +// ).Return( +// &types.Amount{Value: "1", Currency: curr}, +// nil, +// ).Once() +// mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() +// _, err = storage.AddingBlock(gctx, g, b1, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) +// assert.True(t, errors.Is(err, storageErrs.ErrAccountMissing)) +// assert.Nil(t, amount) +// }) +// +// t.Run("add block 2a", func(t *testing.T) { +// dbTx := database.Transaction(ctx) +// g, gctx := errgroup.WithContext(ctx) +// mockHelper.On( +// "AccountBalance", +// gctx, +// addr2, +// curr, +// b1.BlockIdentifier, +// ).Return( +// &types.Amount{Value: "0", Currency: curr}, +// nil, +// ).Once() +// mockHandler.On("AccountsSeen", gctx, dbTx, 1).Return(nil).Once() +// _, err = storage.AddingBlock(gctx, g, b2a, dbTx) +// assert.NoError(t, err) +// assert.NoError(t, g.Wait()) +// assert.NoError(t, dbTx.Commit(ctx)) +// +// amount, err := storage.GetBalance(ctx, addr1, curr, b0.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "101", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b1.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "0", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr1, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "1", +// Currency: curr, +// }, amount) +// amount, err = storage.GetBalance(ctx, addr2, curr, b2.BlockIdentifier.Index) +// assert.NoError(t, err) +// assert.Equal(t, &types.Amount{ +// Value: "100", +// Currency: curr, +// }, amount) +// }) +// +// mockHelper.AssertExpectations(t) +// mockHandler.AssertExpectations(t) +//} diff --git a/types/amount_with_sequence.go b/types/amount_with_sequence.go new file mode 100644 index 000000000..b5651d7d2 --- /dev/null +++ b/types/amount_with_sequence.go @@ -0,0 +1,24 @@ +// Copyright 2022 Coinbase, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Generated by: OpenAPI Generator (https://openapi-generator.tech) + +package types + + +type BalanceSeq struct { + Amount *Amount + SeqNumSupport *SequenceNumSupport + Seq int32 +} diff --git a/types/sequence_number.go b/types/sequence_number.go new file mode 100644 index 000000000..5d3be8cfe --- /dev/null +++ b/types/sequence_number.go @@ -0,0 +1,20 @@ +// Copyright 2022 Coinbase, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type SequenceNumSupport struct { + SupportSeq bool + SeqOpType string +}