Browse Source

Multioperation to JSON-RPC and Blockchain

Added code to implement MultioOperation (PIP-0017) at the Blockchain and
at JSON-RPC calls
PascalCoin 7 years ago
parent
commit
29fdc1a03e

+ 93 - 1
README.md

@@ -1,6 +1,6 @@
 # Pascal Coin: P2P Cryptocurrency without need of historical operations.  
 # Pascal Coin: P2P Cryptocurrency without need of historical operations.  
   
   
-Copyright (c) 2016 Albert Molina  
+Copyright (c) 2016-2018 PascalCoin developers based on original Albert Molina source code
   
   
 THIS IS EXPERIMENTAL SOFTWARE. Use it for educational purposes only.  
 THIS IS EXPERIMENTAL SOFTWARE. Use it for educational purposes only.  
   
   
@@ -34,6 +34,98 @@ Also, consider a donation at PascalCoin development account: "0-10"
 
 
 ## History:  
 ## History:  
 
 
+### DEVELOPMENT STATUS
+- TODO: Explore possible modification to sinoidial effect on blocks time (No PIP) 
+- TODO: PIP - 0010
+- TODO: Add new network operations
+  - Get account status
+  - Get penging pool
+- MultiOperation: PIP-0017
+  - Multioperation allows a transactional like operations, they can include transactions and change info operations in a signle multioperation
+    - Allow to send coins from N accounts to M receivers in a transaction mixing, without knowledge of how many coins where sent from "Alice" to "Bob" if properly mixed
+	- Ophash can be previously known by all signers before signing. They must sign only if multioperation includes it's transactions as expected 
+	- OpHash of a multioperation will allow to include n_operation and account of each signer account, but md160hash chunk will be the same for all
+- JSON-RPC changes:
+  - Added param "startblock" to "getaccountoperations" in order to start searching backwards on a specific block. Note: Balance will not be returned on each operation due cannot be calculated. Default value "0" means start searching on current block as usual
+  - Operation Object changes:
+    - "balance" will not be included when is not possible to calc previous balance of account searching at the past
+    - Fields not included when in Multioperation:
+      - "account" will not be included in Multioperations
+      - "signer_account" will not be included in Multioperations
+      - "n_operation" will not be included in Multioperations
+      - "amount" will not be included in Multioperations, need search on each field
+      - "payload" will not be included in Multioperations, need search on each field
+    - On Multioperations, will include those new fields:
+      - "totalamount" will be the total amount equal to SUM each "receivers"."amount" field
+      - "senders" : Will return an Array with Objects
+        - "account" : Sending Account 
+        - "n_operation"
+        - "amount" : In negative value, due it's outgoing form "account"
+        - "payload"
+      - "receivers"
+        - "account" : Receoving Account 
+        - "amount" : In positive value, due it's incoming from a sender to "account"
+        - "payload"
+      - "changers" : Will return an Array with Objects
+        - "account" : changing Account 
+        - "n_operation"
+        - "new_enc_pubkey" : If public key is changed
+        - "new_name" : If name is changed
+        - "new_type" : If type is changed
+- Protections against invalid nodes (scammers):
+  - Protection on GetBlocks and GetBlockOperations
+- Merged new GUI with current stable core
+- New folders organization
+
+### Build 2.1.6 - 2018-02-14
+- Important improvements
+  - Improved speed when processing operations on start
+  - Improved speed when processing pending operations after a new block found
+  - Deleted duplicate "SanitizeOperations" call
+  - Verify signed operations only once (TPCOperation.FSignatureChecked Boolean)
+  - Improvements in search methods of TOperationsHashTree
+    - Increase speed in search methods thanks to internal ordered lists
+    - Increase speed copying thanks to using FHashTree sender buffer instad of generating new one
+  - Internal bugs
+- Those improvements solved BUG that caused operations not included to blockchain due slow processing with MemPool 
+- NOTE: It's HIGHLY RECOMMENDED to upgrade to this version
+
+### Build 2.1.5 - 2018-02-09
+- GUI changes:
+  - Allow massive accounts "change info" operation
+  - Added "account type" and "sale price" on accounts grid
+  - Show "account type" stats on search account form  
+  - Changed Icon to current PascalCoin icon
+- Pending operations buffer cached to file to allow daemon/app restart without losing pending operations
+- Less memory usage thanks to a Public keys centralised buffer
+- JSON-RPC changes
+  - Added param "n_operation" to "Operation Object" JSON-RPC call
+  - New method "findnoperation": Search an operation made to an account based on n_operation field
+    - Params:
+	  - "account" : Account
+	  - "n_operation" : n_operation field (n_operation is an incremental value to protect double spend)
+	- Result:
+	  - If success, returns an Operation Object	  
+  - New method "findnoperations": Search an operation made to an account based on n_operation 
+    - Params:
+	  - "account" : Account
+	  - "n_operation_min" : Min n_operation to search
+	  - "n_operation_max" : Max n_operation to search
+	  - "start_block" : (optional) Block number to start search. 0=Search all, including pending operations
+	- Result:
+	  - If success, returns an array of Operation Object
+  - New method "decodeophash": Decodes block/account/n_operation info of a 32 bytes ophash
+    - Params:
+      - "ophash" : HEXASTRING with an ophash (ophash is 32 bytes, so must be 64 hexa valid chars)
+    - Result:
+      - "block" : Integer. Block number. 0=unknown or pending
+      - "account" : Integer. Account number
+      - "n_operation" : Integer. n_operation used by the account. n_operation is an incremental value, cannot be used twice on same account.
+      - "md160hash" : HEXASTRING with MD160 hash
+- Solved bug that caused to delete blockchain when checking memory 
+- Solved bug in Network adjusted time on receiving connections caused by full entry buffer
+- Minor optimizations
+
 ### Build 2.1.3.0 - 2017-11-15
 ### Build 2.1.3.0 - 2017-11-15
 - Fixed BUG when buying account assigning an invalid public key
 - Fixed BUG when buying account assigning an invalid public key
 - Added maxim value to node servers buffer, deleting old node servers not used, this improves speed
 - Added maxim value to node servers buffer, deleting old node servers not used, this improves speed

+ 0 - 429
README.txt

