Эх сурвалжийг харах

PIP-0017 basic implementation

Describes how will work PIP-0017, not integrated in core yet
PascalCoin 7 жил өмнө
parent
commit
0c9c5165b0

+ 8 - 2
src/core/UBlockChain.pas

@@ -20,7 +20,7 @@ unit UBlockChain;
 interface
 interface
 
 
 uses
 uses
-  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs;
+  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs, UtxMultiOperation;
 {$I config.inc}
 {$I config.inc}
 
 
 {
 {
@@ -133,6 +133,9 @@ Type
     OperationHash : TRawBytes;
     OperationHash : TRawBytes;
     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
+    Senders : TAccountsTxInfoArray;
+    Receivers : TAccountsTxInfoArray;
   end;
   end;
 
 
   TOperationsResumeList = Class
   TOperationsResumeList = Class
@@ -423,7 +426,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:'');
+  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);
 
 
 implementation
 implementation
 
 
@@ -2446,6 +2449,9 @@ begin
       OperationResume.OperationTxt:= 'Changed '+s+' of account '+TAccountComp.AccountNumberToAccountTxtNumber(Operation.DestinationAccount);
       OperationResume.OperationTxt:= 'Changed '+s+' of account '+TAccountComp.AccountNumberToAccountTxtNumber(Operation.DestinationAccount);
       OperationResume.OpSubtype:=CT_OpSubtype_ChangeAccountInfo;
       OperationResume.OpSubtype:=CT_OpSubtype_ChangeAccountInfo;
       Result := True;
       Result := True;
+    end;
+    CT_Op_MultiTransaction : Begin
+
     end
     end
   else Exit;
   else Exit;
   end;
   end;

+ 2 - 0
src/core/UConst.pas

@@ -122,6 +122,8 @@ Const
   CT_Op_BuyAccount = $06;
   CT_Op_BuyAccount = $06;
   CT_Op_ChangeKeySigned = $07;
   CT_Op_ChangeKeySigned = $07;
   CT_Op_ChangeAccountInfo = $08;
   CT_Op_ChangeAccountInfo = $08;
+  // Protocol 3 new operations
+  CT_Op_MultiTransaction = $09;  // PIP-0017
 
 
   CT_PseudoOpSubtype_Miner                = 1;
   CT_PseudoOpSubtype_Miner                = 1;
   CT_PseudoOpSubtype_Developer            = 2;
   CT_PseudoOpSubtype_Developer            = 2;

+ 234 - 1
src/core/UOpTransaction.pas

@@ -19,7 +19,7 @@ unit UOpTransaction;
 
 
 interface
 interface
 
 
-Uses UCrypto, UBlockChain, Classes, UAccounts;
+Uses UCrypto, UBlockChain, Classes, UAccounts, UTxMultiOperation;
 
 
 Type
 Type
   // Operations Type
   // Operations Type
@@ -283,6 +283,45 @@ Type
     Function toString : String; Override;
     Function toString : String; Override;
   End;
   End;
 
 
+  // NEW OPERATIONS PROTOCOL 3
+
+  { TOpMultiTransaction }
+  // PIP-0017
+
+  TOpMultiTransactionData = Record
+    senders: TTxInfoSender;
+    receivers: TTxInfoReceiver;
+  end;
+
+  TOpMultiTransaction = Class(TPCOperation)
+  private
+    FData : TOpMultiTransactionData;
+  protected
+    procedure InitializeData; override;
+    function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
+    function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+  public
+    function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
+    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    procedure AffectedAccounts(list : TList); override;
+    //
+    Class Function GetTransactionHashToSign(const multitrans : TOpMultiTransactionData) : TRawBytes;
+    Class Function DoSignMultiTransactionSigner(SignerAccount : Cardinal; key : TECPrivateKey; var trans : TOpMultiTransactionData) : Integer;
+    class function OpType : Byte; override;
+    function OperationAmount : Int64; override;
+    function OperationFee : UInt64; override;
+    function OperationPayload : TRawBytes; override;
+    function SignerAccount : Cardinal; override;
+    function DestinationAccount : Int64; override;
+    function SellerAccount : Int64; override;
+    function N_Operation : Cardinal; override;
+    Property Data : TOpMultiTransactionData read FData;
+    //
+    Constructor CreateMultiTransaction(const senders, receivers : TAccountsTxInfoArray; senders_keys: Array of TECPrivateKey);
+    Destructor Destroy; override;
+    Function toString : String; Override;
+  End;
+
 
 
 Procedure RegisterOperationsClass;
 Procedure RegisterOperationsClass;
 
 
