Browse Source

PIP-0024 Account Data implementation

PascalCoin 6 years ago
parent
commit
a75e5e3a53

+ 10 - 1
README.md

@@ -37,12 +37,15 @@ Also, consider a donation at PascalCoin development account: "0-10"
 ### Current Build (Pending release date)
 - Upgrade to Protocol 5 (Hard fork)
 - Implementation of PIP-0032 (Atomic Swaps) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0032.md
-  - Atomic Swaps can be executed ONLY during the locking period specified
 - Implementation of PIP-0030 (Safebox root) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0030.md
 - Implementation of PIP-0029 (Account Seals) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0029.md
+- Implementation of PIP-0024 (Account Data) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0024.md
 - Updated "OP_DATA" operation: (PIP-0016)
   - New digest hash value for OP_DATA ( PIP-0016 ) on Protocol 5
   - Added "id" field (GUID/UUID type as described on PIP-0016), was missing on V4, added on V5
+- Updated "OP_CHANGE_ACCOUNT_INFO" and "OP_MULTIOPERATION" to allow Account.Data as described on PIP-0024
+  - Added "new_data" field allowing update Account.Data field (0..32 bytes)
+  - Updated digest hash value adding "new_data" field  
 - Hardcoded RandomHash digest/hash values for quick speed safebox check on fresh installation
 - JSON-RPC changes:  
   - Updated "listaccountforsale" call to allow ATOMIC SWAPS (PIP-0032)
@@ -53,6 +56,8 @@ Also, consider a donation at PascalCoin development account: "0-10"
       - "atomic_coin_swap": Need to provide a valid "enc_hash_lock"
       - If no "type" is defined, will automatically select between "public_sale" or "private_sale"
     - Added "enc_hash_lock" (HEXASTRING) that must be exactly a 32 bytes value (stored as 64 bytes because is HexaString)
+  - Updated "changeaccountinfo" and "signchangeaccountinfo" calls to allow add "new_data" field for change Account.Data value (PIP-0024)
+    - New param "new_data" (HEXASTRING) if provided will change Account Data info. Limited from 0 to 32 bytes.
   - Updated "Account Object" return values:
     - "state": Can return "normal", "listed", "account_swap", "coin_swap"
     - "hashed_secret" : (HEXASTRING) will contain the SHA256( SECRET ) value that must match Payload received data for Atomic Swaps (only when "state" in "account_swap" or "coin_swap")
@@ -67,6 +72,10 @@ Also, consider a donation at PascalCoin development account: "0-10"
     - "changers" : ARRAY
       - "new_data" : (HEXASTRING) : If "data" is changed on "account"
       - "changes" : (String) Description of changes type made
+  - Updated "Multi Operation Object" values:
+    - "changers" : ARRAY
+      - "new_data" : (HEXASTRING) : If "data" is changed on "account"
+    
 TODO  
 - TODO: RPC calls for PIP-0029
 - TODO Implement Seal calculation

+ 5 - 1
src/core/UBlockChain.pas

@@ -559,7 +559,7 @@ 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:Nil;y:Nil);OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:Nil;PrintablePayload:'';OperationHash:Nil;OperationHash_OLD:Nil;errors:'';isMultiOperation:False;Senders:Nil;Receivers:Nil;changers:Nil);
   CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:Nil;Signature:(r:Nil;s:Nil));
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:Nil);
-  CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);New_Name:Nil;New_Type:0;Seller_Account:-1;Account_Price:-1;Locked_Until_Block:0;
+  CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);New_Name:Nil;New_Type:0;New_Data:Nil;Seller_Account:-1;Account_Price:-1;Locked_Until_Block:0;
     Hashed_secret:Nil;
     Fee:0;Signature:(r:Nil;s:Nil));
   CT_TOpChangeAccountInfoType_Txt : Array[Low(TOpChangeAccountInfoType)..High(TOpChangeAccountInfoType)] of String = ('public_key','account_name','account_type','list_for_public_sale','list_for_private_sale', 'delist', 'account_data','list_for_account_swap','list_for_coin_swap');
@@ -3386,6 +3386,10 @@ begin
         if s<>'' then s:=s+',';
         s := s + 'type';
       end;
+      if (account_data in TOpChangeAccountInfo(Operation).Data.changes_type) then begin
+        if s<>'' then s:=s+',';
+        s := s + 'data';
+      end;
       OperationResume.OperationTxt:= 'Changed '+s+' of account '+TAccountComp.AccountNumberToAccountTxtNumber(Operation.DestinationAccount);
       OperationResume.OpSubtype:=CT_OpSubtype_ChangeAccountInfo;
       Result := True;