@@ -1,429 +0,0 @@
-# Pascal Coin: P2P Cryptocurrency without need of historical operations.  
-  
-Copyright (c) 2016 Albert Molina  
-  
-THIS IS EXPERIMENTAL SOFTWARE. Use it for educational purposes only.  
-  
-This software is a Node of the Pascal Coin P2P Cryptocurrency.  
-It can be used to Mine and Explore blocks and operations.  
-  
-Distributed under the MIT software license, see the accompanying file  
-LICENSE  or visit http://www.opensource.org/licenses/mit-license.php.  
-
-This product includes software developed by the OpenSSL Project and Denis  
-Grinyuk (https://github.com/Arvur/OpenSSL-Delphi), and some  
-cryptographic functions inspirated in code written by Ladar Levison and   
-Marco Ferrante.  
-Original source code is written in Pascal Language and is available at   
-https://github.com/PascalCoin/PascalCoin  
-  
-  
-## HOW TO COMPILE:  
-  
-See instructions at GitHub Wiki: https://github.com/PascalCoin/PascalCoin/wiki
-  
-  
-Enjoy Pascal Coin!
-  
-## Donations  
-  
-If you like it, consider a donation using BitCoin:
-16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
-
-Also, consider a donation at PascalCoin development account: "0-10"
-
-## History:  
-
-### Future Build 2.1.4 - 2017-12-22
-- Pending operations buffer cached to file to allow daemon/app restart without losing pending operations
-- Less memory usage thanks to a Public keys centralised buffer
-- JSON-RPC changes
-  - Added param "n_operation" to "Operation Object" JSON-RPC call
-  - New method "findnoperation": Search an operation made to an account based on n_operation field
-    - Params:
-	  - "account" : Account
-	  - "n_operation" : n_operation field (n_operation is an incremental value to protect double spend)
-	- Result:
-	  - If success, returns an Operation Object	  
-  - New method "findnoperations": Search an operation made to an account based on n_operation 
-    - Params:
-	  - "account" : Account
-	  - "n_operation_min" : Min n_operation to search
-	  - "n_operation_max" : Max n_operation to search
-	  - "start_block" : (optional) Block number to start search. 0=Search all, including pending operations
-	- Result:
-	  - If success, returns an array of Operation Object
-  - New method "decodeophash": Decodes block/account/n_operation info of a 32 bytes ophash
-    - Params:
-      - "ophash" : HEXASTRING with an ophash (ophash is 32 bytes, so must be 64 hexa valid chars)
-    - Result:
-      - "block" : Integer. Block number. 0=unknown or pending
-      - "account" : Integer. Account number
-      - "n_operation" : Integer. n_operation used by the account. n_operation is an incremental value, cannot be used twice on same account.
-      - "md160hash" : HEXASTRING with MD160 hash
-- Solved bug that caused to delete blockchain when checking memory 
-- Minor optimizations
-
-### Build 2.1.3.0 - 2017-11-15
-- Fixed BUG when buying account assigning an invalid public key
-- Added maxim value to node servers buffer, deleting old node servers not used, this improves speed
-- Re-add orphaned operations back into the pending pool
-- RPC locking to prevent N_Operation race-condition on concurrent invocations
-- Minor bugs
-
-### Build 2.1.2.0 - 2017-07-27
-- No more blockchain in installer = TRUE DELETABLE BLOCKCHAIN (Safebox will be automatically downloaded by client from network)
-- Fixed storage bug when downloading a new safebox
-- Safebox will be downloaded in small chunks (aprox 2mb per chunk)
-- Read safebox file improvements (quick start)
-
-### Build 2.1.1.0 - 2017-07-16
-- Fixed installer bug: In last Windows installer a malformed safebox file was included. This build is only to provide a good safebox file with the installer
-- No important changes in exe/binary file 
-
-### Build 2.1.0.0 - 2017-07-14
-- Fixed bug of slow mass transactions from a single account due to incorrect sending order
-- Fixed memory leak on GetSafeBox request call
-- Added new GUI improvements on Operations form
-
-### Build 2.0.0.0 - 2017-06-23
-- MANDATORY UPGRADE - HARD FORK ACTIVATION WILL OCCUR ON BLOCK 115000
-- Introducing Protocol v2.
-  - https://github.com/PascalCoin/PascalCoin/blob/master/PascalCoinWhitePaperV2.pdf
-  - Core Changes:
-    - New safebox hash calculation algorithm to allow safebox checkpointing
-    - Improved difficulty target calculation to obtain a more stable average blocktime of 5 minutes, given abrupt hashpower fluctuations
-    - Bug-fix for China region users
-    - Anti-spam measures
-      - New Consensus Rule: A block can only contain 1 zero fee operation per sender account. If a sender account issues 2 or more zero-fee operations, that block is invalid.
-    - SafeBox now stored in chunks to facilitate checkpoint distribution
-	- Reintroduce orphan blocks operations on main blockchain
-  - Added Checkpointing:
-    - Checkpoint created on every 100'th block
-    - Added network operations to transfer Checkpoint chunks (compressed)
-  - Addded In-Protocol PASA Exchanging:
-    - New Operation: List Account, used to list an account for public or private sale 
-    - New Operation: Delist Account, used to delist an account from sale 
-    - New Operation: Buy Account, used to purchase an account lised for sale
-    - Updated Operation: Transaction, can be used in place of Buy Account operation in private account sales
-  - RPC API Changes:
-    - All changes are backwards-compatible for current 3rd-party infrastructure providers (exchanges, wallets).
-    - However, use of param "ophash" has been changed in V2, see "Operation Object changes" section to know how to use "old_ophash" param value    
-    - JSON changes:
-      - Account Object changes:
-        - Added param "state"  (values can be "normal" or "listed". When listed then account is for sale)
-        - If "state" is "listed" then will fill next values:
-          - "locked_until_block", "price", "seller_account" 
-          - "private_sale" : Boolean value 
-          - "new_enc_pubkey" : (Only on "private_sale" = true) - HEXASTRING with private sale encoded public key
-        - Added param "name": String
-          - Name available chars: abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-+{}[]\_:"|<>,.?/~
-          - Name first char: (cannot start with number): abcdefghijklmnopqrstuvwxyz!@#$%^&*()-+{}[]\_:"|<>,.?/~
-          - Name lenght: Empty or 3..64 chars length
-        - Added param "type": Integer (Valid values from 0..65535)
-      - Operation Object changes:
-        - Changed param "ophash": Current returned value of "ophash" is not the same than previous builds (changed calculation method)
-        - New param "old_ophash": Will return previous "ophash" value only for old blocks (prior to v2 protocol activation)
-        - New param "subtype" : Based on "optype" param, this param allows to discrimine point of view of operation (sender/receiver/buyer/seller ...)
-        - New param "signer_account" : Will return the account that signed (and payed fee) for this operation
-        - Param "enc_pubkey": Will return both change key and the private sale public key value
-        - About method "findoperation" and "ophash"
-          - Due to changed "ophash" param value calculation, method "findoperation" will find old "ophash" values too
-          - This is to avoid 100% compatibility, but remember that returned "Operation object" will contain "ophash" value calculated in new format only (you can read "old_ophash" value in older blocks)
-      - New methods:
-        - "listaccountforsale" : Lists an account for sale (public or private)
-          - Params:
-            - "account_target" : Account to be listed
-            - "account_signer" : Account that signs and pays the fee (must have same public key that listed account, or be the same)
-            - "price": price account can be purchased for
-            - "seller_account" : Account that will receive "price" amount on sell
-            - "new_b58_pubkey"/"new_enc_pubkey": If used, then will be a private sale
-            - "locked_until_block" : Block number until this account will be locked (a locked account cannot execute operations while locked)
-            - "fee","payload","payload_method","pwd"
-        - "signlistaccountforsale" : Signs a List an account for sale (public or private) for cold wallets
-          - Params: Same "listaccountforsale" params, and also:
-            - "rawoperations" : HEXASTRING with previous signed operations (optional)
-            - "signer_b58_pubkey"/"signer_enc_pubkey" : The current public key of "account_signer"
-            - "last_n_operation" : The current n_operation of signer account        
-        - "delistaccountforsale" : Delist an account for sale
-          - Params:
-            - "account_target" : Account to be delisted
-            - "account_signer" : Account that signs and pays the fee (must have same public key that delisted account, or be the same)
-            - "fee","payload","payload_method","pwd"
-        - "signdelistaccountforsale" : Signs a List an account for sale (public or private) for cold wallets
-		  - Params: Same "delistaccountforsale" params, and also:
-            - "rawoperations" : HEXASTRING with previous signed operations (optional)
-            - "signer_b58_pubkey"/"signer_enc_pubkey" : The current public key of "account_signer"
-            - "last_n_operation" : The current n_operation of signer account                      
-        - "buyaccount" : Buy an account previously listed for sale (public or private)
-          - Params: 
-            - "buyer_account","account_to_purchase",
-            - "price","seller_account",
-            - "new_b58_pubkey"/"new_enc_pubkey","amount",
-            - "fee","payload","payload_method","pwd"
-        - "signbuyaccount" : Signs a buy operation for cold wallets
-          - Params: Same "buyaccount" params, and also:
-            - "rawoperations" : HEXASTRING with previous signed operations (optional)
-            - "signer_b58_pubkey"/"signer_enc_pubkey" : The current public key of "buyer_account"
-            - "last_n_operation" : The current n_operation of buyer account	
-        - "changeaccountinfo" : Changes an account Public key, or name, or type value (at least 1 on 3)
-            - Params:
-            - "account_target" : Account to be delisted
-            - "account_signer" : Account that signs and pays the fee (must have same public key that target account, or be the same)
-            - "new_b58_pubkey"/"new_enc_pubkey": If used, then will change the target account public key
-            - "new_name": If used, then will change the target account name
-            - "new_type": If used, then will change the target account type
-            - "fee","payload","payload_method","pwd"
-        - "signchangeaccountinfo" : Signs a change account info for cold cold wallets
-          - Params: Same "changeaccountinfo" params, and also:
-            - "rawoperations" : HEXASTRING with previous signed operations (optional)
-            - "signer_b58_pubkey"/"signer_enc_pubkey" : The current public key of "account_signer"
-            - "last_n_operation" : The current n_operation of signer account
-        - "findaccounts" : Find accounts by name/type and returns them as an array of Account objects
-          - Returned array will be sorted by account number
-          - Params:
-            - "name" : (String) If has value, will return the account that match name
-            - "type" : (Integer) If has value, will return accounts with same type
-            - "start" : (Integer) Start account (by default, 0)
-            - "max" : (Integer) Max of accounts returned in array (by default, 100)
-      - Updated methods:
-        - "changekey" : Added param "account_signer" that allow to pay fee using another account with same public key than target "account" param. This allows to change keys on empty accounts (balance = 0) and pay fee
-        - "signchangekey" : Added param "account_signer", same use that in "changekey" method
-  - GUI changes:  (not available on daemon)
-    - New columns "name" and "type" on accounts
-    - Information about Accounts/Operation using F1
-    - Operations form now accepts:  (Will be active after V2 protocol activation)
-      - List account for sale (public or private)
-      - Delist account
-      - Buy account
-      - Change account name/type
-    - Operations form allows to search for accounts using F2 over an account edit box
-    - Changes in text showing operations information     
-- Other changes:
-  - Bug for china users fixed  
-  - Bugs fixed
-
-### Build 1.5.6.0 - 2017-05-03
-- Allow multiselect accounts (GUI wallet)
-- Priority for operations with fee:
-  - Allow miner server (pools) to select what to mine using pascalcoin_daemon.ini file (daemon only)
-    - RPC_SERVERMINER_MAX_OPERATIONS_PER_BLOCK: Max of operations that can be included in a block (Default 5000, min value 1000)
-    - RPC_SERVERMINER_MAX_ZERO_FEE_OPERATIONS: Max of operations with 0 fee that can be included in a block (Default 2000, min value 400)
-  - Operations with fee have always preference over 0 fee operations (will be mined first)
-  - If operations with fee fills all the buffer, then no zero fee operation will be included
-- Fixed bug on receiving big blocks and slow net connection to prevent never synchronize
-- Fixed bug on miner server that produces "invalid operations hash" error on valid solutions with 0 operations
-- Fixed bug on file storage
-- Fixed minor bugs
-
-### Build 1.5.5.0 - 2017-04-11
-- Corrected fee result on RPC calls as a negative number on "Change key" operations
-- Corrected PASCURRENCY value to be limited as a 4 decimal digits on RPC calls
-- JSON-RPC method "getaccountoperations" changed: if param "start" is -1, will include pending operations, otherwise not
-- Fixed bug: On "getaccountoperations" if an account had a lot of operations (receive tx included) then sometimes app crashed when executing depth search.
-  - Note: High depth search is slow because it search always starting from current state, going backwards, in order to return past balance. This can be a slow method on some account with high transactions volume.
-
-### Build 1.5.4.0 - 2017-03-14
-
-- Added Network Timestamp Adjustment (NAT) to calc valid timestamps
-  - Minimum 4 active connections to calc median used for NAT, otherwise use local timestamp
-  - Based on IP's (to prevent a malicious IP timejacking, each IP is only used once)
-  - Removed IP's and recalculated after disconnecting (to prevent malicious node connecting/disconnecting for timejacking)
-- New blocks will not be accepted if using future timestamp greater than NAT timestamp + 15 seconds (also, mantaining current protocol rule >= lastBlock.timestamp)
-- Network protocol fixed to 5-5 (Nodes with version prior to 1.5 will not be allowed)
-- Added protection for non included operations on a block, to prevent continuous sending. Only will resend operations once.
-- Bug #27 fixed: Invalid timestamp on FPC (https://github.com/PascalCoin/PascalCoin/issues/27)
-- JSON-RPC
-  - Method "getconnections" added "timediff" to know timestamp diff of node
-  - Method "payloaddecrypt" added "unenc_hexpayload" result value with HEXASTRING of unencrypted payload
-- Fixed some "Random Memory access violation errors" bugs found caused by multithreading and disconnected nodes
-
-### Build 1.5.3.0 - 2017-03-06
-
-- Fixed issue #23: RPC findoperation fails to find operation by opHash
-- Miners best practices: Sending new job with new timestamp every 30 seconds
-- Buffering last 10 sent jobs to miners
-- Small delay prior to destroy a connection to prevent exception handling
-- Minor logs changes
-
-
-### Build 1.5.2.0 - 2017-03-03
-
-- Added a jobs buffer for miners. This will allow to submit old job solutions (limited buffer). (Fix the "tx" issue)
-- Miner jobs will not be sent every time a transaction is received, thet will be buffered and sent every few seconds (Fix the "tx" issue)
-- Better network performance, allowing more operations and nodes thanks to buffering before relaying
-- Daemon: Allow select on ini file how many connections can handle
-- Fixed a locking when deleting connections
-
-### Build 1.5.1.0 - 2017-02-20
-
-- Memory leak fixed on RPC-JSON commands
-- Memory leak fixed on node connections
-- Improved network speed processing new blocks/operations
-- Some minor bugs
-
-### Build 1.5.0.0 - 2017-02-15
-
-- Net protocol upgrade to 4-5
-- Introducing "more work" with high priority than "more high". Work is calculated based on target. Higher target (more work) is more important than higher length
-- Solved locking/crash bug on high connections (caused by bad thread locking)
-- Improved network connection
-- Added JSON-RPC port 4003 protection Whitelist (only allowed IP's can use JSON-RPC)
-
-
-### Build 1.4.3.0 - 2017-02-02
-
-- Adding "maturation" param to "JSON Operation object", return null when operation is not included on a blockchain yet, 0 means that is included in highest block and so on...
-- Fixing miner timestamp value to prevent invalid time
-
-### Build 1.4.2.0 - 2017-01-23
-
-- Max JSON-RPC miner connections is now 1000 (before, 10)
-- JSON-RPC miner enabled at pascalcoin_daemon (linux daemon)
-- Screen messages for daemon
-- pascalcoin_daemon.ini file for daemon
-
-### Build 1.4.1.0 - 2017-01-18
-
-- Improved JSON communications with Miner client (Port 4009 by default)
-- Deleted adding numeration to JSON miner clients (only sends miner name)
-- Minor changes
-
-### Build 1.4.0.0 - 2016-12-30
-
-- JSON-RPC changes:
-  - Added method "signsendto" to allow a off-line wallet sign transaction operations without being syncrhonized
-  - Added method "signchangekey" to allow a off-line wallet sign change key operations without being syncrhonized
-  - Added method "executeoperations" to allow a on-line wallet execute operations signed off-line
-  - Added method "operationsinfo" that will decode operations signed off-line
-  - Added param "max" at JSON-RPC method "getblocks"
-  - Changed param name "deep" for "depht" in "getaccountoperations". Deep will be available for compatibility.
-  - Changed result for "changekeys". Now returns a "JSON operation object" array with each change key operation result
-  - Changes on "JSON Operation object": May return a "valid" param and "errors" param
-- Corrected a memory leak when processing JSON-RPC calls
-- Better performance in connection protocol
-- Updated protocol available to 1
-- Updated net protocol to 3-4
-- Important bug corrected
-
-### Build 1.3.0.0 - 2016-11-24
-
-- JSON-RPC modifications:
-  - New param "b58_pubkey", can be used instead of "enc_pubkey": b58_pubkey is a Base58 encoded public key with checksum, is the value that Wallet exports/imports public key
-  - New JSON object type Public Key: "name","can_use","enc_pubkey","b58_pubkey","ec_nid","x" and "y" for each public key
-  - Added params "start" and "max" to "getaccountoperations". By default deep=100, max=100 and start=0, so will return last 100 operations made to an account
-  - Added params "start" and "max" to "getblockoperations". By default max=100 and start=0, so will return last 100 operations made to a block
-  - Added params "start" and "max" to "getwalletaccounts". By default max=100 and start=0, so will return first 100 accounts of the wallet
-  - Added params "start" and "max" to "getwalletpubkeys". By default max=100 and start=0, so will return first 100 public keys of the wallet
-    - Will return a Public key JSON object
-  - Method "decodepubkey" allows params "enc_pubkey" or "b58_pubkey" (if used together, returns error if not match)
-    - Will return a Public key JSON object
-  - Method "changekey" allows params "new_enc_pubkey" or "new_b58_pubkey" (if used together, returns error if not match)
-  - Method "getwalletaccounts" allows params "enc_pubkey" or "b58_pubkey" (if used together, returns error if not match)
-  - Method "getwalletaccountscount" allows params "enc_pubkey" or "b58_pubkey" (if used together, returns error if not match)
-  - Method "getwalletcoins" allows params "enc_pubkey" or "b58_pubkey" (if used together, returns error if not match)  
-  - Method "payloadencrypt"  allows params "enc_pubkey" or "b58_pubkey" when payload_method="pubkey" (if used together, returns error if not match)  
-  - Method "addnewkey" changed. Does not return an HEXASTRING with enc_pubkey, now returns a public key JSON object
-  - New methods:
-    - Method "changekeys". Similar to "changekey" but for multiple accounts at param "accounts" as a coma separated string (ex: "accounts"="1248,1753,85056"). Returns a JSON object with result information
-    - Method "lock" to lock wallet. Returns true if locked, returns false if wallet has no password (an empty string, must put a new password prior to lock)
-    - New method "getwalletpubkey". Search for "enc_pubkey" or "b58_pubkey" and returns a Public key JSON object
-- Corrected a issue saving last 5 .bank files
-- Improved protections for .bank corruption files
-- Added "open data folder" button on options form
-- Other minor changes
-
-### Build 1.2.0.0 - 2016-11-16
-
-- Account checksum values modified to be more easy and more distributed: Checksum = ((N * 101) MOD 89)+10
-- Allow find operations by "ophash"
-- Show Operation "ophash" in operation payload decoder
-- Added param "enc_pubkey" to "getwalletaccounts" JSON-RPC method to return only accounts from this public key
-- Added params "pow" and "sbh" to "nodestatus" JSON-RPC method
-- Added method "getwalletaccountscount" returning accounts count of the entire wallet or for a single "enc_pubkey"
-- Added method "getwalletcoins" returning coins of the entire wallet or for a signle "enc_pubkey"
-- Modified seed nodes distribution to send only checked IP nodes
-- Corrected invalid operation block index when showing account operations
-
-
-### Build 1.1.0.0 - 2016-11-03
-
-- JSON-RPC Server included
-- Minor changes
-
-
-### Build 1.0.9.0 - 2016-10-21
-
-- Corrected a BUG (BUG-101) that causes blocking connections when received more than 100 connections, causing "alone in the world" after a cert period time.
-- It's necessary to update because new version will refuse old versions to make network stable.
-
-
-### Build 1.0.8.0 - 2016-10-20
-
-- Cross compatible
-- Can compile with Delphi or Lazarus (Free Pascal)
-- New storage system. No more access database
-- Network hashrate calculation
-
-
-### Build 1.0.7.0 - 2016-10-10
-
-- Introducing basic JSON-RPC to allow GPU miners development (Third party).
-- See file "HOWTO_DEVELOP_GPU_MINER_FOR_PASCALCOIN.txt"
-- No more CPU mining due exists GPU mining
-
-
-### Build 1.0.6.0 - 2016-10-04
-
-- Memory leaks corrections
-- Introducing net protocol 2-3
-- Source code modified, next build will be compiled with Lazarus and FPC
-
-
-### Build 1.0.5.0 - 2016-09-21
-
-- Massive operations, selecting multiple accounts
-- Filter accounts by balance
-- Correct operations explorer order of operations for each block (descending order)
-- Minor changes
-
-
-### Build 1.0.4.0 - 2016-09-16
-
-- IMPORTANT: Introducing net protocol changes: Must update!
-- More and more and more stable
-- Prevents "Alone in the world" if everybody is updated ;-)
-- Invalid local time detector with corrections
-- IP nodes configurator
-
-
-### Build 1.0.3.0 - 2016-09-08
-
-- Important changes to database 
-- Peer cache
-- Issues with Connections
-- More stable
-- Miner key selector
-- Invalid local time detector
-
-
-### Build 1.0.2.0 - 2016-08-31
-
-- Improved hashing speed
-- Allow mining without opening external ports
-- Choose how many CPU's want to mine
-- Show real-time pending operations waiting to be included in the block
-- More stable
-- Some miner modifications
-
-### Build 1.0.1.0 - 2016-08-12
-
-- Included an option to Import/Export Wallet keys file
-- Some miner modifications
-
-
-### Build 1.0.0.0 - 2016-08-11
-
-- First stable version.
-- Created with Genesis block hardcoded
-- Published at same time than Genesis block. NO PREMINE

+ 182 - 9
src/core/UAccounts.pas

@@ -307,6 +307,32 @@ Type
     Function Get(index : Integer) : TAccount;
     Function Get(index : Integer) : TAccount;
   End;
   End;
 
 
+  TAccountPreviousBlockInfoData = Record
+    Account : Cardinal;
+    Previous_updated_block : Cardinal;
+  end;
+
+  { TAccountPreviousBlockInfo }
+
+  TAccountPreviousBlockInfo = Class
+  private
+    FList : TList;
+    Function FindAccount(const account: Cardinal; var Index: Integer): Boolean;
+    function GetData(index : Integer): TAccountPreviousBlockInfoData;
+  public
+    Constructor Create;
+    Destructor Destroy; override;
+    Procedure UpdateIfLower(account, previous_updated_block : Cardinal);
+    Function Add(account, previous_updated_block : Cardinal) : Integer;
+    Procedure Remove(account : Cardinal);
+    Procedure Clear;
+    Procedure CopyFrom(Sender : TAccountPreviousBlockInfo);
+    Function IndexOfAccount(account : Cardinal) : Integer;
+    Property Data[index : Integer] : TAccountPreviousBlockInfoData read GetData;
+    Function GetPreviousUpdatedBlock(account : Cardinal; defaultValue : Cardinal) : Cardinal;
+    Function Count : Integer;
+  end;
+
   { TPCSafeBoxTransaction }
   { TPCSafeBoxTransaction }
 
 
   TPCSafeBoxTransaction = Class
   TPCSafeBoxTransaction = Class
@@ -323,10 +349,10 @@ Type
   public
   public
     Constructor Create(SafeBox : TPCSafeBox);
     Constructor Create(SafeBox : TPCSafeBox);
     Destructor Destroy; override;
     Destructor Destroy; override;
-    Function TransferAmount(sender,target : Cardinal; n_operation : Cardinal; amount, fee : UInt64; var errors : AnsiString) : Boolean;
-    Function TransferAmounts(const senders, n_operations : Array of Cardinal; const sender_amounts : Array of UInt64; const receivers : Array of Cardinal; const receivers_amounts : Array of UInt64; var errors : AnsiString) : Boolean;
-    Function UpdateAccountInfo(signer_account, signer_n_operation, target_account: Cardinal; accountInfo: TAccountInfo; newName : TRawBytes; newType : Word; fee: UInt64; var errors : AnsiString) : Boolean;
-    Function BuyAccount(buyer,account_to_buy,seller: Cardinal; n_operation : Cardinal; amount, account_price, fee : UInt64; const new_account_key : TAccountKey; var errors : AnsiString) : Boolean;
+    Function TransferAmount(previous : TAccountPreviousBlockInfo; sender,target : Cardinal; n_operation : Cardinal; amount, fee : UInt64; var errors : AnsiString) : Boolean;
+    Function TransferAmounts(previous : TAccountPreviousBlockInfo; const senders, n_operations : Array of Cardinal; const sender_amounts : Array of UInt64; const receivers : Array of Cardinal; const receivers_amounts : Array of UInt64; var errors : AnsiString) : Boolean;
+    Function UpdateAccountInfo(previous : TAccountPreviousBlockInfo; signer_account, signer_n_operation, target_account: Cardinal; accountInfo: TAccountInfo; newName : TRawBytes; newType : Word; fee: UInt64; var errors : AnsiString) : Boolean;
+    Function BuyAccount(previous : TAccountPreviousBlockInfo; buyer,account_to_buy,seller: Cardinal; n_operation : Cardinal; amount, account_price, fee : UInt64; const new_account_key : TAccountKey; var errors : AnsiString) : Boolean;
     Function Commit(Const operationBlock : TOperationBlock; var errors : AnsiString) : Boolean;
     Function Commit(Const operationBlock : TOperationBlock; var errors : AnsiString) : Boolean;
     Function Account(account_number : Cardinal) : TAccount;
     Function Account(account_number : Cardinal) : TAccount;
     Procedure Rollback;
     Procedure Rollback;
@@ -349,6 +375,8 @@ Type
     class Function ReadAccountKey(Stream: TStream; var value : TAccountKey): Integer;
     class Function ReadAccountKey(Stream: TStream; var value : TAccountKey): Integer;
   End;
   End;
 
 
+
+
 Const
 Const
   CT_OperationBlock_NUL : TOperationBlock = (block:0;account_key:(EC_OpenSSL_NID:0;x:'';y:'');reward:0;fee:0;protocol_version:0;
   CT_OperationBlock_NUL : TOperationBlock = (block:0;account_key:(EC_OpenSSL_NID:0;x:'';y:'');reward:0;fee:0;protocol_version:0;
     protocol_available:0;timestamp:0;compact_target:0;nonce:0;block_payload:'';operations_hash:'';proof_of_work:'');
     protocol_available:0;timestamp:0;compact_target:0;nonce:0;block_payload:'';operations_hash:'';proof_of_work:'');
@@ -1740,7 +1768,7 @@ function TPCSafeBox.DoUpgradeToProtocol3: Boolean;
 begin
 begin
   // XXXXXXXXXXX
   // XXXXXXXXXXX
   // XXXXXXXXXXX
   // XXXXXXXXXXX
-  // TODO
+  // TODO IF NEEDED
   // XXXXXXXXXXX
   // XXXXXXXXXXX
   // XXXXXXXXXXX
   // XXXXXXXXXXX
   FCurrentProtocol := CT_PROTOCOL_3;
   FCurrentProtocol := CT_PROTOCOL_3;
@@ -2569,7 +2597,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TPCSafeBoxTransaction.BuyAccount(buyer, account_to_buy,
+function TPCSafeBoxTransaction.BuyAccount(previous : TAccountPreviousBlockInfo; buyer, account_to_buy,
   seller: Cardinal; n_operation: Cardinal; amount, account_price, fee: UInt64;
   seller: Cardinal; n_operation: Cardinal; amount, account_price, fee: UInt64;
   const new_account_key: TAccountKey; var errors: AnsiString): Boolean;
   const new_account_key: TAccountKey; var errors: AnsiString): Boolean;
 Var PaccBuyer, PaccAccountToBuy, PaccSeller : PAccount;
 Var PaccBuyer, PaccAccountToBuy, PaccSeller : PAccount;
@@ -2635,6 +2663,10 @@ begin
     Exit;
     Exit;
   end;
   end;
 
 
+  previous.UpdateIfLower(PaccBuyer^.account,PaccBuyer^.updated_block);
+  previous.UpdateIfLower(PaccAccountToBuy^.account,PaccAccountToBuy^.updated_block);
+  previous.UpdateIfLower(PaccSeller^.account,PaccSeller^.updated_block);
+
   If PaccBuyer^.updated_block<>FFreezedAccounts.BlocksCount then begin
   If PaccBuyer^.updated_block<>FFreezedAccounts.BlocksCount then begin
     PaccBuyer^.previous_updated_block := PaccBuyer^.updated_block;
     PaccBuyer^.previous_updated_block := PaccBuyer^.updated_block;
     PaccBuyer^.updated_block := FFreezedAccounts.BlocksCount;
     PaccBuyer^.updated_block := FFreezedAccounts.BlocksCount;
@@ -2835,7 +2867,7 @@ begin
   CleanTransaction;
   CleanTransaction;
 end;
 end;
 
 
-function TPCSafeBoxTransaction.TransferAmount(sender, target: Cardinal;
+function TPCSafeBoxTransaction.TransferAmount(previous : TAccountPreviousBlockInfo; sender, target: Cardinal;
   n_operation: Cardinal; amount, fee: UInt64; var errors: AnsiString): Boolean;
   n_operation: Cardinal; amount, fee: UInt64; var errors: AnsiString): Boolean;
 Var
 Var
   intSender, intTarget : Integer;
   intSender, intTarget : Integer;
@@ -2883,6 +2915,9 @@ begin
     Exit;
     Exit;
   end;
   end;
 
 
+  previous.UpdateIfLower(PaccSender^.account,PaccSender^.updated_block);
+  previous.UpdateIfLower(PaccTarget^.account,PaccTarget^.updated_block);
+
   If PaccSender^.updated_block<>FFreezedAccounts.BlocksCount then begin
   If PaccSender^.updated_block<>FFreezedAccounts.BlocksCount then begin
     PaccSender^.previous_updated_block := PaccSender^.updated_block;
     PaccSender^.previous_updated_block := PaccSender^.updated_block;
     PaccSender^.updated_block := FFreezedAccounts.BlocksCount;
     PaccSender^.updated_block := FFreezedAccounts.BlocksCount;
@@ -2902,7 +2937,7 @@ begin
   Result := true;
   Result := true;
 end;
 end;
 
 
-function TPCSafeBoxTransaction.TransferAmounts(const senders,
+function TPCSafeBoxTransaction.TransferAmounts(previous : TAccountPreviousBlockInfo; const senders,
   n_operations: array of Cardinal; const sender_amounts: array of UInt64;
   n_operations: array of Cardinal; const sender_amounts: array of UInt64;
   const receivers: array of Cardinal; const receivers_amounts: array of UInt64;
   const receivers: array of Cardinal; const receivers_amounts: array of UInt64;
   var errors: AnsiString): Boolean;
   var errors: AnsiString): Boolean;
@@ -2993,6 +3028,7 @@ begin
   // Ok, execute!
   // Ok, execute!
   for i:=Low(senders) to High(senders) do begin
   for i:=Low(senders) to High(senders) do begin
     PaccSender := GetInternalAccount(senders[i]);
     PaccSender := GetInternalAccount(senders[i]);
+    previous.UpdateIfLower(PaccSender^.account,PaccSender^.updated_block);
     If PaccSender^.updated_block<>FFreezedAccounts.BlocksCount then begin
     If PaccSender^.updated_block<>FFreezedAccounts.BlocksCount then begin
       PaccSender^.previous_updated_block := PaccSender^.updated_block;
       PaccSender^.previous_updated_block := PaccSender^.updated_block;
       PaccSender^.updated_block := FFreezedAccounts.BlocksCount;
       PaccSender^.updated_block := FFreezedAccounts.BlocksCount;
@@ -3002,6 +3038,7 @@ begin
   end;
   end;
   for i:=Low(receivers) to High(receivers) do begin
   for i:=Low(receivers) to High(receivers) do begin
     PaccTarget := GetInternalAccount(receivers[i]);
     PaccTarget := GetInternalAccount(receivers[i]);
+    previous.UpdateIfLower(PaccTarget^.account,PaccTarget^.updated_block);
     If PaccTarget^.updated_block<>FFreezedAccounts.BlocksCount then begin
     If PaccTarget^.updated_block<>FFreezedAccounts.BlocksCount then begin
       PaccTarget^.previous_updated_block := PaccTarget.updated_block;
       PaccTarget^.previous_updated_block := PaccTarget.updated_block;
       PaccTarget^.updated_block := FFreezedAccounts.BlocksCount;
       PaccTarget^.updated_block := FFreezedAccounts.BlocksCount;
@@ -3013,7 +3050,8 @@ begin
   Result := true;
   Result := true;
 end;
 end;
 
 
-function TPCSafeBoxTransaction.UpdateAccountInfo(signer_account, signer_n_operation, target_account: Cardinal;
+function TPCSafeBoxTransaction.UpdateAccountInfo(previous : TAccountPreviousBlockInfo;
+  signer_account, signer_n_operation, target_account: Cardinal;
   accountInfo: TAccountInfo; newName: TRawBytes; newType: Word; fee: UInt64; var errors: AnsiString): Boolean;
   accountInfo: TAccountInfo; newName: TRawBytes; newType: Word; fee: UInt64; var errors: AnsiString): Boolean;
 Var i : Integer;
 Var i : Integer;
   P_signer, P_target : PAccount;
   P_signer, P_target : PAccount;
@@ -3052,11 +3090,13 @@ begin
     errors := 'Target account is locked until block '+Inttostr(P_target^.accountInfo.locked_until_block);
     errors := 'Target account is locked until block '+Inttostr(P_target^.accountInfo.locked_until_block);
     Exit;
     Exit;
   end;
   end;
+  previous.UpdateIfLower(P_signer^.account,P_signer^.updated_block);
   if P_signer^.updated_block <> FFreezedAccounts.BlocksCount then begin
   if P_signer^.updated_block <> FFreezedAccounts.BlocksCount then begin
     P_signer^.previous_updated_block := P_signer^.updated_block;
     P_signer^.previous_updated_block := P_signer^.updated_block;
     P_signer^.updated_block := FFreezedAccounts.BlocksCount;
     P_signer^.updated_block := FFreezedAccounts.BlocksCount;
   end;
   end;
   if (signer_account<>target_account) then begin
   if (signer_account<>target_account) then begin
+    previous.UpdateIfLower(P_target^.account,P_target^.updated_block);
     if P_target^.updated_block <> FFreezedAccounts.BlocksCount then begin
     if P_target^.updated_block <> FFreezedAccounts.BlocksCount then begin
       P_target^.previous_updated_block := P_target^.updated_block;
       P_target^.previous_updated_block := P_target^.updated_block;
       P_target^.updated_block := FFreezedAccounts.BlocksCount;
       P_target^.updated_block := FFreezedAccounts.BlocksCount;
@@ -3364,6 +3404,139 @@ begin
   Dispose(P);
   Dispose(P);
 end;
 end;
 
 
+{ TAccountPreviousBlockInfo }
+
+Type PAccountPreviousBlockInfoData = ^TAccountPreviousBlockInfoData;
+
+function TAccountPreviousBlockInfo.FindAccount(const account: Cardinal; var Index: Integer): Boolean;
+var L, H, I: Integer;
+  C : Int64;
+  P : PAccountPreviousBlockInfoData;
+begin
+  Result := False;
+  L := 0;
+  H := FList.Count - 1;
+  while L <= H do
+  begin
+    I := (L + H) shr 1;
+    P := FList[i];
+    C := Int64(P^.Account) - Int64(account);
+    if C < 0 then L := I + 1 else
+    begin
+      H := I - 1;
+      if C = 0 then
+      begin
+        Result := True;
+        L := I;
+      end;
+    end;
+  end;
+  Index := L;
+end;
+
+function TAccountPreviousBlockInfo.GetData(index : Integer): TAccountPreviousBlockInfoData;
+begin
+  Result := PAccountPreviousBlockInfoData(FList[index])^;
+end;
+
+constructor TAccountPreviousBlockInfo.Create;
+begin
+  FList := TList.Create;
+end;
+
+destructor TAccountPreviousBlockInfo.Destroy;
+begin
+  Clear;
+  FreeAndNil(FList);
+  inherited Destroy;
+end;
+
+procedure TAccountPreviousBlockInfo.UpdateIfLower(account, previous_updated_block: Cardinal);
+Var P : PAccountPreviousBlockInfoData;
+  i : Integer;
+begin
+  if (account>=CT_AccountsPerBlock) And (previous_updated_block=0) then Exit; // Only accounts 0..4 allow update on block 0
+
+  if Not FindAccount(account,i) then begin
+    New(P);
+    P^.Account:=account;
+    P^.Previous_updated_block:=previous_updated_block;
+    FList.Insert(i,P);
+  end else begin
+    P := FList[i];
+    If (P^.Previous_updated_block>previous_updated_block) then begin
+      P^.Previous_updated_block:=previous_updated_block;
+    end;
+  end
+end;
+
+function TAccountPreviousBlockInfo.Add(account, previous_updated_block: Cardinal): Integer;
+Var P : PAccountPreviousBlockInfoData;
+begin
+  if Not FindAccount(account,Result) then begin
+    New(P);
+    P^.Account:=account;
+    P^.Previous_updated_block:=previous_updated_block;
+    FList.Insert(Result,P);
+  end else begin
+    P := FList[Result];
+    P^.Previous_updated_block:=previous_updated_block;
+  end
+end;
+
+procedure TAccountPreviousBlockInfo.Remove(account: Cardinal);
+Var i : Integer;
+  P : PAccountPreviousBlockInfoData;
+begin
+  If FindAccount(account,i) then begin
+    P := FList[i];
+    FList.Delete(i);
+    Dispose(P);
+  end;
+end;
+
+procedure TAccountPreviousBlockInfo.Clear;
+var P : PAccountPreviousBlockInfoData;
+  i : Integer;
+begin
+  For i:=0 to FList.Count-1 do begin
+    P := FList[i];
+    Dispose(P);
+  end;
+  FList.Clear;
+end;
+
+procedure TAccountPreviousBlockInfo.CopyFrom(Sender: TAccountPreviousBlockInfo);
+Var P : PAccountPreviousBlockInfoData;
+  i : Integer;
+begin
+  if (Sender = Self) then Raise Exception.Create('ERROR DEV 20180312-4 Myself');
+  Clear;
+  For i:=0 to Sender.Count-1 do begin
+    New(P);
+    P^ := Sender.GetData(i);
+    FList.Add(P);
+  end;
+end;
+
+function TAccountPreviousBlockInfo.IndexOfAccount(account: Cardinal): Integer;
+begin
+  If Not FindAccount(account,Result) then Result := -1;
+end;
+
+function TAccountPreviousBlockInfo.GetPreviousUpdatedBlock(account: Cardinal; defaultValue : Cardinal): Cardinal;
+var i : Integer;
+begin
+  i := IndexOfAccount(account);
+  If i>=0 then Result := GetData(i).Previous_updated_block
+  else Result := defaultValue;
+end;
+
+function TAccountPreviousBlockInfo.Count: Integer;
+begin
+  Result := FList.Count;
+end;
+
 { TOrderedCardinalList }
 { TOrderedCardinalList }
 
 
 function TOrderedCardinalList.Add(Value: Cardinal): Integer;
 function TOrderedCardinalList.Add(Value: Cardinal): Integer;

+ 94 - 27
src/core/UBlockChain.pas

@@ -159,6 +159,7 @@ Type
     OperationHash_OLD : TRawBytes; // Will include old oeration hash value
     OperationHash_OLD : TRawBytes; // Will include old oeration hash value
     errors : AnsiString;
     errors : AnsiString;
     // New on V3 for PIP-0017
     // New on V3 for PIP-0017
+    isMultiOperation : Boolean;
     Senders : TMultiOpSenders;
     Senders : TMultiOpSenders;
     Receivers : TMultiOpReceivers;
     Receivers : TMultiOpReceivers;
     Changers : TMultiOpChangesInfo;
     Changers : TMultiOpChangesInfo;
@@ -199,28 +200,33 @@ Type
     procedure InitializeData; virtual;
     procedure InitializeData; virtual;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; virtual; abstract;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; virtual; abstract;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; virtual; abstract;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; virtual; abstract;
+    procedure FillOperationResume(Block : Cardinal; Affected_account_number : Cardinal; var OperationResume : TOperationResume); virtual;
+    Property Previous_Signer_updated_block : Cardinal read FPrevious_Signer_updated_block; // deprecated
+    Property Previous_Destination_updated_block : Cardinal read FPrevious_Destination_updated_block; // deprecated
+    Property Previous_Seller_updated_block : Cardinal read FPrevious_Seller_updated_block; // deprecated
   public
   public
     constructor Create; virtual;
     constructor Create; virtual;
+    destructor Destroy; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; virtual;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; virtual;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean; virtual; abstract;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean; virtual; abstract;
     procedure AffectedAccounts(list : TList); virtual; abstract;
     procedure AffectedAccounts(list : TList); virtual; abstract;
     class function OpType: Byte; virtual; abstract;
     class function OpType: Byte; virtual; abstract;
-    Class Function OperationToOperationResume(Block : Cardinal; Operation : TPCOperation; Affected_account_number : Cardinal; var OperationResume : TOperationResume) : Boolean;
+    Class Function OperationToOperationResume(Block : Cardinal; Operation : TPCOperation; Affected_account_number : Cardinal; var OperationResume : TOperationResume) : Boolean; virtual;
     function OperationAmount : Int64; virtual; abstract;
     function OperationAmount : Int64; virtual; abstract;
     function OperationFee: UInt64; virtual; abstract;
     function OperationFee: UInt64; virtual; abstract;
     function OperationPayload : TRawBytes; virtual; abstract;
     function OperationPayload : TRawBytes; virtual; abstract;
     function SignerAccount : Cardinal; virtual; abstract;
     function SignerAccount : Cardinal; virtual; abstract;
+    function IsSignerAccount(account : Cardinal) : Boolean; virtual;
+    function IsAffectedAccount(account : Cardinal) : Boolean; virtual;
     function DestinationAccount : Int64; virtual;
     function DestinationAccount : Int64; virtual;
     function SellerAccount : Int64; virtual;
     function SellerAccount : Int64; virtual;
     function N_Operation : Cardinal; virtual; abstract;
     function N_Operation : Cardinal; virtual; abstract;
+    function GetAccountN_Operation(account : Cardinal) : Cardinal; virtual;
     Property tag : integer read Ftag Write Ftag;
     Property tag : integer read Ftag Write Ftag;
     function SaveToNettransfer(Stream: TStream): Boolean;
     function SaveToNettransfer(Stream: TStream): Boolean;
     function LoadFromNettransfer(Stream: TStream): Boolean;
     function LoadFromNettransfer(Stream: TStream): Boolean;
     function SaveToStorage(Stream: TStream): Boolean;
     function SaveToStorage(Stream: TStream): Boolean;
-    function LoadFromStorage(Stream: TStream; LoadProtocolV2 : Boolean): Boolean;
-    Property Previous_Signer_updated_block : Cardinal read FPrevious_Signer_updated_block;
-    Property Previous_Destination_updated_block : Cardinal read FPrevious_Destination_updated_block;
-    Property Previous_Seller_updated_block : Cardinal read FPrevious_Seller_updated_block;
+    function LoadFromStorage(Stream: TStream; LoadProtocolVersion : Word; APreviousUpdatedBlocks : TAccountPreviousBlockInfo): Boolean;
     Property HasValidSignature : Boolean read FHasValidSignature;
     Property HasValidSignature : Boolean read FHasValidSignature;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
@@ -260,7 +266,7 @@ Type
     Property TotalAmount : Int64 read FTotalAmount;
     Property TotalAmount : Int64 read FTotalAmount;
     Property TotalFee : Int64 read FTotalFee;
     Property TotalFee : Int64 read FTotalFee;
     function SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage : Boolean): Boolean;
     function SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage : Boolean): Boolean;