@@ -303,6 +342,200 @@ Begin
   TPCOperationsComp.RegisterOperationClass(TOpChangeAccountInfo);
   TPCOperationsComp.RegisterOperationClass(TOpChangeAccountInfo);
 End;
 End;
 
 
+{ TOpMultiTransaction }
+
+procedure TOpMultiTransaction.InitializeData;
+begin
+  inherited InitializeData;
+  FData.receivers := TTxInfoReceiver.Create;
+  FData.senders := TTxInfoSender.Create;
+end;
+
+function TOpMultiTransaction.SaveOpToStream(Stream: TStream; SaveExtendedData: Boolean): Boolean;
+begin
+  FData.senders.SaveToStream(Stream);
+  FData.receivers.SaveToStream(Stream);
+  Result := true;
+end;
+
+function TOpMultiTransaction.LoadOpFromStream(Stream: TStream; LoadExtendedData: Boolean): Boolean;
+var b : Byte;
+begin
+  Result := False;
+  Try
+    FData.senders.LoadFromStream(Stream);
+    FData.receivers.LoadFromStream(Stream);
+    Result := True;
+  Except
+    On E:Exception do begin
+      TLog.NewLog(lterror,Self.ClassName,'('+E.ClassName+'):'+E.Message);
+    end;
+  end;
+end;
+
+function TOpMultiTransaction.GetBufferForOpHash(UseProtocolV2: Boolean): TRawBytes;
+begin
+  Result:=inherited GetBufferForOpHash(UseProtocolV2);
+end;
+
+function TOpMultiTransaction.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+begin
+  // TODO
+  { XXXXXXXXXXXXXXXXXXXXXXXXXX
+
+  Implementation as expected and explained at PIP-0017
+
+  Note: I've added "payload", that must be checked too
+
+  }
+  Raise Exception.Create('NOT IMPLEMENTED ERROR DEV 20180308-1');
+  Result := False;
+end;
+
+procedure TOpMultiTransaction.AffectedAccounts(list: TList);
+Var i : Integer;
+  Procedure _doAdd(nAcc : Cardinal);
+  Begin
+    If list.IndexOf(TObject(nAcc))<0 then list.Add(TObject(nAcc));
+  end;
+begin
+  For i:=0 to FData.senders.Count-1 do begin
+    _doAdd(FData.senders.AccountTxInfo[i].Account);
+  end;
+  For i:=0 to FData.receivers.Count-1 do begin
+    _doAdd(FData.senders.AccountTxInfo[i].Account);
+  end;
+end;
+
+class function TOpMultiTransaction.GetTransactionHashToSign(const multitrans: TOpMultiTransactionData): TRawBytes;
+Var ms : TMemoryStream;
+  rb : TRawBytes;
+begin
+  ms := TMemoryStream.Create;
+  try
+    rb := multitrans.senders.GetHash;
+    if (length(rb)>0) then ms.Write(rb[1],length(rb));
+    rb := multitrans.receivers.GetHash;
+    if (length(rb)>0) then ms.Write(rb[1],length(rb));
+    SetLength(Result,ms.Size);
+    ms.Position := 0;
+    ms.ReadBuffer(Result[1],ms.Size);
+  finally
+    ms.Free;
+  end;
+end;
+
+class function TOpMultiTransaction.DoSignMultiTransactionSigner(SignerAccount : Cardinal; key : TECPrivateKey; var trans : TOpMultiTransactionData) : Integer;
+Var i : Integer;
+var raw : TRawBytes;
+  _sign : TECDSA_SIG;
+begin
+  Result := 0;
+  If Not Assigned(key.PrivateKey) then begin
+    exit;
+  end;
+  raw := GetTransactionHashToSign(trans);
+  Try
+    _sign := TCrypto.ECDSASign(key.PrivateKey,raw);
+  Except
+    On E:Exception do begin
+      TLog.NewLog(ltError,ClassName,'Error signing ('+E.ClassName+') '+E.Message);
+      Exit;
+    end;
+  End;
+  Result := trans.senders.SetSignatureForAccount(SignerAccount,_sign);
+end;
+
+class function TOpMultiTransaction.OpType: Byte;
+begin
+  Result := CT_Op_MultiTransaction;
+end;
+
+function TOpMultiTransaction.OperationAmount: Int64;
+begin
+  Result := FData.senders.TotalAmount;
+end;
+
+function TOpMultiTransaction.OperationFee: UInt64;
+begin
+  Result := FData.senders.TotalFees;
+end;
+
+function TOpMultiTransaction.OperationPayload: TRawBytes;
+begin
+  Result := '';
+end;
+
+function TOpMultiTransaction.SignerAccount: Cardinal;
+begin
+  // On a multitransaction, the signer account are senders N accounts, cannot verify which one is correct... will send first one
+  If FData.senders.Count>0 then Result := FData.senders.AccountTxInfo[0].Account
+  else Result := MaxInt;
+end;
+
+function TOpMultiTransaction.DestinationAccount: Int64;
+begin
+  Result:=inherited DestinationAccount;
+end;
+
+function TOpMultiTransaction.SellerAccount: Int64;
+begin
+  Result:=inherited SellerAccount;
+end;
+
+function TOpMultiTransaction.N_Operation: Cardinal;
+begin
+  // On a multitransaction, there are senders N accounts, need specify
+  Result := 0;  // Note: N_Operation = 0 means NO OPERATION
+end;
+
+constructor TOpMultiTransaction.CreateMultiTransaction(const senders, receivers: TAccountsTxInfoArray; senders_keys: array of TECPrivateKey);
+Var i : Integer;
+begin
+  inherited Create;
+  FData.senders.Disable;
+  Try
+    For i:=low(senders) to high(senders) do begin
+      FData.senders.AddSender(senders[i].Account,senders[i].Amount,senders[i].Fee,senders[i].N_Operation,senders[i].Payload);
+    end;
+  finally
+    FData.senders.Enable;
+  end;
+  FData.receivers.Disable;
+  Try
+    For i:=low(receivers) to high(receivers) do begin
+      FData.receivers.AddReceiver(receivers[i].Account,receivers[i].Amount,receivers[i].Payload);
+    end;
+  finally
+    FData.receivers.Enable;
+  end;
+  FSignatureChecked:=True;
+  FHasValidSignature:=False;
+  If (length(senders_keys)<>length(senders)) then exit; // Cannot sign!
+  For i:=low(senders) to high(senders) do begin
+    If DoSignMultiTransactionSigner(senders[i].Account,senders_keys[i],FData)=0 then begin
+      TLog.NewLog(lterror,Classname,'Error signing a new MultiTransaction operation');
+      Exit;
+    end;
+  end;
+  FHasValidSignature:=True;
+end;
+
+destructor TOpMultiTransaction.Destroy;
+begin
+  FreeAndNil(FData.senders);
+  FreeAndNil(FData.receivers);
+  inherited Destroy;
+end;
+
+function TOpMultiTransaction.toString: String;
+begin
+  Result := Format('Multitransaction senders %s receivers %s Amount:%s Fees:%s',
+    [FData.senders.toString,FData.receivers.toString,
+     TAccountComp.FormatMoney(FData.senders.TotalAmount),
+     TAccountComp.FormatMoney(FData.senders.TotalFees)]);
+end;
+
 { TOpChangeAccountInfo }
 { TOpChangeAccountInfo }
 
 
 procedure TOpChangeAccountInfo.InitializeData;
 procedure TOpChangeAccountInfo.InitializeData;