+ 43 - 4
src/core/UOpTransaction.pas

@@ -205,10 +205,11 @@ Type
     fee: UInt64;
     payload: TRawBytes;
     public_key: TECDSA_Public;
-    changes_type : TOpChangeAccountInfoTypes; // bits mask. $0001 = New account key , $0002 = New name , $0004 = New type
+    changes_type : TOpChangeAccountInfoTypes; // bits mask. $0001 = New account key , $0002 = New name , $0004 = New type , $0008 = New Data
     new_accountkey: TAccountKey;  // If (changes_mask and $0001)=$0001 then change account key
     new_name: TRawBytes;          // If (changes_mask and $0002)=$0002 then change name
     new_type: Word;               // If (changes_mask and $0004)=$0004 then change type
+    new_data: TRawBytes;          // If (changes_mask and $0008)=$0008 then change type
     sign: TECDSA_SIG;
   End;
 
@@ -217,7 +218,7 @@ Const
   CT_TOpListAccountData_NUL : TOpListAccountData = (account_signer:0;account_target:0;operation_type:lat_Unknown;n_operation:0;account_state:as_Unknown;account_price:0;account_to_pay:0;fee:0;
     hash_lock:(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);payload:Nil;public_key:(EC_OpenSSL_NID:0;x:Nil;y:Nil);new_public_key:(EC_OpenSSL_NID:0;x:Nil;y:Nil);locked_until_block:0;sign:(r:Nil;s:Nil));
   CT_TOpChangeAccountInfoData_NUL : TOpChangeAccountInfoData = (account_signer:0;account_target:0;n_operation:0;fee:0;payload:Nil;public_key:(EC_OpenSSL_NID:0;x:Nil;y:Nil);changes_type:[];
-    new_accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);new_name:Nil;new_type:0;sign:(r:Nil;s:Nil));
+    new_accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);new_name:Nil;new_type:0;new_data:Nil;sign:(r:Nil;s:Nil));
 
 Type
 
@@ -302,6 +303,7 @@ Type
       change_key : Boolean; const new_account_key : TAccountKey;
       change_name: Boolean; const new_name : TRawBytes;
       change_type: Boolean; const new_type : Word;
+      change_data: Boolean; const new_data : TRawBytes;
       fee: UInt64; payload: TRawBytes);
     Property Data : TOpChangeAccountInfoData read FData;
     Function toString : String; Override;
@@ -407,10 +409,14 @@ begin
   if (public_key in FData.changes_type) then b:=b OR $01;
   if (account_name in FData.changes_type) then b:=b OR $02;
   if (account_type in FData.changes_type) then b:=b OR $04;
+  if (account_data in FData.changes_type) then b:=b OR $08;
   Stream.Write(b,Sizeof(b));
   TStreamOp.WriteAccountKey(Stream,FData.new_accountkey);
   TStreamOp.WriteAnsiString(Stream,FData.new_name);
   Stream.Write(FData.new_type,Sizeof(FData.new_type));
+  if FProtocolVersion>=CT_PROTOCOL_5 then begin
+    TStreamOp.WriteAnsiString(Stream,FData.new_data);
+  end;
   TStreamOp.WriteAnsiString(Stream,FData.sign.r);
   TStreamOp.WriteAnsiString(Stream,FData.sign.s);
   Result := true;
@@ -432,11 +438,15 @@ begin
   if (b AND $01)=$01 then FData.changes_type:=FData.changes_type + [public_key];
   if (b AND $02)=$02 then FData.changes_type:=FData.changes_type + [account_name];
   if (b AND $04)=$04 then FData.changes_type:=FData.changes_type + [account_type];
+  if (b AND $08)=$08 then FData.changes_type:=FData.changes_type + [account_data];
   // Check
-  if (b AND $F8)<>0 then Exit;
+  if (b AND $F0)<>0 then Exit;
   if TStreamOp.ReadAccountKey(Stream,FData.new_accountkey)<0 then Exit;
   if TStreamOp.ReadAnsiString(Stream,FData.new_name)<0 then Exit;
   Stream.Read(FData.new_type,Sizeof(FData.new_type));