-    function LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage, LoadProtocolV2 : Boolean; var errors : AnsiString): Boolean;
+    function LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage : Boolean; LoadProtocolVersion : Word; PreviousUpdatedBlocks : TAccountPreviousBlockInfo; var errors : AnsiString): Boolean;
     function IndexOfOperation(op : TPCOperation) : Integer;
     function IndexOfOperation(op : TPCOperation) : Integer;
     function CountOperationsBySameSignerWithoutFee(account_number : Cardinal) : Integer;
     function CountOperationsBySameSignerWithoutFee(account_number : Cardinal) : Integer;
     Procedure Delete(index : Integer);
     Procedure Delete(index : Integer);
@@ -282,6 +288,7 @@ Type
     FStreamPoW : TMemoryStream;
     FStreamPoW : TMemoryStream;
     FDisableds : Integer;
     FDisableds : Integer;
     FOperationsLock : TPCCriticalSection;
     FOperationsLock : TPCCriticalSection;
+    FPreviousUpdatedBlocks : TAccountPreviousBlockInfo; // New Protocol V3 struct to store previous updated blocks
     function GetOperation(index: Integer): TPCOperation;
     function GetOperation(index: Integer): TPCOperation;
     procedure SetBank(const value: TPCBank);
     procedure SetBank(const value: TPCBank);
     procedure SetnOnce(const value: Cardinal);
     procedure SetnOnce(const value: Cardinal);