+ 381 - 0
src/core/UTxMultiOperation.pas

@@ -0,0 +1,381 @@
+unit UtxMultiOperation;
+
+{$IFDEF FPC}
+  {$mode delphi}
+{$ENDIF}
+
+{ Copyright (c) 2018 by Albert Molina - PascalCoin developers
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  This unit is a part of Pascal Coin, a P2P crypto currency without need of
+  historical operations.
+
+  If you like it, consider a donation using BitCoin:
+  16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
+
+  }
+
+interface
+
+uses
+  Classes, SysUtils, UCrypto;
+
+Type
+  TAccountTxInfo = Record
+    Account : Cardinal;
+    Amount : Int64;
+    Fee : Int64;               // Not used if AffectedAccount is the receiver
+    N_Operation : Cardinal;    // Not used if AffectedAccount is the receiver
+    Payload : TRawBytes;
+    Signature : TECDSA_SIG;    // Not used if AffectedAccount is the receiver
+  end;
+
+  TAccountsTxInfoArray = Array of TAccountTxInfo;
+
+  { TTxInfo }
+
+  TTxInfo = Class
+  private
+    FAccountTxInfo : Array of TAccountTxInfo;
+    FDisableds : Integer;
+    FNeedRecalc : Boolean;
+    Procedure Recalc;
+    function GetAccounTxInfo(index : Integer): TAccountTxInfo;
+    function GetCount: Integer;
+  protected
+    FHash : TRawBytes;
+    FTotalAmount : Int64;
+    FTotalFees : Int64;
+    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); virtual; abstract;
+    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); virtual; abstract;
+    Procedure InternalRecalc;
+  public
+    Constructor Create;
+    Destructor Destroy; override;
+    procedure LoadFromStream(stream : TStream);
+    procedure SaveToStream(stream : TStream);
+    Property Count : Integer read GetCount;
+    Function IndexOf(searchAccount : Cardinal) : Integer;
+    Procedure DeleteAccount(Account : Integer);
+    Procedure Disable;
+    Procedure Enable;
+    Property AccountTxInfo[index : Integer] : TAccountTxInfo read GetAccounTxInfo;
+    Procedure Clear;
+    Function GetHash : TRawBytes;
+    Function TotalAmount : Int64;
+    Function TotalFees : Int64;
+    Procedure ToArray(var txArray : TAccountsTxInfoArray);
+  end;
+
+  { TTxInfoSender }
+
+  TTxInfoSender = Class(TTxInfo)
+  protected
+    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); override;
+    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); override;
+  public
+    Procedure AddSender(SenderAccount : Cardinal; Amount : Int64; Fee : Int64; N_Operation : Cardinal; Payload : TRawBytes);
+    Function SetSignatureForAccount(SignerAccount : Cardinal; const SignatureValue : TECDSA_SIG) : Integer;
+    Function toString : String; Override;
+  end;
+
+  { TTxInfoReceiver }
+
+  TTxInfoReceiver = Class(TTxInfo)
+  protected
+    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); override;
+    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); override;
+  public
+    Procedure AddReceiver(DestinationAccount : Cardinal; Amount : Int64; Payload : TRawBytes);
+    Function toString : String; Override;
+  end;
+
+Const
+  CT_TAccountTxInfo_NULL : TAccountTxInfo = (Account:0;Amount:0;Fee:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
+
+implementation
+
+Uses UAccounts;
+
+{ TTxInfoReceiver }
+
+procedure TTxInfoReceiver.InternalLoadTxFromStream(stream: TStream; var tx: TAccountTxInfo);
+begin
+  tx := CT_TAccountTxInfo_NULL;
+  // Note: Receiver will read ONLY account, amount and Payload (not fee, not N_Operation nor signature...)
+  stream.Read(tx.Account,SizeOf(tx.Account));
+  stream.Read(tx.Amount,SizeOf(tx.Amount));
+  TStreamOp.ReadAnsiString(stream,tx.Payload);
+end;
+
+procedure TTxInfoReceiver.InternalSaveTxToStream(stream: TStream; const tx: TAccountTxInfo);
+begin
+  stream.Write(tx.Account,SizeOf(tx.Account));
+  stream.Write(tx.Amount,SizeOf(tx.Amount));
+  TStreamOp.WriteAnsiString(stream,tx.Payload);
+end;
+
+procedure TTxInfoReceiver.AddReceiver(DestinationAccount: Cardinal; Amount: Int64; Payload : TRawBytes);
+var i : Integer;
+begin
+  i := IndexOf(DestinationAccount);
+  If i>=0 then Raise Exception.Create(Format('Cannot add Destination Account %d (found at pos %d)',[DestinationAccount,i]));
+  i := length(FAccountTxInfo);
+  SetLength(FAccountTxInfo,i+1);
+  FAccountTxInfo[i] := CT_TAccountTxInfo_NULL;
+  FAccountTxInfo[i].Account:= DestinationAccount;
+  FAccountTxInfo[i].Amount:= Amount;
+  FAccountTxInfo[i].Payload:= Payload;
+  Recalc;
+end;
+
+function TTxInfoReceiver.toString: String;
+Var i : Integer;
+begin
+  Result := '';
+  for i:=0 to Count-1 do begin
+    Result := Result + Format('%d:(%s,%s)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(AccountTxInfo[i].Account),
+      TAccountComp.FormatMoney(AccountTxInfo[i].Amount)]);
+  end;
+end;
+
+{ TTxInfoSender }
+
+procedure TTxInfoSender.InternalLoadTxFromStream(stream: TStream; var tx : TAccountTxInfo);
+begin
+  tx := CT_TAccountTxInfo_NULL;
+  stream.Read(tx.Account,SizeOf(tx.Account));
+  stream.Read(tx.Amount,SizeOf(tx.Amount));
+  stream.Read(tx.Fee,SizeOf(tx.Fee));
+  stream.Read(tx.N_Operation,SizeOf(tx.N_Operation));
+  TStreamOp.ReadAnsiString(stream,tx.Payload);
+  TStreamOp.ReadAnsiString(stream,tx.Signature.r);
+  TStreamOp.ReadAnsiString(stream,tx.Signature.s);
+end;
+
+procedure TTxInfoSender.InternalSaveTxToStream(stream: TStream; const tx : TAccountTxInfo);
+begin
+  stream.Write(tx.Account,SizeOf(tx.Account));
+  stream.Write(tx.Amount,SizeOf(tx.Amount));
+  stream.Write(tx.Fee,SizeOf(tx.Fee));
+  stream.Write(tx.N_Operation,SizeOf(tx.N_Operation));
+  TStreamOp.WriteAnsiString(stream,tx.Payload);
+  TStreamOp.WriteAnsiString(stream,tx.Signature.r);
+  TStreamOp.WriteAnsiString(stream,tx.Signature.s);
+end;
+
+procedure TTxInfoSender.AddSender(SenderAccount: Cardinal; Amount: Int64; Fee: Int64; N_Operation: Cardinal; Payload: TRawBytes);
+var i : Integer;
+begin
+  i := IndexOf(SenderAccount);
+  If i>=0 then Raise Exception.Create(Format('Cannot add Sender Account %d (found at pos %d)',[SenderAccount,i]));
+  i := length(FAccountTxInfo);
+  SetLength(FAccountTxInfo,i+1);
+  FAccountTxInfo[i] := CT_TAccountTxInfo_NULL;
+  FAccountTxInfo[i].Account:= SenderAccount;
+  FAccountTxInfo[i].Amount:= Amount;
+  FAccountTxInfo[i].Fee:= Fee;
+  FAccountTxInfo[i].N_Operation:= N_Operation;
+  FAccountTxInfo[i].Payload:=Payload;
+  Recalc;
+end;
+
+function TTxInfoSender.SetSignatureForAccount(SignerAccount: Cardinal; const SignatureValue: TECDSA_SIG): Integer;
+Var i : Integer;
+begin
+  Result := 0;
+  For i:=0 to Count-1 do begin
+    If (AccountTxInfo[i].Account=SignerAccount) then begin
+      FAccountTxInfo[i].Signature := SignatureValue;
+      Inc(Result);
+    end;
+  end;
+  // Note: SignatureValue has not effect on Recalc because is not hashed, so we don't need to call Recalc
+end;
+
+function TTxInfoSender.toString: String;
+Var i : Integer;
+begin
+  Result := '';
+  for i:=0 to Count-1 do begin
+    Result := Result + Format('%d:(%s,%s,%s)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(AccountTxInfo[i].Account),
+      TAccountComp.FormatMoney(AccountTxInfo[i].Amount),TAccountComp.FormatMoney(AccountTxInfo[i].Fee)]);
+  end;
+end;
+
+{ TTxInfo }
+
+procedure TTxInfo.Recalc;
+begin
+  If (FDisableds>0) then begin
+    FNeedRecalc:=True;
+    Exit;
+  end;
+  FNeedRecalc:=False;
+  If Count>0 then InternalRecalc
+  else FHash:='';
+end;
+
+function TTxInfo.GetAccounTxInfo(index : Integer): TAccountTxInfo;
+begin
+  If (index<low(FAccountTxInfo)) Or (index>High(FAccountTxInfo)) then Raise Exception.Create(ClassName+' invalid index');
+  Result := FAccountTxInfo[index];
+end;
+
+function TTxInfo.GetCount: Integer;
+begin
+  Result := Length(FAccountTxInfo);
+end;
+
+procedure TTxInfo.LoadFromStream(stream: TStream);
+Var w : Word;
+  i : Integer;
+  tx : TAccountTxInfo;
+begin
+  Clear;
+  stream.read(w,SizeOf(w));
+  SetLength(FAccountTxInfo,w);
+  If w>0 then begin
+    for i:=0 to w-1 do begin
+      InternalLoadTxFromStream(stream,tx);
+      If IndexOf(tx.Account)<0 then FAccountTxInfo[i] := tx
+      else begin
+        Clear;
+        Raise Exception.Create(Format('Error reading TTxInfo Stream. Cannot add Account %d (%d/%d)',[tx.Account,i+1,w]));
+      end;
+    end;
+  end;
+  Recalc;
+end;
+
+procedure TTxInfo.SaveToStream(stream: TStream);
+Var i : Integer;
+  w : Word;
+begin
+  w := Count;
+  stream.Write(w,Sizeof(w));
+  If w>0 then begin
+    for i:=0 to w-1 do begin
+      InternalSaveTxToStream(stream,AccountTxInfo[i]);
+    end;
+  end;
+end;
+
+procedure TTxInfo.InternalRecalc;
+Var stream : TMemoryStream;
+  i : Integer;
+  tx : TAccountTxInfo;
+begin
+  { Will SHA256( for each account (account+amount+fee+N_Operation+payload) )
+    Note: Will NOT hash the Signature value as explained at PIP-0017 in order to create
+    a OpHash that can be known prior to every account has signed }
+  If Count=0 then begin
+    FHash:='';
+    FTotalAmount:=0;
+    FTotalFees:=0;
+    Exit;
+  end;
+  stream := TMemoryStream.Create;
+  try
+    for i:=0 to Count-1 do begin
+      tx := AccountTxInfo[i];
+      stream.Write(tx.Account,SizeOf(tx.Account));
+      stream.Write(tx.Amount,SizeOf(tx.Amount));
+      stream.Write(tx.Fee,SizeOf(tx.Fee));
+      stream.Write(tx.N_Operation,SizeOf(tx.N_Operation));
+      If length(tx.Payload)>0 then stream.WriteBuffer(tx.Payload[1],Length(tx.Payload));
+      inc(FTotalAmount,tx.Amount);
+      inc(FTotalFees,tx.Fee);
+    end;
+    stream.Position:=0;
+    FHash := TCrypto.DoSha256(TMemoryStream(stream).Memory,stream.Size);
+  finally
+    stream.Free;
+  end;
+end;
+
+constructor TTxInfo.Create;
+begin
+  SetLength(FAccountTxInfo,0);
+  FDisableds := 0;
+  FNeedRecalc:=False;
+  FHash:='';
+  Clear;
+end;
+
+destructor TTxInfo.Destroy;
+begin
+  inherited Destroy;
+end;
+
+function TTxInfo.IndexOf(searchAccount: Cardinal): Integer;
+begin
+  For Result:=low(FAccountTxInfo) to high(FAccountTxInfo) do begin
+    If (FAccountTxInfo[Result].Account = searchAccount) then Exit;
+  end;
+  Result := -1;
+end;
+
+procedure TTxInfo.DeleteAccount(Account: Integer);
+Var i,j : Integer;
+begin
+  i := IndexOf(Account);
+  If i<0 then Exit;
+  For j:=i+1 to High(FAccountTxInfo) do begin
+    FAccountTxInfo[j-1] := FAccountTxInfo[j];
+  end;
+  SetLength(FAccountTxInfo,length(FAccountTxInfo)-1);
+  Recalc;
+end;
+
+procedure TTxInfo.Disable;
+begin
+  Inc(FDisableds);
+end;
+
+procedure TTxInfo.Enable;
+begin
+  If FDisableds<=0 then Raise Exception.Create('ERROR DEV 20180306-2');
+  Dec(FDisableds);
+  If (FDisableds=0) And (FNeedRecalc) then Recalc;
+end;
+
+procedure TTxInfo.Clear;
+begin
+  SetLength(FAccountTxInfo,0);
+  FHash:='';
+  FTotalAmount:=0;
+  FTotalFees:=0;
+end;
+
+function TTxInfo.GetHash: TRawBytes;
+begin
+  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-3');
+  Result := FHash;
+end;
+
+function TTxInfo.TotalAmount: Int64;
+begin
+  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-4');
+  Result := FTotalAmount;
+end;
+
+function TTxInfo.TotalFees: Int64;
+begin
+  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-5');
+  Result := FTotalFees;
+end;
+
+procedure TTxInfo.ToArray(var txArray: TAccountsTxInfoArray);
+Var i : Integer;
+begin
+  SetLength(txArray,Count);
+  For i:=0 to Count-1 do begin
+    txArray[i] := GetAccounTxInfo(i);
+  end;
+end;
+
+end.
+