+  if FProtocolVersion>=CT_PROTOCOL_5 then begin
+    if TStreamOp.ReadAnsiString(Stream,FData.new_data)<0 then Exit;
+  end else FData.new_data := Nil;
   if TStreamOp.ReadAnsiString(Stream,FData.sign.r)<0 then Exit;
   if TStreamOp.ReadAnsiString(Stream,FData.sign.s)<0 then Exit;
   Result := true;
@@ -452,6 +462,7 @@ begin
   OperationResume.Changers[0].New_Accountkey := FData.new_accountkey;
   OperationResume.Changers[0].New_Name := FData.new_name;
   OperationResume.Changers[0].New_Type := FData.new_type;
+  OperationResume.Changers[0].New_Data := FData.new_data;
   If (FData.account_signer=FData.account_target) then begin
     OperationResume.Changers[0].N_Operation := FData.n_operation;
     OperationResume.Changers[0].Signature := FData.sign;
@@ -509,7 +520,7 @@ begin
     Exit;
   end;
   if (account_signer.balance<FData.fee) then begin
-    errors := 'Insuficient founds';
+    errors := 'Insuficient funds';
     exit;
   end;
   if (length(FData.payload)>CT_MaxPayloadSize) then begin
@@ -542,6 +553,18 @@ begin
       Exit;
     end;
   end;
+  if (account_data in FData.changes_type) then begin
+    // TAccount.Data is a 0..32 bytes length
+    if (Length(FData.new_data)>CT_MaxAccountDataSize) then begin
+      errors := 'New data length ('+IntToStr(Length(FData.new_data))+') > '+IntToStr(CT_MaxAccountDataSize);
+      Exit;
+    end;
+  end else begin
+    if Length(FData.new_data)<>0 then begin
+      errors := 'New data must be null when no data change';
+      Exit;
+    end;
+  end;
   If (FData.changes_type=[]) then begin
     errors := 'No change';
     Exit;
@@ -582,6 +605,9 @@ begin
   If (account_type in FData.changes_type) then begin
     account_target.account_type := FData.new_type;
   end;