@@ -345,6 +352,8 @@ Type
     Property PoW_Digest_Part1 : TRawBytes read FDigest_Part1;
     Property PoW_Digest_Part1 : TRawBytes read FDigest_Part1;
     Property PoW_Digest_Part2_Payload : TRawBytes read FDigest_Part2_Payload;
     Property PoW_Digest_Part2_Payload : TRawBytes read FDigest_Part2_Payload;
     Property PoW_Digest_Part3 : TRawBytes read FDigest_Part3;
     Property PoW_Digest_Part3 : TRawBytes read FDigest_Part3;
+    //
+    Property PreviousUpdatedBlocks : TAccountPreviousBlockInfo read FPreviousUpdatedBlocks; // New Protocol V3 struct to store previous updated blocks
   End;
   End;
 
 
   TPCBankLog = procedure(sender: TPCBank; Operations: TPCOperationsComp; Logtype: TLogType ; Logtxt: AnsiString) of object;
   TPCBankLog = procedure(sender: TPCBank; Operations: TPCOperationsComp; Logtype: TLogType ; Logtxt: AnsiString) of object;
@@ -457,7 +466,7 @@ Type
   End;
   End;
 
 
 Const
 Const
-  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;OpSubtype:0;time:0;AffectedAccount:0;SignerAccount:-1;n_operation:0;DestAccount:-1;SellerAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';OperationHash_OLD:'';errors:'';Senders:Nil;Receivers:Nil;changers:Nil);
+  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;OpSubtype:0;time:0;AffectedAccount:0;SignerAccount:-1;n_operation:0;DestAccount:-1;SellerAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';OperationHash_OLD:'';errors:'';isMultiOperation:False;Senders:Nil;Receivers:Nil;changers:Nil);
   CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
   CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:'');
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:'');
   CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:'';y:'');New_Name:'';New_Type:0;Signature:(r:'';s:''));
   CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:'';y:'');New_Name:'';New_Type:0;Signature:(r:'';s:''));
@@ -830,7 +839,7 @@ Begin
         exit;
         exit;
       end;
       end;
       // Only process when in current address, prevent do it when reading operations from file
       // Only process when in current address, prevent do it when reading operations from file
-      Result := op.DoOperation(SafeBoxTransaction, errors);
+      Result := op.DoOperation(FPreviousUpdatedBlocks, FSafeBoxTransaction, errors);
     end else Result := true;
     end else Result := true;
     if Result then begin
     if Result then begin
       FOperationsHashTree.AddOperationToHashTree(op);
       FOperationsHashTree.AddOperationToHashTree(op);
@@ -903,6 +912,7 @@ begin
   Try
   Try
     if DeleteOperations then begin
     if DeleteOperations then begin
       FOperationsHashTree.ClearHastThree;
       FOperationsHashTree.ClearHastThree;
+      FPreviousUpdatedBlocks.Clear;
       if Assigned(FSafeBoxTransaction) then
       if Assigned(FSafeBoxTransaction) then
         FSafeBoxTransaction.CleanTransaction;
         FSafeBoxTransaction.CleanTransaction;
     end;
     end;
@@ -958,6 +968,7 @@ begin
     if Assigned(FSafeBoxTransaction) And Assigned(Operations.FSafeBoxTransaction) then begin
     if Assigned(FSafeBoxTransaction) And Assigned(Operations.FSafeBoxTransaction) then begin
       FSafeBoxTransaction.CopyFrom(Operations.FSafeBoxTransaction);
       FSafeBoxTransaction.CopyFrom(Operations.FSafeBoxTransaction);
     end;
     end;
