diff --git a/src/node/main.go b/src/node/main.go index 29dd328a..a11ddae1 100644 --- a/src/node/main.go +++ b/src/node/main.go @@ -23,6 +23,7 @@ const ( validationIntervalInSeconds = 60 verificationsCountPerValidation = 6 defaultPort = 8106 + minimalTransactionFee = 1000 ) func main() { @@ -57,8 +58,8 @@ func main() { now := watch.Now() initialTimestamp := now.Truncate(validationTimer).Add(validationTimer).UnixNano() genesisTransaction := validation.NewRewardTransaction(wallet.Address(), initialTimestamp, settings.GenesisAmount) - blockchain := verification.NewBlockchain(genesisTransaction, registry, validationTimer, synchronizer, logger) - pool := validation.NewTransactionsPool(blockchain, registry, wallet.Address(), validationTimer, watch, logger) + blockchain := verification.NewBlockchain(genesisTransaction, minimalTransactionFee, registry, validationTimer, synchronizer, logger) + pool := validation.NewTransactionsPool(blockchain, minimalTransactionFee, registry, wallet.Address(), validationTimer, watch, logger) validationEngine := tick.NewEngine(pool.Validate, watch, validationTimer, 1, 0) verificationEngine := tick.NewEngine(blockchain.Update, watch, validationTimer, verificationsCountPerValidation, 1) serverFactory := gp2p.NewServerFactory() diff --git a/src/node/protocol/validation/transaction.go b/src/node/protocol/validation/transaction.go index 8c63cfa7..0cb5a2de 100644 --- a/src/node/protocol/validation/transaction.go +++ b/src/node/protocol/validation/transaction.go @@ -8,10 +8,7 @@ import ( "github.com/my-cloud/ruthenium/src/node/network" ) -const ( - transactionFee = 1000 - rewardSenderAddress = "REWARD SENDER ADDRESS" -) +const rewardSenderAddress = "REWARD SENDER ADDRESS" type Transaction struct { recipientAddress string @@ -29,7 +26,7 @@ func NewRewardTransaction(recipientAddress string, timestamp int64, value uint64 SenderAddress: rewardSenderAddress, Timestamp: timestamp, Value: value, - Fee: transactionFee, + Fee: 0, } } diff --git a/src/node/protocol/validation/transactions_pool.go b/src/node/protocol/validation/transactions_pool.go index 03a874d9..16afd6ed 100644 --- a/src/node/protocol/validation/transactions_pool.go +++ b/src/node/protocol/validation/transactions_pool.go @@ -17,9 +17,10 @@ type TransactionsPool struct { transactionResponses []*network.TransactionResponse mutex sync.RWMutex - blockchain protocol.Blockchain - registry protocol.Registry - validatorAddress string + blockchain protocol.Blockchain + minimalTransactionFee uint64 + registry protocol.Registry + validatorAddress string validationTimer time.Duration watch clock.Watch @@ -27,9 +28,10 @@ type TransactionsPool struct { logger log.Logger } -func NewTransactionsPool(blockchain protocol.Blockchain, registry protocol.Registry, validatorAddress string, validationTimer time.Duration, watch clock.Watch, logger log.Logger) *TransactionsPool { +func NewTransactionsPool(blockchain protocol.Blockchain, minimalTransactionFee uint64, registry protocol.Registry, validatorAddress string, validationTimer time.Duration, watch clock.Watch, logger log.Logger) *TransactionsPool { pool := new(TransactionsPool) pool.blockchain = blockchain + pool.minimalTransactionFee = minimalTransactionFee pool.registry = registry pool.validatorAddress = validatorAddress pool.validationTimer = validationTimer @@ -153,11 +155,14 @@ func (pool *TransactionsPool) Validate(timestamp int64) { pool.logger.Debug(fmt.Sprintf("reward: %d", reward)) } -func (pool *TransactionsPool) addTransaction(transactionRequest *network.TransactionRequest) (err error) { +func (pool *TransactionsPool) addTransaction(transactionRequest *network.TransactionRequest) error { + fee := *transactionRequest.Fee + if fee < pool.minimalTransactionFee { + return fmt.Errorf("the transaction fee is too low, fee: %d, minimal fee: %d", fee, pool.minimalTransactionFee) + } transaction, err := NewTransactionFromRequest(transactionRequest) if err != nil { - err = fmt.Errorf("failed to instantiate transaction: %w", err) - return + return fmt.Errorf("failed to instantiate transaction: %w", err) } currentBlockchain := pool.blockchain.Copy() blocks := currentBlockchain.Blocks() @@ -165,30 +170,25 @@ func (pool *TransactionsPool) addTransaction(transactionRequest *network.Transac timestamp := transaction.Timestamp() nextBlockTimestamp := blocks[len(blocks)-1].Timestamp + 2*pool.validationTimer.Nanoseconds() if nextBlockTimestamp < timestamp { - err = fmt.Errorf("the transaction timestamp is too far in the future: %v, now: %v", time.Unix(0, timestamp), time.Unix(0, nextBlockTimestamp)) - return + return fmt.Errorf("the transaction timestamp is too far in the future: %v, now: %v", time.Unix(0, timestamp), time.Unix(0, nextBlockTimestamp)) } currentBlockTimestamp := blocks[len(blocks)-1].Timestamp if timestamp < currentBlockTimestamp { - err = fmt.Errorf("the transaction timestamp is too old: %v, current block timestamp: %v", time.Unix(0, timestamp), time.Unix(0, currentBlockTimestamp)) - return + return fmt.Errorf("the transaction timestamp is too old: %v, current block timestamp: %v", time.Unix(0, timestamp), time.Unix(0, currentBlockTimestamp)) } for _, validatedTransaction := range blocks[len(blocks)-1].Transactions { if transaction.Equals(validatedTransaction) { - err = errors.New("the transaction is already in the blockchain") - return + return errors.New("the transaction is already in the blockchain") } } } for _, pendingTransaction := range pool.transactionResponses { if transaction.Equals(pendingTransaction) { - err = errors.New("the transaction is already in the transactions pool") - return + return errors.New("the transaction is already in the transactions pool") } } if err = transaction.VerifySignature(); err != nil { - err = errors.New("failed to verify transaction") - return + return errors.New("failed to verify transaction") } var senderWalletAmount uint64 if len(blocks) > 0 { @@ -196,14 +196,13 @@ func (pool *TransactionsPool) addTransaction(transactionRequest *network.Transac } insufficientBalance := senderWalletAmount < transaction.Value()+transaction.Fee() if insufficientBalance { - err = errors.New("not enough balance in the sender wallet") - return + return errors.New("not enough balance in the sender wallet") } pool.mutex.Lock() defer pool.mutex.Unlock() pool.transactions = append(pool.transactions, transaction) pool.transactionResponses = append(pool.transactionResponses, transaction.GetResponse()) - return + return nil } func (pool *TransactionsPool) clear() { diff --git a/src/node/protocol/verification/blockchain.go b/src/node/protocol/verification/blockchain.go index 895b4eda..838c8d5f 100644 --- a/src/node/protocol/verification/blockchain.go +++ b/src/node/protocol/verification/blockchain.go @@ -19,25 +19,27 @@ const ( ) type Blockchain struct { - blocks []*Block - blockResponses []*network.BlockResponse - lambda float64 - mutex sync.RWMutex - registry protocol.Registry - synchronizer network.Synchronizer - validationTimer time.Duration - logger log.Logger + blocks []*Block + blockResponses []*network.BlockResponse + lambda float64 + minimalTransactionFee uint64 + mutex sync.RWMutex + registry protocol.Registry + synchronizer network.Synchronizer + validationTimer time.Duration + logger log.Logger } -func NewBlockchain(genesisTransaction *network.TransactionResponse, registry protocol.Registry, validationTimer time.Duration, synchronizer network.Synchronizer, logger log.Logger) *Blockchain { - blockchain := newBlockchain(nil, registry, validationTimer, synchronizer, logger) +func NewBlockchain(genesisTransaction *network.TransactionResponse, minimalTransactionFee uint64, registry protocol.Registry, validationTimer time.Duration, synchronizer network.Synchronizer, logger log.Logger) *Blockchain { + blockchain := newBlockchain(nil, minimalTransactionFee, registry, validationTimer, synchronizer, logger) blockchain.addGenesisBlock(genesisTransaction) return blockchain } -func newBlockchain(blockResponses []*network.BlockResponse, registry protocol.Registry, validationTimer time.Duration, synchronizer network.Synchronizer, logger log.Logger) *Blockchain { +func newBlockchain(blockResponses []*network.BlockResponse, minimalTransactionFee uint64, registry protocol.Registry, validationTimer time.Duration, synchronizer network.Synchronizer, logger log.Logger) *Blockchain { blockchain := new(Blockchain) blockchain.blockResponses = blockResponses + blockchain.minimalTransactionFee = minimalTransactionFee blockchain.registry = registry blockchain.validationTimer = validationTimer blockchain.synchronizer = synchronizer @@ -399,6 +401,7 @@ func (blockchain *Blockchain) verify(neighborBlocks []*network.BlockResponse, ho validBlocks = append(validBlocks, previousBlock) neighborBlockchain := newBlockchain( append(oldHostBlocks, neighborBlocks...), + blockchain.minimalTransactionFee, blockchain.registry, blockchain.validationTimer, blockchain.synchronizer, @@ -469,6 +472,9 @@ func (blockchain *Blockchain) verify(neighborBlocks []*network.BlockResponse, ho return nil, fmt.Errorf("neighbor transaction is invalid: %w", err) } fee := transaction.Fee() + if fee < blockchain.minimalTransactionFee { + return nil, fmt.Errorf("a neighbor block transaction fee is too low, fee: %d, minimal fee: %d", fee, blockchain.minimalTransactionFee) + } totalTransactionsValueBySenderAddress[transaction.SenderAddress()] += transaction.Value() + fee totalTransactionsFees += fee if currentBlockTimestamp+blockchain.validationTimer.Nanoseconds() < transaction.Timestamp() { diff --git a/src/ui/main.go b/src/ui/main.go index 67629095..bd412e2a 100644 --- a/src/ui/main.go +++ b/src/ui/main.go @@ -23,6 +23,7 @@ import ( const ( defaultPort = 8080 defaultHostPort = 8106 + transactionFee = 1000 ) func main() { @@ -52,7 +53,7 @@ func main() { particlesCount := settings.ParticlesCount http.Handle("/", index.NewHandler(*templatesPath, logger)) http.Handle("/wallet", wallet.NewHandler(*mnemonic, *derivationPath, *password, *privateKey, logger)) - http.Handle("/transaction", transaction.NewHandler(host, particlesCount, logger)) + http.Handle("/transaction", transaction.NewHandler(host, particlesCount, transactionFee, logger)) http.Handle("/transactions", transactions.NewHandler(host, logger)) http.Handle("/wallet/amount", amount.NewHandler(host, particlesCount, logger)) http.Handle("/validation/start", start.NewHandler(host, logger)) diff --git a/src/ui/server/transaction.go b/src/ui/server/transaction.go index 2bb1cc81..61f78f27 100644 --- a/src/ui/server/transaction.go +++ b/src/ui/server/transaction.go @@ -7,8 +7,6 @@ import ( "github.com/my-cloud/ruthenium/src/node/network" ) -const transactionFee = 1000 - type Transaction struct { recipientAddress string senderAddress string @@ -19,14 +17,14 @@ type Transaction struct { fee uint64 } -func NewTransaction(recipientAddress string, senderAddress string, senderPublicKey *encryption.PublicKey, timestamp int64, value uint64) *Transaction { +func NewTransaction(recipientAddress string, senderAddress string, senderPublicKey *encryption.PublicKey, timestamp int64, value uint64, fee uint64) *Transaction { return &Transaction{ recipientAddress: recipientAddress, senderAddress: senderAddress, senderPublicKey: senderPublicKey, timestamp: timestamp, value: value, - fee: transactionFee, + fee: fee, } } diff --git a/src/ui/server/transaction/handler.go b/src/ui/server/transaction/handler.go index 9f22df4c..1d6472cf 100644 --- a/src/ui/server/transaction/handler.go +++ b/src/ui/server/transaction/handler.go @@ -17,11 +17,12 @@ import ( type Handler struct { host network.Neighbor particlesInOneAtom uint64 + transactionFee uint64 logger log.Logger } -func NewHandler(host network.Neighbor, particlesInOneAtom uint64, logger log.Logger) *Handler { - return &Handler{host, particlesInOneAtom, logger} +func NewHandler(host network.Neighbor, particlesInOneAtom uint64, transactionFee uint64, logger log.Logger) *Handler { + return &Handler{host, particlesInOneAtom, transactionFee, logger} } func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { @@ -59,7 +60,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) return } senderPublicKey := encryption.NewPublicKey(privateKey) - transaction := server.NewTransaction(*transactionRequest.RecipientAddress, *transactionRequest.SenderAddress, senderPublicKey, time.Now().UnixNano(), value) + transaction := server.NewTransaction(*transactionRequest.RecipientAddress, *transactionRequest.SenderAddress, senderPublicKey, time.Now().UnixNano(), value, handler.transactionFee) err = transaction.Sign(privateKey) if err != nil { handler.logger.Error(fmt.Errorf("failed to generate signature: %w", err).Error()) diff --git a/test/dataset.go b/test/dataset.go index bf940534..a45538cf 100644 --- a/test/dataset.go +++ b/test/dataset.go @@ -5,6 +5,5 @@ const ( Mnemonic2 = "screen wrap color drop lady keep dwarf horror recipe gap ride garage" DerivationPath = "m/44'/60'/0'/0/0" PrivateKey = "0x48913790c2bebc48417491f96a7e07ec94c76ccd0fe1562dc1749479d9715afd" - PublicKey = "0x046bd857ce80ff5238d6561f3a775802453c570b6ea2cbf93a35a8a6542b2edbe5f625f9e3fbd2a5df62adebc27391332a265fb94340fb11b69cf569605a5df782" Address = "0x9C69443c3Ec0D660e257934ffc1754EB9aD039CB" ) diff --git a/test/node/protocol/validation/transactions_pool_test.go b/test/node/protocol/validation/transactions_pool_test.go index 298c624f..46d36eb5 100644 --- a/test/node/protocol/validation/transactions_pool_test.go +++ b/test/node/protocol/validation/transactions_pool_test.go @@ -16,6 +16,39 @@ import ( "time" ) +func Test_AddTransaction_TransactionFeeIsTooLow_TransactionNotAdded(t *testing.T) { + // Arrange + validatorWallet, _ := encryption.DecodeWallet(test.Mnemonic1, test.DerivationPath, "", "") + validatorWalletAddress := validatorWallet.Address() + registryMock := new(protocoltest.RegistryMock) + registryMock.IsRegisteredFunc = func(string) (bool, error) { return true, nil } + watchMock := new(clocktest.WatchMock) + var now int64 = 2 + watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } + validationTimer := time.Nanosecond + logger := logtest.NewLoggerMock() + var invalidTransactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now+2, 1, invalidTransactionFee) + _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) + invalidTransactionRequest := invalidTransaction.GetRequest() + var blockResponses []*network.BlockResponse + blockResponses = append(blockResponses, protocoltest.NewGenesisBlockResponse(validatorWalletAddress)) + blockchainMock := new(protocoltest.BlockchainMock) + blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } + blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } + var minimalTransactionFee uint64 = 1 + pool := validation.NewTransactionsPool(blockchainMock, minimalTransactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + + // Act + pool.AddTransaction(&invalidTransactionRequest, nil) + + // Assert + transactions := pool.Transactions() + expectedTransactionsLength := 0 + actualTransactionsLength := len(transactions) + test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) +} + func Test_AddTransaction_TransactionTimestampIsInTheFuture_TransactionNotAdded(t *testing.T) { // Arrange validatorWallet, _ := encryption.DecodeWallet(test.Mnemonic1, test.DerivationPath, "", "") @@ -27,7 +60,8 @@ func Test_AddTransaction_TransactionTimestampIsInTheFuture_TransactionNotAdded(t watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } validationTimer := time.Nanosecond logger := logtest.NewLoggerMock() - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now+2, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now+2, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() var blockResponses []*network.BlockResponse @@ -36,7 +70,7 @@ func Test_AddTransaction_TransactionTimestampIsInTheFuture_TransactionNotAdded(t blockchainMock := new(protocoltest.BlockchainMock) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) // Act pool.AddTransaction(&invalidTransactionRequest, nil) @@ -59,7 +93,8 @@ func Test_AddTransaction_TransactionTimestampIsOlderThan1Blocks_TransactionNotAd watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } validationTimer := time.Nanosecond logger := logtest.NewLoggerMock() - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now-2, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now-2, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() var blockResponses []*network.BlockResponse @@ -68,7 +103,7 @@ func Test_AddTransaction_TransactionTimestampIsOlderThan1Blocks_TransactionNotAd blockchainMock := new(protocoltest.BlockchainMock) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) // Act pool.AddTransaction(&invalidTransactionRequest, nil) @@ -91,7 +126,8 @@ func Test_AddTransaction_TransactionIsAlreadyInTheBlockchain_TransactionNotAdded watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } validationTimer := time.Nanosecond logger := logtest.NewLoggerMock() - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() transaction, _ := validation.NewTransactionFromRequest(&invalidTransactionRequest) @@ -103,7 +139,7 @@ func Test_AddTransaction_TransactionIsAlreadyInTheBlockchain_TransactionNotAdded blockchainMock := new(protocoltest.BlockchainMock) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) // Act pool.AddTransaction(&invalidTransactionRequest, nil) @@ -133,12 +169,12 @@ func Test_AddTransaction_InvalidSignature_TransactionNotAdded(t *testing.T) { blockchainMock := new(protocoltest.BlockchainMock) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) - var amount uint64 = 1 - transaction := server.NewTransaction(walletAAddress, validatorWalletAddress, validatorWallet.PublicKey(), now, amount) + var transactionFee uint64 = 0 + transaction := server.NewTransaction(walletAAddress, validatorWalletAddress, validatorWallet.PublicKey(), now, amount, transactionFee) _ = transaction.Sign(walletA.PrivateKey()) transactionRequest := transaction.GetRequest() + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) // Act pool.AddTransaction(&transactionRequest, nil) @@ -168,13 +204,13 @@ func Test_AddTransaction_ValidTransaction_TransactionAdded(t *testing.T) { blockchainMock := new(protocoltest.BlockchainMock) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) - var amount uint64 = 1 - transaction := server.NewTransaction(walletAAddress, validatorWalletAddress, validatorWallet.PublicKey(), now, amount) + var transactionFee uint64 = 0 + transaction := server.NewTransaction(walletAAddress, validatorWalletAddress, validatorWallet.PublicKey(), now, amount, transactionFee) _ = transaction.Sign(validatorWallet.PrivateKey()) transactionRequest := transaction.GetRequest() blockchainMock.CalculateTotalAmountFunc = func(int64, string) uint64 { return *transactionRequest.Value + *transactionRequest.Fee } + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) // Act pool.AddTransaction(&transactionRequest, nil) @@ -203,12 +239,13 @@ func Test_Validate_TransactionTimestampIsInTheFuture_TransactionNotValidated(t * var blockResponses []*network.BlockResponse blockResponses = append(blockResponses, protocoltest.NewGenesisBlockResponse(validatorWalletAddress)) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now+2, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now+2, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() genesisAmount := *invalidTransactionRequest.Value + *invalidTransactionRequest.Fee blockchainMock.CalculateTotalAmountFunc = func(int64, string) uint64 { return genesisAmount } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) pool.AddTransaction(&invalidTransactionRequest, nil) // Act @@ -235,12 +272,13 @@ func Test_Validate_TransactionTimestampIsOlderThan2Blocks_TransactionNotValidate var blockResponses []*network.BlockResponse blockResponses = append(blockResponses, protocoltest.NewGenesisBlockResponse(validatorWalletAddress)) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now-3, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now-3, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() genesisAmount := *invalidTransactionRequest.Value + *invalidTransactionRequest.Fee blockchainMock.CalculateTotalAmountFunc = func(int64, string) uint64 { return genesisAmount } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) pool.AddTransaction(&invalidTransactionRequest, nil) blockResponses = append(blockResponses, protocoltest.NewEmptyBlockResponse(now-2)) blockResponses = append(blockResponses, protocoltest.NewEmptyBlockResponse(now-1)) @@ -270,12 +308,13 @@ func Test_Validate_TransactionIsAlreadyInTheBlockchain_TransactionNotValidated(t var blockResponses []*network.BlockResponse blockResponses = append(blockResponses, protocoltest.NewGenesisBlockResponse(validatorWalletAddress)) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } - invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1) + var transactionFee uint64 = 0 + invalidTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1, transactionFee) _ = invalidTransaction.Sign(validatorWallet.PrivateKey()) invalidTransactionRequest := invalidTransaction.GetRequest() genesisAmount := *invalidTransactionRequest.Value + *invalidTransactionRequest.Fee blockchainMock.CalculateTotalAmountFunc = func(int64, string) uint64 { return genesisAmount } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) pool.AddTransaction(&invalidTransactionRequest, nil) transaction, _ := validation.NewTransactionFromRequest(&invalidTransactionRequest) var transactionResponses []*network.TransactionResponse @@ -307,12 +346,13 @@ func Test_Validate_ValidTransaction_TransactionValidated(t *testing.T) { var blockResponses []*network.BlockResponse blockResponses = append(blockResponses, protocoltest.NewGenesisBlockResponse(validatorWalletAddress)) blockchainMock.BlocksFunc = func() []*network.BlockResponse { return blockResponses } - validTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1) + var transactionFee uint64 = 0 + validTransaction := server.NewTransaction("A", validatorWalletAddress, validatorWallet.PublicKey(), now, 1, transactionFee) _ = validTransaction.Sign(validatorWallet.PrivateKey()) validTransactionRequest := validTransaction.GetRequest() genesisAmount := *validTransactionRequest.Value + *validTransactionRequest.Fee blockchainMock.CalculateTotalAmountFunc = func(int64, string) uint64 { return genesisAmount } - pool := validation.NewTransactionsPool(blockchainMock, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) + pool := validation.NewTransactionsPool(blockchainMock, transactionFee, registryMock, validatorWalletAddress, validationTimer, watchMock, logger) pool.AddTransaction(&validTransactionRequest, nil) // Act diff --git a/test/node/protocol/verification/blockchain_test.go b/test/node/protocol/verification/blockchain_test.go index aad35647..4d825329 100644 --- a/test/node/protocol/verification/blockchain_test.go +++ b/test/node/protocol/verification/blockchain_test.go @@ -28,7 +28,7 @@ func Test_AddBlock_ValidParameters_NoErrorLogged(t *testing.T) { logger := logtest.NewLoggerMock() synchronizer := new(networktest.SynchronizerMock) genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) // Act blockchain.AddBlock(0, nil, nil) @@ -43,7 +43,7 @@ func Test_Blocks_ValidParameters_NoErrorLogged(t *testing.T) { logger := logtest.NewLoggerMock() synchronizer := new(networktest.SynchronizerMock) genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) // Act blocks := blockchain.Blocks() @@ -58,7 +58,7 @@ func Test_CalculateTotalAmount_InitialValidator_ReturnsGenesisAmount(t *testing. logger := logtest.NewLoggerMock() synchronizer := new(networktest.SynchronizerMock) genesisTransaction := validation.NewRewardTransaction("", 0, 10) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) // Act amount := blockchain.CalculateTotalAmount(1, genesisTransaction.RecipientAddress) @@ -90,7 +90,7 @@ func Test_Update_NeighborBlockchainIsBetter_IsReplaced(t *testing.T) { return []network.Neighbor{neighborMock} } genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) // Act blockchain.Update(watchMock.Now().UnixNano()) @@ -121,7 +121,7 @@ func Test_Update_NeighborNewBlockTimestampIsInvalid_IsNotReplaced(t *testing.T) return []network.Neighbor{neighborMock} } genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) type args struct { firstBlockTimestamp int64 @@ -214,7 +214,7 @@ func Test_Update_NeighborNewBlockTimestampIsInTheFuture_IsNotReplaced(t *testing return []network.Neighbor{neighborMock} } genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, 0, registry, 1, synchronizer, logger) // Act blockchain.Update(watchMock.Now().UnixNano()) @@ -234,6 +234,65 @@ func Test_Update_NeighborNewBlockTimestampIsInTheFuture_IsNotReplaced(t *testing test.Assert(t, isExplicitMessageLogged, "no explicit message is logged whereas it should be") } +func Test_Update_NeighborNewBlockTransactionFeeIsTooLow_IsNotReplaced(t *testing.T) { + // Arrange + registry := new(protocoltest.RegistryMock) + registry.IsRegisteredFunc = func(address string) (bool, error) { return true, nil } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 1) } + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + wallet, _ := encryption.DecodeWallet(test.Mnemonic1, test.DerivationPath, "", "") + address := wallet.Address() + var invalidTransactionFee uint64 = 0 + serverTransaction := server.NewTransaction("A", address, wallet.PublicKey(), 3, 1, invalidTransactionFee) + _ = serverTransaction.Sign(wallet.PrivateKey()) + transactionRequest := serverTransaction.GetRequest() + transaction, _ := validation.NewTransactionFromRequest(&transactionRequest) + transactionResponse := transaction.GetResponse() + neighborMock.GetBlocksFunc = func() ([]*network.BlockResponse, error) { + blockResponse1 := protocoltest.NewGenesisBlockResponse(address) + block1, _ := verification.NewBlockFromResponse(blockResponse1) + hash, _ := block1.Hash() + var block2Timestamp int64 = 1 + transactions := []*network.TransactionResponse{ + transactionResponse, + validation.NewRewardTransaction(address, block2Timestamp, 0), + } + var registeredAddresses []string + registeredAddresses = append(registeredAddresses, address) + blockResponse2 := verification.NewBlockResponse(block2Timestamp, hash, transactions, registeredAddresses) + return []*network.BlockResponse{blockResponse1, blockResponse2}, nil + } + neighborMock.TargetFunc = func() string { + return "neighbor" + } + synchronizer := new(networktest.SynchronizerMock) + synchronizer.NeighborsFunc = func() []network.Neighbor { + return []network.Neighbor{neighborMock} + } + genesisTransaction := validation.NewRewardTransaction("", 0, 0) + var minimalTransactionFee uint64 = 1 + blockchain := verification.NewBlockchain(genesisTransaction, minimalTransactionFee, registry, 1, synchronizer, logger) + + // Act + blockchain.Update(watchMock.Now().UnixNano()) + + // Assert + var isKept bool + var isExplicitMessageLogged bool + for _, call := range logger.DebugCalls() { + expectedMessage := fmt.Sprintf("a neighbor block transaction fee is too low, fee: %d, minimal fee: %d", invalidTransactionFee, minimalTransactionFee) + if call.Msg == blockchainKeptMessage { + isKept = true + } else if strings.Contains(call.Msg, expectedMessage) { + isExplicitMessageLogged = true + } + } + test.Assert(t, isKept, "blockchain is replaced whereas it should be kept") + test.Assert(t, isExplicitMessageLogged, "no explicit message is logged whereas it should be") +} + func Test_Update_NeighborNewBlockTransactionTimestampIsTooFarInTheFuture_IsNotReplaced(t *testing.T) { // Arrange registry := new(protocoltest.RegistryMock) @@ -244,7 +303,8 @@ func Test_Update_NeighborNewBlockTransactionTimestampIsTooFarInTheFuture_IsNotRe neighborMock := new(networktest.NeighborMock) wallet, _ := encryption.DecodeWallet(test.Mnemonic1, test.DerivationPath, "", "") address := wallet.Address() - serverTransaction := server.NewTransaction("A", address, wallet.PublicKey(), 3, 1) + var transactionFee uint64 = 0 + serverTransaction := server.NewTransaction("A", address, wallet.PublicKey(), 3, 1, transactionFee) _ = serverTransaction.Sign(wallet.PrivateKey()) transactionRequest := serverTransaction.GetRequest() transaction, _ := validation.NewTransactionFromRequest(&transactionRequest) @@ -271,7 +331,7 @@ func Test_Update_NeighborNewBlockTransactionTimestampIsTooFarInTheFuture_IsNotRe return []network.Neighbor{neighborMock} } genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, transactionFee, registry, 1, synchronizer, logger) // Act blockchain.Update(watchMock.Now().UnixNano()) @@ -301,7 +361,8 @@ func Test_Update_NeighborNewBlockTransactionTimestampIsTooOld_IsNotReplaced(t *t neighborMock := new(networktest.NeighborMock) wallet, _ := encryption.DecodeWallet(test.Mnemonic1, test.DerivationPath, "", "") address := wallet.Address() - serverTransaction := server.NewTransaction("A", address, wallet.PublicKey(), 0, 1) + var transactionFee uint64 = 0 + serverTransaction := server.NewTransaction("A", address, wallet.PublicKey(), 0, 1, transactionFee) _ = serverTransaction.Sign(wallet.PrivateKey()) transactionRequest := serverTransaction.GetRequest() transaction, _ := validation.NewTransactionFromRequest(&transactionRequest) @@ -328,7 +389,7 @@ func Test_Update_NeighborNewBlockTransactionTimestampIsTooOld_IsNotReplaced(t *t return []network.Neighbor{neighborMock} } genesisTransaction := validation.NewRewardTransaction("", 0, 0) - blockchain := verification.NewBlockchain(genesisTransaction, registry, 1, synchronizer, logger) + blockchain := verification.NewBlockchain(genesisTransaction, transactionFee, registry, 1, synchronizer, logger) // Act blockchain.Update(watchMock.Now().UnixNano()) diff --git a/test/ui/server/transaction/handler_test.go b/test/ui/server/transaction/handler_test.go index 9892bb93..2b127715 100644 --- a/test/ui/server/transaction/handler_test.go +++ b/test/ui/server/transaction/handler_test.go @@ -21,7 +21,7 @@ func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) recorder := httptest.NewRecorder() invalidHttpMethods := []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} for _, method := range invalidHttpMethods { @@ -44,7 +44,7 @@ func Test_ServeHTTP_UndecipherableTransaction_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := "" b, _ := json.Marshal(transactionRequest) body := bytes.NewReader(b) @@ -65,7 +65,7 @@ func Test_ServeHTTP_InvalidTransaction_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest("", "", "", "") b, _ := json.Marshal(transactionRequest) body := bytes.NewReader(b) @@ -86,7 +86,7 @@ func Test_ServeHTTP_InvalidPrivateKey_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( "InvalidPrivateKey", test.Address, @@ -112,7 +112,7 @@ func Test_ServeHTTP_InvalidTransactionValue_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( test.PrivateKey, test.Address, @@ -138,7 +138,7 @@ func Test_ServeHTTP_TransactionValueIsTooBig_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( test.PrivateKey, test.Address, @@ -164,7 +164,7 @@ func Test_ServeHTTP_TransactionValueIsTooSmall_BadRequest(t *testing.T) { // Arrange neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( test.PrivateKey, test.Address, @@ -191,7 +191,7 @@ func Test_ServeHTTP_NodeError_InternalServerError(t *testing.T) { neighborMock := new(networktest.NeighborMock) neighborMock.AddTransactionFunc = func(network.TransactionRequest) error { return errors.New("") } logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( test.PrivateKey, test.Address, @@ -218,7 +218,7 @@ func Test_ServeHTTP_ValidTransaction_NeighborMethodCalled(t *testing.T) { neighborMock := new(networktest.NeighborMock) neighborMock.AddTransactionFunc = func(network.TransactionRequest) error { return nil } logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, 1, logger) + handler := transaction.NewHandler(neighborMock, 1, 0, logger) transactionRequest := newTransactionRequest( test.PrivateKey, test.Address,