+  If (account_data in FData.changes_type) then begin
+    account_target.account_data := FData.new_data;
+  end;
   Result := AccountTransaction.UpdateAccountInfo(AccountPreviousUpdatedBlock,
          GetOpID,
          FData.account_signer,FData.n_operation,FData.account_target,
@@ -639,6 +665,7 @@ constructor TOpChangeAccountInfo.CreateChangeAccountInfo(ACurrentProtocol : word
   account_target: Cardinal; key: TECPrivateKey; change_key: Boolean;
   const new_account_key: TAccountKey; change_name: Boolean;
   const new_name: TRawBytes; change_type: Boolean; const new_type: Word;
+  change_data: Boolean; const new_data : TRawBytes;
   fee: UInt64; payload: TRawBytes);
 begin
   inherited Create(ACurrentProtocol);
@@ -662,6 +689,10 @@ begin
     FData.changes_type:=FData.changes_type + [account_type];
     FData.new_type:=new_type;
   end;
+  If change_data then begin
+    FData.changes_type:=FData.changes_type + [account_data];
+    FData.new_data:=new_data;
+  end;
 
   if Assigned(key) then begin
     FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
@@ -686,6 +717,10 @@ begin
     if s<>'' then s:=s+', ';
     s := s + 'new type to '+IntToStr(FData.new_type);
   end;
+  If (account_data IN FData.changes_type)  then begin
+    if s<>'' then s:=s+', ';
+    s := s + 'new data to '+FData.new_data.ToHexaString;
+  end;
   Result := Format('Change account %s info: %s fee:%s (n_op:%d) payload size:%d',[
      TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target),
      s,
@@ -708,10 +743,14 @@ begin
     if (public_key in FData.changes_type) then b:=b OR $01;
     if (account_name in FData.changes_type) then b:=b OR $02;
     if (account_type in FData.changes_type) then b:=b OR $04;
+    if (account_data in FData.changes_type) then b:=b OR $08;
     Stream.Write(b,Sizeof(b));
     TStreamOp.WriteAccountKey(Stream,FData.new_accountkey);
     TStreamOp.WriteAnsiString(Stream,FData.new_name);
     Stream.Write(FData.new_type,Sizeof(FData.new_type));
+    if (current_protocol>=CT_PROTOCOL_5) then begin
+      TStreamOp.WriteAnsiString(Stream,FData.new_data);
+    end;
     if (current_protocol<=CT_PROTOCOL_3) then begin
       Stream.Position := 0;
       setlength(Result,Stream.Size);

+ 34 - 5
src/core/URPC.pas

@@ -274,10 +274,6 @@ Begin
         auxObj.GetAsVariant('fee').Value := TAccountComp.FormatMoneyDecimal(OPR.Changers[i].Fee * (-1));
         auxObj.GetAsVariant('fee_s').Value := TAccountComp.FormatMoney(OPR.Changers[i].Fee * (-1));
       end;
-      { XXXXXXXXXX
-      if (OPR.OpType = CT_Op_Data) then begin
-        FillDataObject(auxObj, OPR.Changers[i].OpData);
-      end;        }
       LString := '';
       for LOpChangeAccountInfoType := Low(LOpChangeAccountInfoType) to High(LOpChangeAccountInfoType) do begin
         if (LOpChangeAccountInfoType in OPR.Changers[i].Changes_type) then begin
@@ -421,6 +417,9 @@ begin
     If account_type in multiOperation.Data.changesInfo[i].Changes_type then begin
       auxObj.GetAsVariant('new_type').Value := multiOperation.Data.changesInfo[i].New_Type;
     end;
+    If account_data in multiOperation.Data.changesInfo[i].Changes_type then begin
+      auxObj.GetAsVariant('new_data').Value := multiOperation.Data.changesInfo[i].New_Data.ToHexaString;
+    end;
   end;
   jsonObject.GetAsVariant('amount').Value:=TAccountComp.FormatMoneyDecimal( multiOperation.OperationAmount );
   jsonObject.GetAsVariant('fee').Value:=TAccountComp.FormatMoneyDecimal( multiOperation.OperationFee );
@@ -1781,6 +1780,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     changePubKey : Boolean; Const new_account_pubkey : TAccountKey;
     changeName: Boolean; Const new_name : TRawBytes;
     changeType: Boolean; new_type : Word;
+    AChangeAccountData : Boolean; ANew_AccountData : TRawBytes;
     fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : TOpChangeAccountInfo;
   // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
   var privateKey : TECPrivateKey;
@@ -1799,6 +1799,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       account_signer,account_last_n_operation+1,account_target,
       privateKey,
       changePubKey,new_account_pubkey,changeName,new_name,changeType,new_type,
+      AChangeAccountData,ANew_AccountData,
       fee,f_raw);
     if Not Result.HasValidSignature then begin
       FreeAndNil(Result);
@@ -1819,11 +1820,12 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     opChangeInfo: TOpChangeAccountInfo;
     account_signer, account_target : Cardinal;
     fee : Int64;
-    changeKey,changeName,changeType : Boolean;
+    changeKey,changeName,changeType,changeAccountData : Boolean;
     new_name : TRawBytes;
     new_type : Word;
     new_typeI : Integer;
     new_pubkey : TAccountKey;
+    new_AccountData : TRawBytes;
   begin
     Result := false;
     account_signer := params.AsInteger('account_signer',MaxInt);
@@ -1875,10 +1877,28 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       changeType:=False;
     end;
 
+    if (params.IndexOfName('new_data')>=0) then begin
+      changeAccountData:=True;
+      if not TCrypto.HexaToRaw(params.AsString('new_data',''),new_AccountData) then begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc := 'new_data is not an HEXASTRING';
+        Exit;
+      end;
+      if Length(new_AccountData)>CT_MaxAccountDataSize then begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc := 'new_data limited to 0..'+IntToStr(CT_MaxAccountDataSize)+' bytes';
+        Exit;
+      end;
+    end else begin
+      new_AccountData := Nil;
+      changeAccountData:=False;
+    end;
+
     opChangeInfo := CreateOperationChangeAccountInfo(current_protocol,account_signer,last_n_operation,account_target,actualAccountKey,
       changeKey,new_pubkey,
       changeName,new_name,
       changeType,new_type,
+      changeAccountData,new_AccountData,
       fee,TCrypto.HexaToRaw(params.AsString('payload','')),
       params.AsString('payload_method','dest'),params.AsString('pwd',''));
     if opChangeInfo=nil then exit;
@@ -2481,6 +2501,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         - "new_b58_pubkey" or "new_enc_pubkey" : (optional) The new public key for this account
         - "new_name" : (optional) The new account name
         - "new_type" : (optional) The new account type
+        - "new_data" : HEXASTRING (optional) The new account data
         }
     Result := false;
     if Not HexaStringToOperationsHashTreeAndGetMultioperation(
@@ -2563,6 +2584,14 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
           changeinfo.Changes_type:=changeinfo.Changes_type + [account_type];
           changeinfo.New_Type:=jsonArr.GetAsObject(i).AsInteger('new_type',0);
         end;
+        if (jsonArr.GetAsObject(i).IndexOfName('new_data')>=0) then begin
+          changeinfo.Changes_type:=changeinfo.Changes_type + [account_data];
+          if Not TCrypto.HexaToRaw(jsonArr.GetAsObject(i).AsString('new_data',''),changeinfo.New_Data) then begin
+            ErrorNum:=CT_RPC_ErrNum_InvalidData;
+            ErrorDesc:='Invalid HEXASTRING value at new_data param';
+            Exit;
+          end;
+        end;
         if (changeinfo.Changes_type = []) then begin
           ErrorNum:=CT_RPC_ErrNum_InvalidData;
           ErrorDesc:='Need change something for account '+inttostr(changeinfo.Account);

+ 36 - 8
src/core/UTxMultiOperation.pas

@@ -318,7 +318,9 @@ var i : Integer;
   b : Byte;
 begin
   // Will save protocol info
-  w := CT_PROTOCOL_3;
+  if FProtocolVersion<CT_PROTOCOL_5 then
+    w := CT_PROTOCOL_3
+  else w := CT_PROTOCOL_5;
   stream.Write(w,SizeOf(w));
   // Save senders count
   w := Length(FData.txSenders);
@@ -352,12 +354,17 @@ begin
     Stream.Write(chi.N_Operation,Sizeof(chi.N_Operation));
     b := 0;
     if (public_key in chi.Changes_type) then b:=b OR $01;
-    if (account_name in chi.changes_type) then b:=b OR $02;
-    if (account_type in chi.changes_type) then b:=b OR $04;
+    if (account_name in chi.Changes_type) then b:=b OR $02;
+    if (account_type in chi.Changes_type) then b:=b OR $04;
+    if (account_data in chi.Changes_type) then b:=b OR $08;
+
     Stream.Write(b,Sizeof(b));
     TStreamOp.WriteAccountKey(Stream,chi.New_Accountkey);
     TStreamOp.WriteAnsiString(Stream,chi.New_Name);
     Stream.Write(chi.New_Type,Sizeof(chi.New_Type));
+    if FProtocolVersion>=CT_PROTOCOL_5 then begin
+      TStreamOp.WriteAnsiString(Stream,chi.New_Data);
+    end;
     If FSaveSignatureValue then begin
       TStreamOp.WriteAnsiString(Stream,chi.Signature.r);
       TStreamOp.WriteAnsiString(Stream,chi.Signature.s);
@@ -368,7 +375,7 @@ end;
 
 function TOpMultiOperation.LoadOpFromStream(Stream: TStream; LoadExtendedData: Boolean): Boolean;
 var i : Integer;
-  w : Word;
+  w, LSavedProtocol : Word;
   txs : TMultiOpSender;
   txr : TMultiOpReceiver;
   chi : TMultiOpChangeInfo;
@@ -394,8 +401,8 @@ begin
   Result := False;
   Try
     // Read protocol info
-    stream.Read(w,SizeOf(w));
-    If w<>CT_PROTOCOL_3 then Raise Exception.Create('Invalid protocol found');
+    stream.Read(LSavedProtocol,SizeOf(LSavedProtocol));
+    If (Not (LSavedProtocol in [CT_PROTOCOL_3,CT_PROTOCOL_5])) then Raise Exception.Create('Invalid protocol found '+IntToStr(LSavedProtocol));
     // Load senders
     stream.Read(w,SizeOf(w));
     If w>CT_MAX_MultiOperation_Senders then Raise Exception.Create('Max senders');
@@ -441,11 +448,17 @@ begin
         if (b AND $01)=$01 then chi.changes_type:=chi.changes_type + [public_key];
         if (b AND $02)=$02 then chi.changes_type:=chi.changes_type + [account_name];
         if (b AND $04)=$04 then chi.changes_type:=chi.changes_type + [account_type];
+        if (b AND $08)=$08 then chi.changes_type:=chi.changes_type + [account_data];
         // Check
-        if (b AND $F8)<>0 then Exit;
+        if (LSavedProtocol=CT_PROTOCOL_3) and ((b AND $F8)<>0) then Exit;
+        if (b AND $F0)<>0 then Exit;
         TStreamOp.ReadAccountKey(Stream,chi.New_Accountkey);
         TStreamOp.ReadAnsiString(Stream,chi.New_Name);
         Stream.Read(chi.New_Type,Sizeof(chi.New_Type));
+        if (LSavedProtocol<>CT_PROTOCOL_3) then begin
+          TStreamOp.ReadAnsiString(Stream,chi.New_Data);
+        end;
+
         TStreamOp.ReadAnsiString(Stream,chi.Signature.r);
         TStreamOp.ReadAnsiString(Stream,chi.Signature.s);
         //
@@ -677,6 +690,18 @@ begin
         Exit;
       end;
     end;
+    // Account Data protection: (PIP-0024)
+    if (account_data in chi.Changes_type) then begin
+      if Length(chi.New_Data)>CT_MaxAccountDataSize then begin
+        errors := 'New data length ('+IntToStr(Length(chi.New_data))+') > '+IntToStr(CT_MaxAccountDataSize);
+        Exit;
+      end;
+    end else begin
+      if Length(chi.New_Data)<>0 then begin
+        errors := 'New data must be null when no data change';
+        Exit;
+      end;
+    end;
     If (chi.changes_type=[]) then begin
       errors := 'No change';
       Exit;
@@ -713,6 +738,9 @@ begin
     If (account_type in chi.Changes_type) then begin
       changer.account_type := chi.New_Type;
     end;
+    If (account_data in chi.Changes_type) then begin
+      changer.account_data := chi.New_Data;
+    end;
     If Not AccountTransaction.UpdateAccountInfo(
            AccountPreviousUpdatedBlock,
            GetOpID,
@@ -999,7 +1027,7 @@ begin
     // check valid Change type
     for ct:=Low(TOpChangeAccountInfoType) to High(TOpChangeAccountInfoType) do begin
       case ct of
-        public_key,account_name,account_type : ; // Allowed
+        public_key,account_name,account_type,account_data : ; // Allowed
       else
         if (ct in changes[i].Changes_type) then begin
           Exit; // Not allowed multioperation change type

+ 22 - 25
src/gui-classic/UFRMOperation.dfm

@@ -295,15 +295,11 @@ object FRMOperation: TFRMOperation
         Top = 7
         Width = 524
         Height = 204
-        ActivePage = tsListAccount
+        ActivePage = tsChangeInfo
         TabOrder = 0
         OnChange = PageControlOpTypeChange
         object tsTransaction: TTabSheet
           Caption = 'Transaction'
-          ExplicitLeft = 0
-          ExplicitTop = 0
-          ExplicitWidth = 0
-          ExplicitHeight = 0
           object lblDestAccount: TLabel
             Left = 13
             Top = 32
@@ -390,10 +386,6 @@ object FRMOperation: TFRMOperation
         object tsChangePrivateKey: TTabSheet
           Caption = 'Change Key'
           ImageIndex = 1
-          ExplicitLeft = 0
-          ExplicitTop = 0
-          ExplicitWidth = 0
-          ExplicitHeight = 0
           object lblNewPrivateKey: TLabel
             Left = 57
             Top = 40
@@ -777,10 +769,6 @@ object FRMOperation: TFRMOperation
         object tsDelistAccount: TTabSheet
           Caption = 'Delist Account'
           ImageIndex = 3
-          ExplicitLeft = 0
-          ExplicitTop = 0
-          ExplicitWidth = 0
-          ExplicitHeight = 0
           object lblDelistErrors: TLabel
             Left = 13
             Top = 10
@@ -801,10 +789,6 @@ object FRMOperation: TFRMOperation
         object tsBuyAccount: TTabSheet
           Caption = 'Buy Account'
           ImageIndex = 4
-          ExplicitLeft = 0
-          ExplicitTop = 0
-          ExplicitWidth = 0
-          ExplicitHeight = 0
           object lblAccountToBuy: TLabel
             Left = 13
             Top = 32
@@ -980,10 +964,6 @@ object FRMOperation: TFRMOperation
         end
         object tsChangeInfo: TTabSheet
           Caption = 'Change Info'
-          ExplicitLeft = 0
-          ExplicitTop = 0
-          ExplicitWidth = 0
-          ExplicitHeight = 0
           object lblChangeInfoErrors: TLabel
             Left = 13
             Top = 10
@@ -1022,12 +1002,24 @@ object FRMOperation: TFRMOperation
             Color = clBtnFace
             ParentColor = False
           end
+          object lblChangeAccountData: TLabel
+            Left = 13
+            Top = 87
+            Width = 100
+            Height = 13
+            Alignment = taRightJustify
+            AutoSize = False
+            Caption = 'Change Data'
+            Color = clBtnFace
+            ParentColor = False
+          end
           object ebChangeName: TEdit
             Left = 118
             Top = 29
             Width = 258
             Height = 21
             TabOrder = 0
+            TextHint = 'Account name (null or 3..32 chars)'
           end
           object ebChangeType: TEdit
             Left = 118
@@ -1035,6 +1027,15 @@ object FRMOperation: TFRMOperation
             Width = 76
             Height = 21
             TabOrder = 1
+            TextHint = '0..65535'
+          end
+          object ebChangeAccountData: TEdit
+            Left = 118
+            Top = 84
+            Width = 258
+            Height = 21
+            TabOrder = 2
+            TextHint = 'Hexadecimal value (0..32 bytes)'
           end
         end
       end
@@ -1049,10 +1050,6 @@ object FRMOperation: TFRMOperation
     object tsGlobalError: TTabSheet
       ImageIndex = 1
       TabVisible = False
-      ExplicitLeft = 0
-      ExplicitTop = 0
-      ExplicitWidth = 0
-      ExplicitHeight = 373
       object lblGlobalErrors: TLabel
         Left = 40
         Top = 50

+ 29 - 9
src/gui-classic/UFRMOperation.pas

@@ -130,6 +130,8 @@ type
     btnHashLock: TSpeedButton;
     sbTimeLock: TSpeedButton;
     cbPayloadAsHex: TCheckBox;
+    lblChangeAccountData: TLabel;
+    ebChangeAccountData: TEdit;
     procedure ebNewPublicKeyExit(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
@@ -169,7 +171,7 @@ type
     Function UpdateOpListAccount(Const TargetAccount : TAccount; var SalePrice : Int64; var SellerAccount,SignerAccount : TAccount; var NewOwnerPublicKey : TAccountKey; var LockedUntilBlock : Cardinal; var HashLock : T32Bytes; var errors : String) : Boolean;
     Function UpdateOpDelist(Const TargetAccount : TAccount; var SignerAccount : TAccount; var errors : String) : Boolean;
     Function UpdateOpBuyAccount(Const SenderAccount : TAccount; var AccountToBuy : TAccount; var amount : Int64; var NewPublicKey : TAccountKey; var ARecipientSigned : Boolean; var errors : String) : Boolean;
-    Function UpdateOpChangeInfo(Const TargetAccount : TAccount; var SignerAccount : TAccount; var changeName : Boolean; var newName : TRawBytes; var changeType : Boolean; var newType : Word; var errors : String) : Boolean;
+    Function UpdateOpChangeInfo(Const TargetAccount : TAccount; var SignerAccount : TAccount; var changeName : Boolean; var newName : TRawBytes; var changeType : Boolean; var newType : Word; var AChangeData : Boolean; var ANewData : TRawBytes; var errors : String) : Boolean;
     procedure SetDefaultFee(const Value: Int64);
     Procedure OnSenderAccountsChanged(Sender : TObject);
     procedure OnWalletKeysChanged(Sender : TObject);
@@ -213,9 +215,9 @@ Var errors : String;
   dooperation : Boolean;
   _newOwnerPublicKey : TECDSA_Public;
   LHashLock : T32Bytes;
-  _newName : TRawBytes;
+  _newName, LNewAccountData : TRawBytes;
   _newType : Word;
-  _changeName, _changeType, _V2, _executeSigner, LRecipientSigned : Boolean;
+  _changeName, _changeType, LChangeAccountData, _V2, _executeSigner, LRecipientSigned : Boolean;
   _senderAccounts : TCardinalsArray;
 label loop_start;
 begin
@@ -330,13 +332,15 @@ loop_start:
         {%endregion}
       end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
         {%region Operation: Change Info}
-        if not UpdateOpChangeInfo(account,signerAccount,_changeName,_newName,_changeType,_newType,errors) then begin
+        if not UpdateOpChangeInfo(account,signerAccount,_changeName,_newName,_changeType,_newType,LChangeAccountData,LNewAccountData,errors) then begin
           If Length(_senderAccounts)=1 then raise Exception.Create(errors);
         end else begin
           if signerAccount.balance>DefaultFee then _fee := DefaultFee
           else _fee := signerAccount.balance;
           op := TOpChangeAccountInfo.CreateChangeAccountInfo(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1,account.account,LKey.PrivateKey,false,CT_TECDSA_Public_Nul,
-             _changeName,_newName,_changeType,_newType,_fee,FEncodedPayload);
+             _changeName,_newName,_changeType,_newType,
+             LChangeAccountData,LNewAccountData,
+             _fee,FEncodedPayload);
         end;
         {%endregion}
       end else begin
@@ -580,6 +584,7 @@ begin
   //
   ebChangeName.OnChange:=updateInfoClick;
   ebChangeType.OnChange:=updateInfoClick;
+  ebChangeAccountData.OnChange:=updateInfoClick;
   //
   sbSearchDestinationAccount.OnClick := sbSearchDestinationAccountClick;
   sbSearchListerSellerAccount.OnClick := sbSearchListerSellerAccountClick;
@@ -663,10 +668,12 @@ begin
     ebSignerAccount.text := TAccountComp.AccountNumberToAccountTxtNumber(SenderAccounts.Get(0));
     ebChangeName.Text := FNode.GetMempoolAccount(SenderAccounts.Get(0)).name.ToPrintable;
     ebChangeType.Text := IntToStr(FNode.GetMempoolAccount(SenderAccounts.Get(0)).account_type);
+    ebChangeAccountData.Text := FNode.GetMempoolAccount(SenderAccounts.Get(0)).account_data.ToHexaString;
   end else begin
     ebSignerAccount.text := '';
     ebChangeName.Text := '';
     ebChangeType.Text := '';
+    ebChangeAccountData.Text := '';
   end;
   UpdateAccountsInfo;
   UpdateOperationOptions(errors);
@@ -878,7 +885,8 @@ begin
 end;
 
 function TFRMOperation.UpdateOpChangeInfo(const TargetAccount: TAccount; var SignerAccount : TAccount;
-   var changeName : Boolean; var newName: TRawBytes; var changeType : Boolean; var newType: Word; var errors: String): Boolean;
+   var changeName : Boolean; var newName: TRawBytes; var changeType : Boolean; var newType: Word;
+   var AChangeData : Boolean; var ANewData : TRawBytes; var errors: String): Boolean;
 var auxC : Cardinal;
   i : Integer;
   errCode : Integer;
@@ -947,6 +955,18 @@ begin
       errors := 'Account name and type are the same. Not changed';
       Exit;
     end;
+    if FNode.Bank.SafeBox.CurrentProtocol>=CT_PROTOCOL_5 then begin
+      // Allow Change Account.Data PIP-0024
+      if Not TCrypto.HexaToRaw(ebChangeAccountData.Text,ANewData) then begin
+        errors := 'Invalid hexadecimal value at Data';
+        Exit;
+      end;
+      AChangeData := Not TBaseType.Equals( TargetAccount.account_data , ANewData);
+      if Length(ANewData)>CT_MaxAccountDataSize then begin
+        errors := Format('Data size (%d) greater than %d',[Length(ANewData),CT_MaxAccountDataSize]);
+        Exit;
+      end;
+    end;
   finally
     Result := errors = '';
     if Not Result then begin
@@ -1085,8 +1105,8 @@ Var
   LHashLock : T32Bytes;
   salePrice, amount : Int64;
   auxC : Cardinal;
-  changeName,changeType, LRecipientSigned : Boolean;
-  newName : TRawBytes;
+  changeName,changeType, LRecipientSigned, LChangeAccountData : Boolean;
+  newName, LNewAccountData : TRawBytes;
   newType : Word;
 begin
   Result := false;
@@ -1156,7 +1176,7 @@ begin
   end else if (PageControlOpType.ActivePage = tsBuyAccount) then begin
     Result := UpdateOpBuyAccount(GetDefaultSenderAccount,account_to_buy,amount,publicKey,LRecipientSigned, errors);
   end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
-    Result := UpdateOpChangeInfo(GetDefaultSenderAccount,signer_account,changeName,newName,changeType,newType,errors);
+    Result := UpdateOpChangeInfo(GetDefaultSenderAccount,signer_account,changeName,newName,changeType,newType,LChangeAccountData,LNewAccountData,errors);
   end else begin
     errors := 'Must select an operation';
   end;