+    FPreviousUpdatedBlocks.CopyFrom(Operations.FPreviousUpdatedBlocks);
     FDigest_Part1 := Operations.FDigest_Part1;
     FDigest_Part1 := Operations.FDigest_Part1;
     FDigest_Part2_Payload := Operations.FDigest_Part2_Payload;
     FDigest_Part2_Payload := Operations.FDigest_Part2_Payload;
     FDigest_Part3 := Operations.FDigest_Part3;
     FDigest_Part3 := Operations.FDigest_Part3;
@@ -983,6 +994,7 @@ begin
     if Assigned(FSafeBoxTransaction) And Assigned(Operations.FSafeBoxTransaction) then begin
     if Assigned(FSafeBoxTransaction) And Assigned(Operations.FSafeBoxTransaction) then begin
       FSafeBoxTransaction.CopyFrom(Operations.FSafeBoxTransaction);
       FSafeBoxTransaction.CopyFrom(Operations.FSafeBoxTransaction);
     end;
     end;
+    FPreviousUpdatedBlocks.CopyFrom(Operations.FPreviousUpdatedBlocks);
     // Recalc all
     // Recalc all
     CalcProofOfWork(true,FOperationBlock.proof_of_work);
     CalcProofOfWork(true,FOperationBlock.proof_of_work);
   finally
   finally
@@ -1007,6 +1019,7 @@ begin
   FBank := Nil;
   FBank := Nil;
   FOperationBlock := GetFirstBlock;
   FOperationBlock := GetFirstBlock;
   FSafeBoxTransaction := Nil;
   FSafeBoxTransaction := Nil;
+  FPreviousUpdatedBlocks := TAccountPreviousBlockInfo.Create;
   if Assigned(AOwner) And (AOwner is TPCBank) then begin
   if Assigned(AOwner) And (AOwner is TPCBank) then begin
     Bank := TPCBank(AOwner);
     Bank := TPCBank(AOwner);
   end else Clear(true);
   end else Clear(true);
@@ -1022,6 +1035,7 @@ begin
       FreeAndNil(FSafeBoxTransaction);
       FreeAndNil(FSafeBoxTransaction);
     end;
     end;
     FreeAndNil(FStreamPoW);
     FreeAndNil(FStreamPoW);
+    FreeAndNil(FPreviousUpdatedBlocks);
   finally
   finally
     FreeAndNil(FOperationsLock);
     FreeAndNil(FOperationsLock);
   end;
   end;
@@ -1117,11 +1131,11 @@ begin
 end;
 end;
 
 
 function TPCOperationsComp.LoadBlockFromStreamExt(Stream: TStream; LoadingFromStorage: Boolean; var errors: AnsiString): Boolean;
 function TPCOperationsComp.LoadBlockFromStreamExt(Stream: TStream; LoadingFromStorage: Boolean; var errors: AnsiString): Boolean;
-Var i: Cardinal;
+Var i : Integer;
   lastfee : UInt64;
   lastfee : UInt64;
   soob : Byte;
   soob : Byte;
   m: AnsiString;
   m: AnsiString;
-  load_protocol_v2 : Boolean;
+  load_protocol_version : Word;
 begin
 begin
   Lock;
   Lock;
   Try
   Try
@@ -1133,6 +1147,7 @@ begin
       errors := 'Invalid protocol structure. Check application version!';
       errors := 'Invalid protocol structure. Check application version!';
       exit;
       exit;
     end;
     end;
+    soob := 255;
     Stream.Read(soob,1);
     Stream.Read(soob,1);
     // About soob var:
     // About soob var:
     // In build prior to 1.0.4 soob only can have 2 values: 0 or 1
     // In build prior to 1.0.4 soob only can have 2 values: 0 or 1
@@ -1143,12 +1158,12 @@ begin
     // - Value 1 and 3 means that only contains operationblock info
     // - Value 1 and 3 means that only contains operationblock info
     // - Value 2 and 3 means that contains protocol info prior to block number
     // - Value 2 and 3 means that contains protocol info prior to block number
     // - Value 4 means that is loading from storage using protocol v2 (so, includes always operations)
     // - Value 4 means that is loading from storage using protocol v2 (so, includes always operations)
-    load_protocol_v2 := false;
+    load_protocol_version := CT_PROTOCOL_1;
     if (soob in [0,2]) then FIsOnlyOperationBlock:=false
     if (soob in [0,2]) then FIsOnlyOperationBlock:=false
     else if (soob in [1,3]) then FIsOnlyOperationBlock:=true
     else if (soob in [1,3]) then FIsOnlyOperationBlock:=true
     else if (soob in [4]) then begin
     else if (soob in [4]) then begin
       FIsOnlyOperationBlock:=false;
       FIsOnlyOperationBlock:=false;
-      load_protocol_v2 := true;
+      load_protocol_version := CT_PROTOCOL_2;
     end else begin
     end else begin
       errors := 'Invalid value in protocol header! Found:'+inttostr(soob)+' - Check if your application version is Ok';
       errors := 'Invalid value in protocol header! Found:'+inttostr(soob)+' - Check if your application version is Ok';
       exit;
       exit;
@@ -1183,10 +1198,11 @@ begin
     // Fee will be calculated for each operation. Set it to 0 and check later for integrity
     // Fee will be calculated for each operation. Set it to 0 and check later for integrity
     lastfee := OperationBlock.fee;
     lastfee := OperationBlock.fee;
     FOperationBlock.fee := 0;
     FOperationBlock.fee := 0;
-    Result := FOperationsHashTree.LoadOperationsHashTreeFromStream(Stream,LoadingFromStorage,load_protocol_v2,errors);
+    Result := FOperationsHashTree.LoadOperationsHashTreeFromStream(Stream,LoadingFromStorage,load_protocol_version,FPreviousUpdatedBlocks,errors);
     if not Result then begin
     if not Result then begin
       exit;
       exit;
     end;
     end;
+    //
     FOperationBlock.fee := FOperationsHashTree.TotalFee;
     FOperationBlock.fee := FOperationsHashTree.TotalFee;
     FOperationBlock.operations_hash := FOperationsHashTree.HashTree;
     FOperationBlock.operations_hash := FOperationsHashTree.HashTree;
     Calc_Digest_Parts;
     Calc_Digest_Parts;
@@ -1278,12 +1294,13 @@ begin
     FOperationBlock.fee := 0;
     FOperationBlock.fee := 0;
     //
     //
     SafeBoxTransaction.CleanTransaction;
     SafeBoxTransaction.CleanTransaction;
+    FPreviousUpdatedBlocks.Clear;
     aux := TOperationsHashTree.Create;
     aux := TOperationsHashTree.Create;
     Try
     Try
       lastn := FOperationsHashTree.OperationsCount;
       lastn := FOperationsHashTree.OperationsCount;
       for i:=0 to lastn-1 do begin
       for i:=0 to lastn-1 do begin
         op := FOperationsHashTree.GetOperation(i);
         op := FOperationsHashTree.GetOperation(i);
-        if (op.DoOperation(SafeBoxTransaction,errors)) then begin
+        if (op.DoOperation(FPreviousUpdatedBlocks, SafeBoxTransaction,errors)) then begin
           inc(n);
           inc(n);
           aux.AddOperationToHashTree(op);
           aux.AddOperationToHashTree(op);
           inc(FOperationBlock.fee,op.OperationFee);
           inc(FOperationBlock.fee,op.OperationFee);
@@ -1490,8 +1507,7 @@ begin
 end;
 end;
 
 
 function TPCOperationsComp.ValidateOperationBlock(var errors : AnsiString): Boolean;
 function TPCOperationsComp.ValidateOperationBlock(var errors : AnsiString): Boolean;
-Var lastpow : AnsiString;
-  i : Integer;
+Var i : Integer;
 begin
 begin
   errors := '';
   errors := '';
   Result := False;
   Result := False;
@@ -1509,8 +1525,9 @@ begin
     If not SafeBoxTransaction.FreezedSafeBox.IsValidNewOperationsBlock(OperationBlock,True,errors) then exit;
     If not SafeBoxTransaction.FreezedSafeBox.IsValidNewOperationsBlock(OperationBlock,True,errors) then exit;
     // Execute SafeBoxTransaction operations:
     // Execute SafeBoxTransaction operations:
     SafeBoxTransaction.Rollback;
     SafeBoxTransaction.Rollback;
+    FPreviousUpdatedBlocks.Clear;
     for i := 0 to Count - 1 do begin
     for i := 0 to Count - 1 do begin
-      If Not Operation[i].DoOperation(SafeBoxTransaction,errors) then begin
+      If Not Operation[i].DoOperation(FPreviousUpdatedBlocks, SafeBoxTransaction,errors) then begin
         errors := 'Error executing operation '+inttostr(i+1)+'/'+inttostr(Count)+': '+errors;
         errors := 'Error executing operation '+inttostr(i+1)+'/'+inttostr(Count)+': '+errors;
         exit;
         exit;
       end;
       end;
@@ -1965,7 +1982,7 @@ begin
   Index := L;
   Index := L;
 end;
 end;
 
 
-function TOperationsHashTree.LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage, LoadProtocolV2: Boolean; var errors: AnsiString): Boolean;
+function TOperationsHashTree.LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage : Boolean; LoadProtocolVersion : Word; PreviousUpdatedBlocks : TAccountPreviousBlockInfo; var errors: AnsiString): Boolean;
 Var c, i: Cardinal;
 Var c, i: Cardinal;
   OpType: Cardinal;
   OpType: Cardinal;
   bcop: TPCOperation;
   bcop: TPCOperation;
@@ -2001,7 +2018,7 @@ begin
       bcop := OpClass.Create;
       bcop := OpClass.Create;
       Try
       Try
         if LoadingFromStorage then begin
         if LoadingFromStorage then begin
-          If not bcop.LoadFromStorage(Stream,LoadProtocolV2) then begin
+          If not bcop.LoadFromStorage(Stream,LoadProtocolVersion,PreviousUpdatedBlocks) then begin
             errors := 'Invalid operation load from storage ' + inttostr(i) + '/' + inttostr(c)+' Class:'+OpClass.ClassName;
             errors := 'Invalid operation load from storage ' + inttostr(i) + '/' + inttostr(c)+' Class:'+OpClass.ClassName;
             exit;
             exit;
           end;
           end;
@@ -2174,6 +2191,11 @@ begin
   InitializeData;
   InitializeData;
 end;
 end;
 
 
+destructor TPCOperation.Destroy;
+begin
+  inherited Destroy;
+end;
+
 function TPCOperation.GetBufferForOpHash(UseProtocolV2: Boolean): TRawBytes;
 function TPCOperation.GetBufferForOpHash(UseProtocolV2: Boolean): TRawBytes;
 Var ms : TMemoryStream;
 Var ms : TMemoryStream;
 begin
 begin
@@ -2271,21 +2293,41 @@ begin
   FSignatureChecked := False;
   FSignatureChecked := False;
 end;
 end;
 
 
+procedure TPCOperation.FillOperationResume(Block: Cardinal; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  // XXXXXXXXXXXXXXXX
+  // TODO
+  // change from class function TPCOperation.OperationToOperationResume(Block : Cardinal; Operation: TPCOperation; Affected_account_number: Cardinal; var OperationResume: TOperationResume): Boolean;
+  // to here
+end;
+
 function TPCOperation.LoadFromNettransfer(Stream: TStream): Boolean;
 function TPCOperation.LoadFromNettransfer(Stream: TStream): Boolean;
 begin
 begin
   Result := LoadOpFromStream(Stream, False);
   Result := LoadOpFromStream(Stream, False);
 end;
 end;
 
 
-function TPCOperation.LoadFromStorage(Stream: TStream; LoadProtocolV2:Boolean): Boolean;
+function TPCOperation.LoadFromStorage(Stream: TStream; LoadProtocolVersion:Word; APreviousUpdatedBlocks : TAccountPreviousBlockInfo): Boolean;
+Var w : Word;
+  i : Integer;
+  cAccount,cPreviousUpdated : Cardinal;
 begin
 begin
   Result := false;
   Result := false;
-  If LoadOpFromStream(Stream, LoadProtocolV2) then begin
+  If LoadOpFromStream(Stream, LoadProtocolVersion>=2) then begin
     if Stream.Size - Stream.Position<8 then exit;
     if Stream.Size - Stream.Position<8 then exit;
     Stream.Read(FPrevious_Signer_updated_block,Sizeof(FPrevious_Signer_updated_block));
     Stream.Read(FPrevious_Signer_updated_block,Sizeof(FPrevious_Signer_updated_block));
     Stream.Read(FPrevious_Destination_updated_block,Sizeof(FPrevious_Destination_updated_block));
     Stream.Read(FPrevious_Destination_updated_block,Sizeof(FPrevious_Destination_updated_block));
-    if (LoadProtocolV2) then begin
+    if (LoadProtocolVersion=2) then begin
       Stream.Read(FPrevious_Seller_updated_block,Sizeof(FPrevious_Seller_updated_block));
       Stream.Read(FPrevious_Seller_updated_block,Sizeof(FPrevious_Seller_updated_block));
     end;
     end;
+    if Assigned(APreviousUpdatedBlocks) then begin
+      // Add to previous list!
+      if SignerAccount>=0 then
+        APreviousUpdatedBlocks.UpdateIfLower(SignerAccount,FPrevious_Signer_updated_block);
+      if DestinationAccount>=0 then
+        APreviousUpdatedBlocks.UpdateIfLower(DestinationAccount,FPrevious_Destination_updated_block);
+      if SellerAccount>=0 then
+        APreviousUpdatedBlocks.UpdateIfLower(SellerAccount,FPrevious_Seller_updated_block);
+    end;
     Result := true;
     Result := true;
   end;
   end;
 end;
 end;
@@ -2351,9 +2393,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-class function TPCOperation.OperationToOperationResume(Block : Cardinal; Operation: TPCOperation;
-  Affected_account_number: Cardinal;
-  var OperationResume: TOperationResume): Boolean;
+class function TPCOperation.OperationToOperationResume(Block : Cardinal; Operation: TPCOperation; Affected_account_number: Cardinal; var OperationResume: TOperationResume): Boolean;
 Var spayload : AnsiString;
 Var spayload : AnsiString;
   s : AnsiString;
   s : AnsiString;
 begin
 begin
@@ -2490,7 +2530,9 @@ begin
       Result := True;
       Result := True;
     end;
     end;
     CT_Op_MultiOperation : Begin
     CT_Op_MultiOperation : Begin
-
+      OperationResume.isMultiOperation:=True;
+      OperationResume.OpSubtype := CT_OpSubtype_MultiOperation;
+      OperationResume.OperationTxt := Operation.ToString;
     end
     end
   else Exit;
   else Exit;
   end;
   end;
@@ -2502,6 +2544,24 @@ begin
     OperationResume.OperationHash_OLD:=TPCOperation.OperationHash_OLD(Operation,Block);
     OperationResume.OperationHash_OLD:=TPCOperation.OperationHash_OLD(Operation,Block);
   end;
   end;
   OperationResume.valid := true;
   OperationResume.valid := true;
+  Operation.FillOperationResume(Block,Affected_account_number,OperationResume);
+end;
+
+function TPCOperation.IsSignerAccount(account: Cardinal): Boolean;
+begin
+  Result := SignerAccount = account;
+end;
+
+function TPCOperation.IsAffectedAccount(account: Cardinal): Boolean;
+Var l : TList;
+begin
+  l := TList.Create;
+  Try
+    AffectedAccounts(l);
+    Result := (l.IndexOf(TObject(account))>=0);
+  finally
+    l.Free;
+  end;
 end;
 end;
 
 
 function TPCOperation.DestinationAccount: Int64;
 function TPCOperation.DestinationAccount: Int64;
@@ -2514,12 +2574,19 @@ begin
   Result := -1;
   Result := -1;
 end;
 end;
 
 
+function TPCOperation.GetAccountN_Operation(account: Cardinal): Cardinal;
+begin
+  If (SignerAccount = account) then Result := N_Operation
+  else Result := 0;
+end;
+
 function TPCOperation.SaveToNettransfer(Stream: TStream): Boolean;
 function TPCOperation.SaveToNettransfer(Stream: TStream): Boolean;
 begin
 begin
   Result := SaveOpToStream(Stream,False);
   Result := SaveOpToStream(Stream,False);
 end;
 end;
 
 
 function TPCOperation.SaveToStorage(Stream: TStream): Boolean;
 function TPCOperation.SaveToStorage(Stream: TStream): Boolean;
+
 begin
 begin
   Result := SaveOpToStream(Stream,True);
   Result := SaveOpToStream(Stream,True);
   Stream.Write(FPrevious_Signer_updated_block,Sizeof(FPrevious_Signer_updated_block));
   Stream.Write(FPrevious_Signer_updated_block,Sizeof(FPrevious_Signer_updated_block));

+ 1 - 0
src/core/UConst.pas

@@ -145,6 +145,7 @@ Const
   CT_OpSubtype_BuyAccountSeller           = 63;
   CT_OpSubtype_BuyAccountSeller           = 63;
   CT_OpSubtype_ChangeKeySigned            = 71;
   CT_OpSubtype_ChangeKeySigned            = 71;
   CT_OpSubtype_ChangeAccountInfo          = 81;
   CT_OpSubtype_ChangeAccountInfo          = 81;
+  CT_OpSubtype_MultiOperation             = 91;
 
 
   CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'2.1.6'{$ELSE}{$IFDEF TESTNET}'TESTNET 2.1.6'{$ELSE}{$ENDIF}{$ENDIF};
   CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'2.1.6'{$ELSE}{$IFDEF TESTNET}'TESTNET 2.1.6'{$ELSE}{$ENDIF}{$ENDIF};
 
 

+ 1 - 1
src/core/UFileStorage.pas

@@ -307,7 +307,7 @@ begin
   Try
   Try
     fs := GetPendingBufferOperationsStream;
     fs := GetPendingBufferOperationsStream;
     fs.Position:=0;
     fs.Position:=0;
-    If OperationsHashTree.LoadOperationsHashTreeFromStream(fs,true,true,errors) then begin
+    If OperationsHashTree.LoadOperationsHashTreeFromStream(fs,true,CT_PROTOCOL_2,Nil,errors) then begin
       TLog.NewLog(ltInfo,ClassName,Format('DoLoadPendingBufferOperations loaded operations:%d',[OperationsHashTree.OperationsCount]));
       TLog.NewLog(ltInfo,ClassName,Format('DoLoadPendingBufferOperations loaded operations:%d',[OperationsHashTree.OperationsCount]));
     end else TLog.NewLog(ltError,ClassName,Format('DoLoadPendingBufferOperations ERROR: loaded operations:%d errors:%s',[OperationsHashTree.OperationsCount,errors]));
     end else TLog.NewLog(ltError,ClassName,Format('DoLoadPendingBufferOperations ERROR: loaded operations:%d errors:%s',[OperationsHashTree.OperationsCount,errors]));
   finally
   finally

+ 49 - 39
src/core/UNode.pas

@@ -80,7 +80,7 @@ Type
     //
     //
     Procedure NotifyBlocksChanged;
     Procedure NotifyBlocksChanged;
     //
     //
-    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer);
+    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer; SearchBackwardsStartingAtBlock : Cardinal=0);
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     Function FindOperationExt(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : TSearchOperationResult;
     Function FindOperationExt(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : TSearchOperationResult;
     Function FindNOperation(block, account, n_operation : Cardinal; var OpResume : TOperationResume) : TSearchOperationResult;
     Function FindNOperation(block, account, n_operation : Cardinal; var OpResume : TOperationResume) : TSearchOperationResult;
@@ -694,16 +694,19 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation: Integer);
+procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation: Integer; SearchBackwardsStartingAtBlock : Cardinal = 0);
   // Optimization:
   // Optimization:
   // For better performance, will only include at "OperationsResume" values betweeen "startOperation" and "endOperation"
   // For better performance, will only include at "OperationsResume" values betweeen "startOperation" and "endOperation"
-  Procedure DoGetFromBlock(block_number : Integer; last_balance : Int64; act_depth : Integer; nOpsCounter : Integer);
+
+  // New use case: Will allow to start in an unknown block when first_block_is_unknows
+  Procedure DoGetFromBlock(block_number : Integer; last_balance : Int64; act_depth : Integer; nOpsCounter : Integer; first_block_is_unknown : Boolean);
   var opc : TPCOperationsComp;
   var opc : TPCOperationsComp;
     op : TPCOperation;
     op : TPCOperation;
     OPR : TOperationResume;
     OPR : TOperationResume;
     l : TList;
     l : TList;
     i : Integer;
     i : Integer;
-    last_block_number, next_block_number : Integer;
+    last_block_number : Integer;
+    found_in_block : Boolean;
   begin
   begin
     if (act_depth<=0) then exit;
     if (act_depth<=0) then exit;
     opc := TPCOperationsComp.Create(Nil);
     opc := TPCOperationsComp.Create(Nil);
@@ -714,8 +717,8 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
         while (last_block_number>block_number) And (act_depth>0)
         while (last_block_number>block_number) And (act_depth>0)
           And (block_number >= (account_number DIV CT_AccountsPerBlock))
           And (block_number >= (account_number DIV CT_AccountsPerBlock))
           And (nOpsCounter <= EndOperation) do begin
           And (nOpsCounter <= EndOperation) do begin
+          found_in_block := False;
           last_block_number := block_number;
           last_block_number := block_number;
-          next_block_number := block_number;
           l.Clear;
           l.Clear;
           If not Bank.Storage.LoadBlockChainBlock(opc,block_number) then begin
           If not Bank.Storage.LoadBlockChainBlock(opc,block_number) then begin
             TLog.NewLog(ltdebug,ClassName,'Block '+inttostr(block_number)+' not found. Cannot read operations');
             TLog.NewLog(ltdebug,ClassName,'Block '+inttostr(block_number)+' not found. Cannot read operations');
@@ -724,23 +727,22 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
           opc.OperationsHashTree.GetOperationsAffectingAccount(account_number,l);
           opc.OperationsHashTree.GetOperationsAffectingAccount(account_number,l);
           for i := l.Count - 1 downto 0 do begin
           for i := l.Count - 1 downto 0 do begin
             op := opc.Operation[PtrInt(l.Items[i])];
             op := opc.Operation[PtrInt(l.Items[i])];
-            if (i=0) then begin
-              If op.SignerAccount=account_number then next_block_number := op.Previous_Signer_updated_block
-              else if (op.DestinationAccount=account_number) then next_block_number := op.Previous_Destination_updated_block
-              else if (op.SellerAccount=account_number) then next_block_number:=op.Previous_Seller_updated_block;
-            end;
             If TPCOperation.OperationToOperationResume(block_number,Op,account_number,OPR) then begin
             If TPCOperation.OperationToOperationResume(block_number,Op,account_number,OPR) then begin
               OPR.NOpInsideBlock := Op.tag; // Note: Used Op.tag to include operation index inside a list
               OPR.NOpInsideBlock := Op.tag; // Note: Used Op.tag to include operation index inside a list
               OPR.time := opc.OperationBlock.timestamp;
               OPR.time := opc.OperationBlock.timestamp;
               OPR.Block := block_number;
               OPR.Block := block_number;
-              OPR.Balance := last_balance;
-              last_balance := last_balance - ( OPR.Amount + OPR.Fee );
+              If last_balance>=0 then begin
+                OPR.Balance := last_balance;
+                last_balance := last_balance - ( OPR.Amount + OPR.Fee );
+              end else OPR.Balance := -1; // Undetermined
               if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
               if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
                 OperationsResume.Add(OPR);
                 OperationsResume.Add(OPR);
               end;
               end;
               inc(nOpsCounter);
               inc(nOpsCounter);
+              found_in_block := True;
             end;
             end;
           end;
           end;
+
           // Is a new block operation?
           // Is a new block operation?
           if (TAccountComp.AccountBlock(account_number)=block_number) And ((account_number MOD CT_AccountsPerBlock)=0) then begin
           if (TAccountComp.AccountBlock(account_number)=block_number) And ((account_number MOD CT_AccountsPerBlock)=0) then begin
             OPR := CT_TOperationResume_NUL;
             OPR := CT_TOperationResume_NUL;
@@ -750,17 +752,24 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
             OPR.AffectedAccount := account_number;
             OPR.AffectedAccount := account_number;
             OPR.Amount := opc.OperationBlock.reward;
             OPR.Amount := opc.OperationBlock.reward;
             OPR.Fee := opc.OperationBlock.fee;
             OPR.Fee := opc.OperationBlock.fee;
-            OPR.Balance := last_balance;
+            If last_balance>=0 then begin
+              OPR.Balance := last_balance;
+            end else OPR.Balance := -1; // Undetermined
             OPR.OperationTxt := 'Blockchain reward';
             OPR.OperationTxt := 'Blockchain reward';
             if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
             if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
               OperationsResume.Add(OPR);
               OperationsResume.Add(OPR);
             end;
             end;
             inc(nOpsCounter);
             inc(nOpsCounter);
+            found_in_block := True;
           end;
           end;
           //
           //
-          opc.Clear(true);
           dec(act_depth);
           dec(act_depth);
-          block_number := next_block_number;
+          If (Not found_in_block) And (first_block_is_unknown) then begin
+            Dec(block_number);
+          end else begin
+            block_number := opc.PreviousUpdatedBlocks.GetPreviousUpdatedBlock(account_number,block_number);
+          end;
+          opc.Clear(true);
         end;
         end;
       finally
       finally
         l.Free;
         l.Free;
@@ -771,12 +780,23 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
   end;
   end;
 
 
 Var acc : TAccount;
 Var acc : TAccount;
+  startBlock : Cardinal;
+  lastBalance : Int64;
 begin
 begin
   if MaxDepth<0 then Exit;
   if MaxDepth<0 then Exit;
   if account_number>=Bank.SafeBox.AccountsCount then Exit;
   if account_number>=Bank.SafeBox.AccountsCount then Exit;
   if StartOperation>EndOperation then Exit;
   if StartOperation>EndOperation then Exit;
   acc := Bank.SafeBox.Account(account_number);
   acc := Bank.SafeBox.Account(account_number);
-  if (acc.updated_block>0) Or (acc.account=0) then DoGetFromBlock(acc.updated_block,acc.balance,MaxDepth,0);
+  if (acc.updated_block>0) Or (acc.account=0) then Begin
+    if (SearchBackwardsStartingAtBlock=0) Or (SearchBackwardsStartingAtBlock>=acc.updated_block) then begin
+      startBlock := acc.updated_block;
+      lastBalance := acc.balance;
+    end else begin
+      startBlock := SearchBackwardsStartingAtBlock;
+      lastBalance := -1;
+    end;
+    DoGetFromBlock(startBlock,lastBalance,MaxDepth,0,startBlock<>acc.updated_block);
+  end;
 end;
 end;
 
 
 function TNode.FindNOperation(block, account, n_operation: Cardinal;
 function TNode.FindNOperation(block, account, n_operation: Cardinal;
@@ -817,8 +837,8 @@ begin
     Try
     Try
       For i:=Operations.Count-1 downto 0 do begin
       For i:=Operations.Count-1 downto 0 do begin
         op := Operations.Operation[i];
         op := Operations.Operation[i];
-        If (op.SignerAccount=account) then begin
-          If (op.N_Operation<=n_operation) then begin
+        If (op.IsSignerAccount(account)) then begin
+          If (op.GetAccountN_Operation(account)<=n_operation) then begin
             TPCOperation.OperationToOperationResume(0,op,account,opr);
             TPCOperation.OperationToOperationResume(0,op,account,opr);
             opr.Balance:=-1;
             opr.Balance:=-1;
             OpResumeList.Add(opr);
             OpResumeList.Add(opr);
@@ -843,10 +863,10 @@ begin
       end;
       end;
       For i:=OperationComp.Count-1 downto 0 do begin
       For i:=OperationComp.Count-1 downto 0 do begin
         op := OperationComp.Operation[i];
         op := OperationComp.Operation[i];
-        if (op.SignerAccount=account) then begin
-          If (n_operation_high=n_operation_low) and (op.N_Operation=n_operation) // If searchin only 1 n_operation, n_operation must match
+        if (op.IsSignerAccount(account)) then begin
+          If (n_operation_high=n_operation_low) and (op.GetAccountN_Operation(account)=n_operation) // If searching only 1 n_operation, n_operation must match
             Or
             Or
-            (n_operation_high>n_operation_low) and (op.N_Operation<=n_operation) and (op.N_Operation>=n_operation_low) and (op.N_Operation<=n_operation_high) then begin
+            (n_operation_high>n_operation_low) and (op.GetAccountN_Operation(account)<=n_operation) and (op.GetAccountN_Operation(account)>=n_operation_low) and (op.GetAccountN_Operation(account)<=n_operation_high) then begin
             TPCOperation.OperationToOperationResume(block,op,account,opr);
             TPCOperation.OperationToOperationResume(block,op,account,opr);
             opr.time:=Bank.SafeBox.Block(block).blockchainInfo.timestamp;
             opr.time:=Bank.SafeBox.Block(block).blockchainInfo.timestamp;
             opr.NOpInsideBlock:=i;
             opr.NOpInsideBlock:=i;
@@ -858,17 +878,13 @@ begin
               Exit;
               Exit;
             end;
             end;
           end else begin
           end else begin
-            If (op.N_Operation < n_operation) then begin
+            If (op.GetAccountN_Operation(account) < n_operation) then begin
               If (n_operation_high>n_operation_low) then Result := found; // multiple search, result is found (not an error)
               If (n_operation_high>n_operation_low) then Result := found; // multiple search, result is found (not an error)
               Exit // First occurrence is lower
               Exit // First occurrence is lower
             end;
             end;
           end;
           end;
-          block := op.Previous_Signer_updated_block;
-        end else if op.DestinationAccount=account then begin
-          block := op.Previous_Destination_updated_block;
-        end else if op.SellerAccount=account then begin
-          block := op.Previous_Seller_updated_block;
         end;
         end;
+        block := OperationComp.PreviousUpdatedBlocks.GetPreviousUpdatedBlock(account,block);
       end;
       end;
       if (block>aux_block) then exit // Error... not found a valid block positioning
       if (block>aux_block) then exit // Error... not found a valid block positioning
       else if (block=aux_block) then begin
       else if (block=aux_block) then begin
@@ -908,7 +924,7 @@ function TNode.FindOperationExt(const OperationComp: TPCOperationsComp;
 var account,n_operation : Cardinal;
 var account,n_operation : Cardinal;
   i : Integer;
   i : Integer;
   op : TPCOperation;
   op : TPCOperation;
-  initial_block, aux_block : Cardinal;
+  initial_block, aux_block, aux_n_op : Cardinal;
   opHashValid, opHash_OLD : TRawBytes;
   opHashValid, opHash_OLD : TRawBytes;
   md160 : TRawBytes;
   md160 : TRawBytes;
 begin
 begin
@@ -953,9 +969,10 @@ begin
     end;
     end;
     For i:=OperationComp.Count-1 downto 0 do begin
     For i:=OperationComp.Count-1 downto 0 do begin
       op := OperationComp.Operation[i];
       op := OperationComp.Operation[i];
-      if (op.SignerAccount=account) then begin
-        If (op.N_Operation<n_operation) then exit; // n_operation is greaten than found
-        If (op.N_Operation=n_operation) then begin
+      if (op.IsSignerAccount(account)) then begin
+        aux_n_op := op.GetAccountN_Operation(account);
+        If (aux_n_op<n_operation) then exit; // n_operation is greaten than found
+        If (aux_n_op=n_operation) then begin
           // Possible candidate or dead
           // Possible candidate or dead
           opHashValid := TPCOperation.OperationHashValid(op,initial_block);
           opHashValid := TPCOperation.OperationHashValid(op,initial_block);
           If (opHashValid=OperationHash) then begin
           If (opHashValid=OperationHash) then begin
@@ -971,16 +988,9 @@ begin
             end else exit; // Not found!
             end else exit; // Not found!
           end else exit; // Not found!
           end else exit; // Not found!
         end;
         end;
-        If op.Previous_Signer_updated_block>block then exit;
-        block := op.Previous_Signer_updated_block;
-      end else if op.DestinationAccount=account then begin
-        If op.Previous_Destination_updated_block > block then exit;
-        block := op.Previous_Destination_updated_block;
-      end else if op.SellerAccount=account then begin
-        If op.Previous_Seller_updated_block > block then exit;
-        block := op.Previous_Seller_updated_block;
       end;
       end;
     end;
     end;
+    block := OperationComp.PreviousUpdatedBlocks.GetPreviousUpdatedBlock(account,block);
     if (block>=aux_block) then exit; // Error... not found a valid block positioning
     if (block>=aux_block) then exit; // Error... not found a valid block positioning
     if (initial_block<>0) then exit; // If not found in specified block, no valid hash
     if (initial_block<>0) then exit; // If not found in specified block, no valid hash
   end;
   end;

+ 18 - 16
src/core/UOpTransaction.pas

@@ -75,7 +75,7 @@ Type
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
   public
   public
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     procedure AffectedAccounts(list : TList); override;
     procedure AffectedAccounts(list : TList); override;
     //
     //
     Class Function GetTransactionHashToSign(const trans : TOpTransactionData) : TRawBytes;
     Class Function GetTransactionHashToSign(const trans : TOpTransactionData) : TRawBytes;
@@ -109,7 +109,7 @@ Type
     class function OpType : Byte; override;
     class function OpType : Byte; override;
 
 
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     function OperationAmount : Int64; override;
     function OperationAmount : Int64; override;
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
@@ -143,7 +143,7 @@ Type
     class function OpType : Byte; override;
     class function OpType : Byte; override;
 
 
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     function OperationAmount : Int64; override;
     function OperationAmount : Int64; override;
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
@@ -211,7 +211,7 @@ Type
     Function IsDelist : Boolean; virtual; abstract;
     Function IsDelist : Boolean; virtual; abstract;
 
 
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     function OperationAmount : Int64; override;
     function OperationAmount : Int64; override;
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
@@ -263,7 +263,7 @@ Type
     class function OpType : Byte; override;
     class function OpType : Byte; override;
 
 
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     function OperationAmount : Int64; override;
     function OperationAmount : Int64; override;
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
@@ -416,7 +416,7 @@ begin
   Result:=inherited GetBufferForOpHash(True);
   Result:=inherited GetBufferForOpHash(True);
 end;
 end;
 
 
-function TOpChangeAccountInfo.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+function TOpChangeAccountInfo.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 Var account_signer, account_target : TAccount;
 Var account_signer, account_target : TAccount;
 begin
 begin
   Result := false;
   Result := false;
@@ -529,7 +529,8 @@ begin
   If (account_type in FData.changes_type) then begin
   If (account_type in FData.changes_type) then begin
     account_target.account_type := FData.new_type;
     account_target.account_type := FData.new_type;
   end;
   end;
-  Result := AccountTransaction.UpdateAccountInfo(FData.account_signer,FData.n_operation,FData.account_target,
+  Result := AccountTransaction.UpdateAccountInfo(AccountPreviousUpdatedBlock,
+         FData.account_signer,FData.n_operation,FData.account_target,
          account_target.accountInfo,
          account_target.accountInfo,
          account_target.name,
          account_target.name,
          account_target.account_type,
          account_target.account_type,
@@ -659,7 +660,7 @@ begin
   FSignatureChecked:=True;
   FSignatureChecked:=True;
 end;
 end;
 
 
-function TOpTransaction.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString): Boolean;
+function TOpTransaction.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 Var s_new, t_new : Int64;
 Var s_new, t_new : Int64;
   totalamount : Cardinal;
   totalamount : Cardinal;
   sender,target,seller : TAccount;
   sender,target,seller : TAccount;
@@ -813,9 +814,9 @@ begin
       errors := 'NOT ALLOWED ON PROTOCOL 1';
       errors := 'NOT ALLOWED ON PROTOCOL 1';
       exit;
       exit;
     end;
     end;
-    Result := AccountTransaction.BuyAccount(sender.account,target.account,seller.account,FData.n_operation,FData.amount,target.accountInfo.price,FData.fee,FData.new_accountkey,errors);
+    Result := AccountTransaction.BuyAccount(AccountPreviousUpdatedBlock,sender.account,target.account,seller.account,FData.n_operation,FData.amount,target.accountInfo.price,FData.fee,FData.new_accountkey,errors);
   end else begin
   end else begin
-    Result := AccountTransaction.TransferAmount(FData.sender,FData.target,FData.n_operation,FData.amount,FData.fee,errors);
+    Result := AccountTransaction.TransferAmount(AccountPreviousUpdatedBlock,FData.sender,FData.target,FData.n_operation,FData.amount,FData.fee,errors);
   end;
   end;
 end;
 end;
 
 
@@ -1085,7 +1086,7 @@ begin
   FSignatureChecked:=True;
   FSignatureChecked:=True;
 end;
 end;
 
 
-function TOpChangeKey.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+function TOpChangeKey.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 Var account_signer, account_target : TAccount;
 Var account_signer, account_target : TAccount;
 begin
 begin
   Result := false;
   Result := false;
@@ -1189,7 +1190,8 @@ begin
   account_target.accountInfo.price := 0;
   account_target.accountInfo.price := 0;
   account_target.accountInfo.account_to_pay := 0;
   account_target.accountInfo.account_to_pay := 0;
   account_target.accountInfo.new_publicKey := CT_TECDSA_Public_Nul;
   account_target.accountInfo.new_publicKey := CT_TECDSA_Public_Nul;
-  Result := AccountTransaction.UpdateAccountInfo(FData.account_signer,FData.n_operation,FData.account_target,
+  Result := AccountTransaction.UpdateAccountInfo(AccountPreviousUpdatedBlock,
+         FData.account_signer,FData.n_operation,FData.account_target,
          account_target.accountInfo,
          account_target.accountInfo,
          account_target.name,
          account_target.name,
          account_target.account_type,
          account_target.account_type,
@@ -1393,7 +1395,7 @@ begin
   FSignatureChecked := True;
   FSignatureChecked := True;
 end;
 end;
 
 
-function TOpRecoverFounds.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+function TOpRecoverFounds.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 Var acc : TAccount;
 Var acc : TAccount;
 begin
 begin
   Result := false;
   Result := false;
@@ -1424,7 +1426,7 @@ begin
     exit;
     exit;
   end;
   end;
   FPrevious_Signer_updated_block := acc.updated_block;
   FPrevious_Signer_updated_block := acc.updated_block;
-  Result := AccountTransaction.TransferAmount(FData.account,FData.account,FData.n_operation,0,FData.fee,errors);
+  Result := AccountTransaction.TransferAmount(AccountPreviousUpdatedBlock,FData.account,FData.account,FData.n_operation,0,FData.fee,errors);
 end;
 end;
 
 
 function TOpRecoverFounds.GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes;
 function TOpRecoverFounds.GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes;
@@ -1516,7 +1518,7 @@ begin
     list.Add(TObject(FData.account_target));
     list.Add(TObject(FData.account_target));
 end;
 end;
 
 
-function TOpListAccount.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 Var account_signer, account_target : TAccount;
 Var account_signer, account_target : TAccount;
 begin
 begin
   Result := false;
   Result := false;
@@ -1649,7 +1651,7 @@ begin
     account_target.accountInfo.account_to_pay := FData.account_to_pay;
     account_target.accountInfo.account_to_pay := FData.account_to_pay;
     account_target.accountInfo.new_publicKey := FData.new_public_key;
     account_target.accountInfo.new_publicKey := FData.new_public_key;
   end;
   end;
-  Result := AccountTransaction.UpdateAccountInfo(FData.account_signer,FData.n_operation,FData.account_target,
+  Result := AccountTransaction.UpdateAccountInfo(AccountPreviousUpdatedBlock,FData.account_signer,FData.n_operation,FData.account_target,
          account_target.accountInfo,
          account_target.accountInfo,
          account_target.name,
          account_target.name,
          account_target.account_type,
          account_target.account_type,

+ 56 - 11
src/core/URPC.pas

@@ -435,7 +435,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       ms.Position := 0;
       ms.Position := 0;
       OperationsHashTree := TOperationsHashTree.Create;
       OperationsHashTree := TOperationsHashTree.Create;
       if (raw<>'') then begin
       if (raw<>'') then begin
-        If not OperationsHashTree.LoadOperationsHashTreeFromStream(ms,false,false,errors) then begin
+        If not OperationsHashTree.LoadOperationsHashTreeFromStream(ms,false,CT_PROTOCOL_1,Nil,errors) then begin
           FreeAndNil(OperationsHashTree);
           FreeAndNil(OperationsHashTree);
           exit;
           exit;
         end;
         end;
@@ -501,6 +501,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
   end;
   end;
 
 
   Procedure FillOperationResumeToJSONObject(Const OPR : TOperationResume; jsonObject : TPCJSONObject);
   Procedure FillOperationResumeToJSONObject(Const OPR : TOperationResume; jsonObject : TPCJSONObject);
+  Var i : Integer;
+    jsonArr : TPCJSONArray;
+    auxObj : TPCJSONObject;
   Begin
   Begin
     if Not OPR.valid then begin
     if Not OPR.valid then begin
       jsonObject.GetAsVariant('valid').Value := OPR.valid;
       jsonObject.GetAsVariant('valid').Value := OPR.valid;
@@ -518,14 +521,53 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     end;
     end;
     jsonObject.GetAsVariant('optype').Value:=OPR.OpType;
     jsonObject.GetAsVariant('optype').Value:=OPR.OpType;
     jsonObject.GetAsVariant('subtype').Value:=OPR.OpSubtype;
     jsonObject.GetAsVariant('subtype').Value:=OPR.OpSubtype;
-    jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
-    jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
-    jsonObject.GetAsVariant('n_operation').Value:=OPR.n_operation;
+    If (Not OPR.isMultiOperation) then Begin
+      jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
+      jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
+      jsonObject.GetAsVariant('n_operation').Value:=OPR.n_operation;
+    end else begin
+      jsonArr := jsonObject.GetAsArray('senders');
+      for i:=Low(OPR.senders) to High(OPR.Senders) do begin
+        auxObj := jsonArr.GetAsObject(jsonArr.Count);
+        auxObj.GetAsVariant('account').Value := OPR.Senders[i].Account;
+        auxObj.GetAsVariant('n_operation').Value := OPR.Senders[i].N_Operation;
+        auxObj.GetAsVariant('amount').Value := ToJSONCurrency(OPR.Senders[i].Amount * (-1));
+        auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(OPR.Senders[i].Payload);
+      end;
+      //
+      jsonArr := jsonObject.GetAsArray('receivers');
+      for i:=Low(OPR.Receivers) to High(OPR.Receivers) do begin
+        auxObj := jsonArr.GetAsObject(jsonArr.Count);
+        auxObj.GetAsVariant('account').Value := OPR.Receivers[i].Account;
+        auxObj.GetAsVariant('amount').Value := ToJSONCurrency(OPR.Receivers[i].Amount);
+        auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(OPR.Receivers[i].Payload);
+      end;
+      jsonArr := jsonObject.GetAsArray('changers');
+      for i:=Low(OPR.Changers) to High(OPR.Changers) do begin
+        auxObj := jsonArr.GetAsObject(jsonArr.Count);
+        auxObj.GetAsVariant('account').Value := OPR.Changers[i].Account;
+        auxObj.GetAsVariant('n_operation').Value := OPR.Changers[i].N_Operation;
+        If public_key in OPR.Changers[i].Changes_type then begin
+          auxObj.GetAsVariant('new_enc_pubkey').Value := TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(OPR.Changers[i].New_Accountkey));
+        end;
+        If account_name in OPR.Changers[i].Changes_type then begin
+          auxObj.GetAsVariant('new_name').Value := OPR.Changers[i].New_Name;
+        end;
+        If account_type in OPR.Changers[i].Changes_type then begin
+          auxObj.GetAsVariant('new_type').Value := OPR.Changers[i].New_Type;
+        end;
+      end;
+    end;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
-    jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
-    if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
-    jsonObject.GetAsVariant('payload').Value:=TCrypto.ToHexaString(OPR.OriginalPayload);
+    if (Not OPR.isMultiOperation) then begin
+      jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
+      if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
+      jsonObject.GetAsVariant('payload').Value:=TCrypto.ToHexaString(OPR.OriginalPayload);
+    end else begin
+      jsonObject.GetAsVariant('totalamount').Value:=ToJSONCurrency(OPR.Amount);
+      if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
+    end;
     If (OPR.OpType = CT_Op_Transaction) then begin
     If (OPR.OpType = CT_Op_Transaction) then begin
       If OPR.SignerAccount>=0 then begin
       If OPR.SignerAccount>=0 then begin
         jsonObject.GetAsVariant('sender_account').Value:=OPR.SignerAccount;
         jsonObject.GetAsVariant('sender_account').Value:=OPR.SignerAccount;
@@ -553,7 +595,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
     jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
   End;
   End;
 
 
-  Function GetAccountOperations(accountNumber : Cardinal; jsonArray : TPCJSONArray; maxBlocksDepth, startReg, maxReg: Integer) : Boolean;
+  Function GetAccountOperations(accountNumber : Cardinal; jsonArray : TPCJSONArray; maxBlocksDepth, startReg, maxReg: Integer; forceStartBlock : Cardinal) : Boolean;
   var list : TList;
   var list : TList;
     Op : TPCOperation;
     Op : TPCOperation;
     OPR : TOperationResume;
     OPR : TOperationResume;
@@ -570,7 +612,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     nCounter := 0;
     nCounter := 0;
     OperationsResume := TOperationsResumeList.Create;
     OperationsResume := TOperationsResumeList.Create;
     try
     try
-      if (startReg=-1) then begin
+      if ((startReg=-1) And (forceStartBlock=0)) then begin
         // 1.5.5 change: If start=-1 then will include PENDING OPERATIONS, otherwise not.
         // 1.5.5 change: If start=-1 then will include PENDING OPERATIONS, otherwise not.
         // Only will return pending operations if start=0, otherwise
         // Only will return pending operations if start=0, otherwise
         list := TList.Create;
         list := TList.Create;
@@ -594,7 +636,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       end;
       end;
       if (nCounter<maxReg) then begin
       if (nCounter<maxReg) then begin
         if (startReg<0) then startReg := 0; // Prevent -1 value
         if (startReg<0) then startReg := 0; // Prevent -1 value
-        FNode.GetStoredOperationsFromAccount(OperationsResume,accountNumber,maxBlocksDepth,startReg,startReg+maxReg-1);
+        FNode.GetStoredOperationsFromAccount(OperationsResume,accountNumber,maxBlocksDepth,startReg,startReg+maxReg-1,forceStartBlock);
       end;
       end;
       for i:=0 to OperationsResume.Count-1 do begin
       for i:=0 to OperationsResume.Count-1 do begin
         Obj := jsonArray.GetAsObject(jsonArray.Count);
         Obj := jsonArray.GetAsObject(jsonArray.Count);
@@ -2386,10 +2428,13 @@ begin
     // Param "account" contains account number
     // Param "account" contains account number
     // Param "depht" (optional or "deep") contains max blocks deep to search (Default: 100)
     // Param "depht" (optional or "deep") contains max blocks deep to search (Default: 100)
     // Param "start" and "max" contains starting index and max operations respectively
     // Param "start" and "max" contains starting index and max operations respectively
+    // Param "startblock" forces to start searching backwards on a fixed block, will not give balance for each operation due it's unknown
     c := params.GetAsVariant('account').AsCardinal(CT_MaxAccount);
     c := params.GetAsVariant('account').AsCardinal(CT_MaxAccount);
     if ((c>=0) And (c<FNode.Bank.AccountsCount)) then begin
     if ((c>=0) And (c<FNode.Bank.AccountsCount)) then begin
       if (params.IndexOfName('depth')>=0) then i := params.AsInteger('depth',100) else i:=params.AsInteger('deep',100);
       if (params.IndexOfName('depth')>=0) then i := params.AsInteger('depth',100) else i:=params.AsInteger('deep',100);
-      Result := GetAccountOperations(c,GetResultArray,i,params.AsInteger('start',0),params.AsInteger('max',100));
+      If params.IndexOfName('startblock')>=0 then c2 := params.AsCardinal('startblock',0)
+      else c2 := 0;
+      Result := GetAccountOperations(c,GetResultArray,i,params.AsInteger('start',0),params.AsInteger('max',100),c2);
     end else begin
     end else begin
       ErrorNum := CT_RPC_ErrNum_InvalidAccount;
       ErrorNum := CT_RPC_ErrNum_InvalidAccount;
       If (c=CT_MaxAccount) then ErrorDesc := 'Need account param'
       If (c=CT_MaxAccount) then ErrorDesc := 'Need account param'

+ 45 - 5
src/core/UTxMultiOperation.pas

@@ -80,6 +80,8 @@ Type
       - Account A sends X
       - Account A sends X
       - Account A changes info
       - Account A changes info
       - Not allowed (same sender and same change info account)
       - Not allowed (same sender and same change info account)
+    - When a transfer is made to an account in private sale mode, the receiver account
+      will not execute a change private key, so sale will not be executed
   }
   }
 
 
   TOpMultiOperationData = Record
   TOpMultiOperationData = Record
@@ -103,12 +105,13 @@ Type
     procedure InitializeData; override;
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
   public
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
 
 
     function CheckSignatures(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
     function CheckSignatures(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
 
 
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
     procedure AffectedAccounts(list : TList); override;
     procedure AffectedAccounts(list : TList); override;
     //
     //
     Function GetTransactionHashToSign : TRawBytes;
     Function GetTransactionHashToSign : TRawBytes;
@@ -118,9 +121,11 @@ Type
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
     function SignerAccount : Cardinal; override;
     function SignerAccount : Cardinal; override;
+    function IsSignerAccount(account : Cardinal) : Boolean; override;
     function DestinationAccount : Int64; override;
     function DestinationAccount : Int64; override;
     function SellerAccount : Int64; override;
     function SellerAccount : Int64; override;
     function N_Operation : Cardinal; override;
     function N_Operation : Cardinal; override;
+    function GetAccountN_Operation(account : Cardinal) : Cardinal; override;
     //
     //
     Constructor CreateMultiOperation(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; const changes : TMultiOpChangesInfo; const senders_keys, changes_keys: Array of TECPrivateKey);
     Constructor CreateMultiOperation(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; const changes : TMultiOpChangesInfo; const senders_keys, changes_keys: Array of TECPrivateKey);
     Destructor Destroy; override;
     Destructor Destroy; override;
@@ -166,6 +171,14 @@ begin
   Result := -1;
   Result := -1;
 end;
 end;
 
 
+procedure TOpMultiOperation.FillOperationResume(Block : Cardinal; Affected_account_number : Cardinal; var OperationResume : TOperationResume);
+begin
+  inherited FillOperationResume(Block, Affected_account_number, OperationResume);
+  OperationResume.Senders := FData.txSenders;
+  OperationResume.Receivers := FData.txReceivers;
+  OperationResume.Changers := FData.changesInfo;
+end;
+
 function TOpMultiOperation.IndexOfAccountChangeNameTo(const newName: AnsiString): Integer;
 function TOpMultiOperation.IndexOfAccountChangeNameTo(const newName: AnsiString): Integer;
 begin
 begin
   If (newName<>'') then begin
   If (newName<>'') then begin
@@ -400,7 +413,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TOpMultiOperation.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+function TOpMultiOperation.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
 var i,j : Integer;
 var i,j : Integer;
   txs : TMultiOpSender;
   txs : TMultiOpSender;
   txr : TMultiOpReceiver;
   txr : TMultiOpReceiver;
@@ -558,7 +571,8 @@ begin
     end;
     end;
   end;
   end;
   // Execute!
   // Execute!
-  If Not AccountTransaction.TransferAmounts(senders,senders_n_operation,senders_amount,
+  If Not AccountTransaction.TransferAmounts(AccountPreviousUpdatedBlock,
+    senders,senders_n_operation,senders_amount,
     receivers,receivers_amount,errors) then Begin
     receivers,receivers_amount,errors) then Begin
     TLog.NewLog(ltError,ClassName,'FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
     TLog.NewLog(ltError,ClassName,'FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
     Raise Exception.Create('FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
     Raise Exception.Create('FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
@@ -582,7 +596,9 @@ begin
     If (account_type in chi.Changes_type) then begin
     If (account_type in chi.Changes_type) then begin
       changer.account_type := chi.New_Type;
       changer.account_type := chi.New_Type;
     end;
     end;
-    If Not AccountTransaction.UpdateAccountInfo(chi.Account,chi.N_Operation,chi.Account,
+    If Not AccountTransaction.UpdateAccountInfo(
+           AccountPreviousUpdatedBlock,
+           chi.Account,chi.N_Operation,chi.Account,
            changer.accountInfo,
            changer.accountInfo,
            changer.name,
            changer.name,
            changer.account_type,
            changer.account_type,
@@ -695,9 +711,16 @@ function TOpMultiOperation.SignerAccount: Cardinal;
 begin
 begin
   // On a multioperation, the signer account are senders N accounts, cannot verify which one is correct... will send first one
   // On a multioperation, the signer account are senders N accounts, cannot verify which one is correct... will send first one
   If length(FData.txSenders)>0 then Result := FData.txSenders[0].Account
   If length(FData.txSenders)>0 then Result := FData.txSenders[0].Account
+  else if (length(FData.changesInfo)>0) then Result := FData.changesInfo[0].Account
   else Result := MaxInt;
   else Result := MaxInt;
 end;
 end;
 
 
+function TOpMultiOperation.IsSignerAccount(account: Cardinal): Boolean;
+begin
+  // This function will override previous due it can be Multi signed
+  Result := (IndexOfAccountSender(account)>=0) Or (IndexOfAccountChanger(account)>=0);
+end;
+
 function TOpMultiOperation.DestinationAccount: Int64;
 function TOpMultiOperation.DestinationAccount: Int64;
 begin
 begin
   Result:=inherited DestinationAccount;
   Result:=inherited DestinationAccount;
@@ -710,10 +733,27 @@ end;
 
 
 function TOpMultiOperation.N_Operation: Cardinal;
 function TOpMultiOperation.N_Operation: Cardinal;
 begin
 begin
-  // On a multitoperation, there are senders N accounts, need specify
+  // On a multitoperation, there are N signers, need specify
   Result := 0;  // Note: N_Operation = 0 means NO OPERATION
   Result := 0;  // Note: N_Operation = 0 means NO OPERATION
 end;
 end;
 
 
+function TOpMultiOperation.GetAccountN_Operation(account: Cardinal): Cardinal;
+var i : Integer;
+begin
+  // On a multitoperation, there are N signers
+  i := IndexOfAccountSender(account);
+  If (i>=0) then begin
+    Result := FData.txSenders[i].N_Operation;
+    Exit;
+  end;
+  i := IndexOfAccountChanger(account);
+  If (i>=0) then begin
+    Result := FData.changesInfo[i].N_Operation;
+    Exit;
+  end;
+  Result := 0;
+end;
+
 constructor TOpMultiOperation.CreateMultiOperation(
 constructor TOpMultiOperation.CreateMultiOperation(
   const senders: TMultiOpSenders; const receivers: TMultiOpReceivers;
   const senders: TMultiOpSenders; const receivers: TMultiOpReceivers;
   const changes: TMultiOpChangesInfo; const senders_keys,
   const changes: TMultiOpChangesInfo; const senders_keys,