Browse Source

Merge upstream

Herman Schoenfeld 7 years ago
parent
commit
0f0c72d5db

+ 446 - 153
src/core.utils/UDataSources.pas

@@ -6,7 +6,7 @@ unit UDataSources;
 interface
 
 uses
-  Classes, SysUtils, UAccounts, UNode, UBlockchain, UCoreObjects,
+  Classes, SysUtils, UAccounts, UNode, UBlockchain, UCrypto, UCoreObjects,
   UCommon, UMemory, UConst, UCommon.Data,
   UCommon.Collections, Generics.Collections, Generics.Defaults, syncobjs;
 
@@ -15,91 +15,361 @@ type
   { TAccountsDataSourceBase }
 
   TAccountsDataSourceBase = class(TCustomDataSource<TAccount>)
-    protected
-      function GetColumns : TDataColumns; override;
-    public
-      function GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant; override;
+  protected
+    function GetColumns: TDataColumns; override;
+  public
+    function GetItemField(constref AItem: TAccount; const ABindingName: ansistring): variant; override;
   end;
 
   { TAccountsDataSource }
 
   TAccountsDataSource = class(TAccountsDataSourceBase)
-    protected
-      FIncludePending : boolean;
-      FKeys : TSortedHashSet<TAccountKey>;
-      function GetFilterKeys : TArray<TAccountKey>;
-      procedure SetFilterKeys (const AKeys : TArray<TAccountKey>);
-    public
-      property IncludePending : boolean read FIncludePending write FIncludePending;
-      property FilterKeys : TArray<TAccountKey> read GetFilterKeys write SetFilterKeys;
-      constructor Create(AOwner: TComponent); override;
-      destructor Destroy; override;
-      procedure FetchAll(const AContainer : TList<TAccount>); override;
+  protected
+    FIncludePending: boolean;
+    FKeys: TSortedHashSet<TAccountKey>;
+    function GetFilterKeys: TArray<TAccountKey>;
+    procedure SetFilterKeys(const AKeys: TArray<TAccountKey>);
+  public
+    property IncludePending: boolean read FIncludePending write FIncludePending;
+    property FilterKeys: TArray<TAccountKey> read GetFilterKeys write SetFilterKeys;
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
   end;
 
   { TMyAccountsDataSource }
 
   TMyAccountsDataSource = class(TAccountsDataSource)
   public
-      procedure FetchAll(const AContainer : TList<TAccount>); override;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
   end;
 
   { TOperationsDataSourceBase }
 
   TOperationsDataSourceBase = class(TCustomDataSource<TOperationResume>)
-    private
-      FStart, FEnd : Cardinal;
-      function GetTimeSpan : TTimeSpan;
-      procedure SetTimeSpan(const ASpan : TTimeSpan);
-    protected
-      function GetColumns : TDataColumns;  override;
-    public
-      constructor Create(AOwner: TComponent); override;
-      property TimeSpan : TTimeSpan read GetTimeSpan write SetTimeSpan;
-      property StartBlock : Cardinal read FStart write FStart;
-      property EndBlock : Cardinal read FEnd write FEnd;
-      function GetItemField(constref AItem: TOperationResume; const ABindingName : AnsiString) : Variant; override;
+  private
+    FStart, FEnd: cardinal;
+    function GetTimeSpan: TTimeSpan;
+    procedure SetTimeSpan(const ASpan: TTimeSpan);
+  protected
+    function GetColumns: TDataColumns; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    property TimeSpan: TTimeSpan read GetTimeSpan write SetTimeSpan;
+    property StartBlock: cardinal read FStart write FStart;
+    property EndBlock: cardinal read FEnd write FEnd;
+    function GetItemField(constref AItem: TOperationResume; const ABindingName: ansistring): variant; override;
   end;
 
   { TAccountsOperationsDataSource }
 
   TAccountsOperationsDataSource = class(TOperationsDataSourceBase)
-    private
-      FAccounts : TSortedHashSet<Cardinal>;
-      function GetAccounts : TArray<Cardinal> ;
-      procedure SetAccounts(const AAccounts : TArray<Cardinal>);
-    public
-      constructor Create(AOwner: TComponent); override;
-      destructor Destroy; override;
-      property Accounts : TArray<Cardinal> read GetAccounts write SetAccounts;
-      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  private
+    FAccounts: TSortedHashSet<cardinal>;
+    function GetAccounts: TArray<cardinal>;
+    procedure SetAccounts(const AAccounts: TArray<cardinal>);
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    property Accounts: TArray<cardinal> read GetAccounts write SetAccounts;
+    procedure FetchAll(const AContainer: TList<TOperationResume>); override;
   end;
 
   { TPendingOperationsDataSource }
 
   TPendingOperationsDataSource = class(TOperationsDataSourceBase)
-    public
-      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  public
+    procedure FetchAll(const AContainer: TList<TOperationResume>); override;
   end;
 
   { TOperationsDataSource }
 
   TOperationsDataSource = class(TOperationsDataSourceBase)
-    public
-      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  public
+    procedure FetchAll(const AContainer: TList<TOperationResume>); override;
+  end;
+
+
+  TBlockChainData = record
+    Block: cardinal;
+    Timestamp: cardinal;
+    BlockProtocolVersion,
+    BlockProtocolAvailable: word;
+    OperationsCount: integer;
+    Volume: int64;
+    Reward, Fee: int64;
+    Target: cardinal;
+    HashRateTargetKhs: int64;
+    HashRateKhs: int64;
+    MinerPayload: TRawBytes;
+    PoW: TRawBytes;
+    SafeBoxHash: TRawBytes;
+    AccumulatedWork: UInt64;
+    TimeAverage200: real;
+    TimeAverage150: real;
+    TimeAverage100: real;
+    TimeAverage75: real;
+    TimeAverage50: real;
+    TimeAverage25: real;
+    TimeAverage10: real;
+  end;
+
+const
+  CT_TBlockChainData_NUL: TBlockChainData = (Block: 0; Timestamp: 0; BlockProtocolVersion: 0; BlockProtocolAvailable: 0; OperationsCount: -1; Volume: -1; Reward: 0; Fee: 0; Target: 0; HashRateTargetKhs: 0; HashRateKhs: 0; MinerPayload: ''; PoW: ''; SafeBoxHash: ''; AccumulatedWork: 0; TimeAverage200: 0; TimeAverage150: 0; TimeAverage100: 0; TimeAverage75: 0; TimeAverage50: 0; TimeAverage25: 0; TimeAverage10: 0);
+
+{ TBlockChainGrid }
+
+type
+  TShowHashRateAs = (hr_Kilo, hr_Mega, hr_Giga, hr_Tera);
+
+
+  { TBlockChainDataSourceBase }
+
+  TBlockChainDataSourceBase = class(TCustomDataSource<TBlockChainData>)
+  private
+    FStart, FEnd: cardinal;
+    FHashRateAs: TShowHashRateAs;
+    FHashRateAverageBlocksCount: integer;
+    function GetTimeSpan: TTimeSpan;
+    procedure SetTimeSpan(const ASpan: TTimeSpan);
+  protected
+    function GetColumns: TDataColumns; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    property TimeSpan: TTimeSpan read GetTimeSpan write SetTimeSpan;
+    property StartBlock: cardinal read FStart write FStart;
+    property EndBlock: cardinal read FEnd write FEnd;
+    property HashRateAs: TShowHashRateAs read FHashRateAs write FHashRateAs;
+    property HashRateAverageBlocksCount: integer read FHashRateAverageBlocksCount write FHashRateAverageBlocksCount;
+    function GetItemField(constref AItem: TBlockChainData; const ABindingName: ansistring): variant; override;
+  end;
+
+  { TBlockChainDataSource }
+
+  TBlockChainDataSource = class(TBlockChainDataSourceBase)
+  public
+    procedure FetchAll(const AContainer: TList<TBlockChainData>); override;
   end;
 
+
+
 implementation
 
 uses
-  math, UCoreUtils, UWallet, UTime;
+  Math, UCoreUtils, UWallet, UTime;
+
+{ TBlockChainDataSource }
+
+procedure TBlockChainDataSource.FetchAll(const AContainer: TList<TBlockChainData>);
+var
+  LStart, LEnd, LIdx: cardinal;
+  LOperationComp: TPCOperationsComp;
+  LBlockChainData: TBlockChainData;
+  LOperationBlock: TOperationBlock;
+  LBigNum: TBigNum;
+  LNode: TNode;
+begin
+
+  LNode := TNode.Node;
+  if not Assigned(LNode) then
+    Exit;
+  if LNode.Bank.BlocksCount <= 0 then
+    Exit;
+  LEnd := EndBlock;
+  LStart := StartBlock;
+  LOperationComp := TPCOperationsComp.Create(nil);
+  try
+    LOperationComp.bank := LNode.Bank;
+
+    for LIdx := LEnd downto LStart do
+    begin
+      LBlockChainData := CT_TBlockChainData_NUL;
+      LOperationBlock := LNode.Bank.SafeBox.Block(LIdx).blockchainInfo;
+      LBlockChainData.Block := LOperationBlock.block;
+      LBlockChainData.Timestamp := LOperationBlock.timestamp;
+      LBlockChainData.BlockProtocolVersion := LOperationBlock.protocol_version;
+      LBlockChainData.BlockProtocolAvailable := LOperationBlock.protocol_available;
+      LBlockChainData.Reward := LOperationBlock.reward;
+      LBlockChainData.Fee := LOperationBlock.fee;
+      LBlockChainData.Target := LOperationBlock.compact_target;
+      LBlockChainData.HashRateKhs := LNode.Bank.SafeBox.CalcBlockHashRateInKhs(LBlockChainData.Block, HashRateAverageBlocksCount);
+      LBigNum := TBigNum.TargetToHashRate(LOperationBlock.compact_target);
+      try
+        LBlockChainData.HashRateTargetKhs := LBigNum.Divide(1024).Divide(CT_NewLineSecondsAvg).Value;
+      finally
+        LBigNum.Free;
+      end;
+      LBlockChainData.MinerPayload := LOperationBlock.block_payload;
+      LBlockChainData.PoW := LOperationBlock.proof_of_work;
+      LBlockChainData.SafeBoxHash := LOperationBlock.initial_safe_box_hash;
+      LBlockChainData.AccumulatedWork := LNode.Bank.SafeBox.Block(LBlockChainData.Block).AccumulatedWork;
+      if (LNode.Bank.LoadOperations(LOperationComp, LIdx)) then
+      begin
+        LBlockChainData.OperationsCount := LOperationComp.Count;
+        LBlockChainData.Volume := LOperationComp.OperationsHashTree.TotalAmount + LOperationComp.OperationsHashTree.TotalFee;
+      end;
+      LBlockChainData.TimeAverage200 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 200);
+      LBlockChainData.TimeAverage150 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 150);
+      LBlockChainData.TimeAverage100 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 100);
+      LBlockChainData.TimeAverage75 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 75);
+      LBlockChainData.TimeAverage50 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 50);
+      LBlockChainData.TimeAverage25 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 25);
+      LBlockChainData.TimeAverage10 := LNode.Bank.GetTargetSecondsAverage(LBlockChainData.Block, 10);
+      AContainer.Add(LBlockChainData);
+    end;
+  finally
+    LOperationComp.Free;
+  end;
+end;
+
+{ TBlockChainDataSourceBase }
+
+function TBlockChainDataSourceBase.GetTimeSpan: TTimeSpan;
+begin
+  Result := TTimeSpan.FromSeconds(CT_NewLineSecondsAvg * (FEnd - FStart + 1));
+end;
+
+procedure TBlockChainDataSourceBase.SetTimeSpan(const ASpan: TTimeSpan);
+var
+  LNode: TNode;
+begin
+  LNode := TNode.Node;
+  if not Assigned(LNode) then
+    exit;
+  FEnd := LNode.Bank.BlocksCount - 1;
+  FStart := ClipValue(FEnd - (ASpan.TotalBlockCount + 1), 0, FEnd);
+end;
+
+function TBlockChainDataSourceBase.GetColumns: TDataColumns;
+var
+  LHashType: string;
+begin
+  case HashRateAs of
+    hr_Kilo: LHashType := 'Kh/s';
+    hr_Mega: LHashType := 'Mh/s';
+    hr_Giga: LHashType := 'Gh/s';
+    hr_Tera: LHashType := 'Th/s';
+    else
+      LHashType := '?h/s';
+  end;
+  Result := TDataColumns.Create(
+    TDataColumn.From('UnixTime'),
+    TDataColumn.From('Time'),
+    TDataColumn.From('Block'),
+    TDataColumn.From('Ops'),
+    TDataColumn.From(LHashType),
+    TDataColumn.From('Volume'),
+    TDataColumn.From('Reward'),
+    TDataColumn.From('Fee'),
+    TDataColumn.From('FeeDecimal'),
+    TDataColumn.From('Target'),
+    TDataColumn.From('MinerPayload'),
+    TDataColumn.From('POW'),
+    TDataColumn.From('SBH'),
+    TDataColumn.From('Protocol'),
+    TDataColumn.From('Deviation'),
+    TDataColumn.From('TimeAverage')
+    );
+end;
+
+constructor TBlockChainDataSourceBase.Create(AOwner: TComponent);
+var
+  LNode: TNode;
+begin
+  inherited Create(AOwner);
+  LNode := TNode.Node;
+  if Assigned(LNode) then
+  begin
+    FStart := 0;
+    FEnd := LNode.Bank.BlocksCount - 1;
+  end
+  else
+  begin
+    FStart := 0;
+    FEnd := 0;
+  end;
+end;
+
+function TBlockChainDataSourceBase.GetItemField(constref AItem: TBlockChainData; const ABindingName: ansistring): variant;
+var
+  LHR_Base: int64;
+  LHashType: string;
+  LDeviation: double;
+begin
+  case FHashRateAs of
+    hr_Kilo: LHashType := 'Kh/s';
+    hr_Mega: LHashType := 'Mh/s';
+    hr_Giga: LHashType := 'Gh/s';
+    hr_Tera: LHashType := 'Th/s';
+    else
+      LHashType := '?h/s';
+  end;
+  //if ABindingName = 'UnixTime' then
+  //  Result := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime((AItem.Timestamp))))
+  //else if ABindingName = 'Time' then
+  //  Result := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime((AItem.Timestamp))))
+  if ABindingName = 'UnixTime' then
+    Result := AItem.Timestamp
+  else if ABindingName = 'Time' then
+    Result := UnixTimeToLocalStr(AItem.Timestamp)
+  else if ABindingName = 'Block' then
+    Result := AItem.Block
+  else if ABindingName = 'Ops' then
+    Result := AItem.OperationsCount
+  else if ABindingName = 'Volume' then
+    Result := TAccountComp.FormatMoney(AItem.Volume)
+  else if ABindingName = 'Reward' then
+    Result := TAccountComp.FormatMoney(AItem.Reward)
+  else if ABindingName = 'Fee' then
+    Result := AItem.Fee
+  else if ABindingName = 'FeeDecimal' then
+    Result := TAccountComp.FormatMoneyDecimal(AItem.Fee)
+  else if ABindingName = 'Target' then
+    Result := IntToHex(AItem.Target, 8)
+  else if ABindingName = LHashType then
+  begin
+
+    case HashRateAs of
+      hr_Kilo: LHR_Base := 1;
+      hr_Mega: LHR_Base := 1000;
+      hr_Giga: LHR_Base := 1000000;
+      hr_Tera: LHR_Base := 1000000000;
+      else
+        Result := 1;
+    end;
+    Result := Format('%.2n (%.2n)', [AItem.HashRateKhs / LHR_Base, AItem.HashRateTargetKhs / LHR_Base]);
+  end
+  else if ABindingName = 'MinerPayload' then
+  begin
+    if TCrypto.IsHumanReadable(AItem.MinerPayload) then
+      Result := AItem.MinerPayload
+    else
+      Result := TCrypto.ToHexaString(AItem.MinerPayload);
+  end
+  else if ABindingName = 'POW' then
+    Result := TCrypto.ToHexaString(AItem.PoW)
+  else if ABindingName = 'SBH' then
+    Result := TCrypto.ToHexaString(AItem.SafeBoxHash)
+  else if ABindingName = 'Protocol' then
+    Result := IntToStr(AItem.BlockProtocolVersion) + '-' + IntToStr(AItem.BlockProtocolAvailable)
+  else if ABindingName = 'Deviation' then
+  begin
+    LDeviation := ((CT_NewLineSecondsAvg - AItem.TimeAverage100) / CT_NewLineSecondsAvg) * 100;
+    Result := Format('%.2f', [LDeviation]) + ' %';
+  end
+  else if ABindingName = 'TimeAverage' then
+    Result := Format('200:%.1f 150:%.1f 100:%.1f 75:%.1f 50:%.1f 25:%.1f 10:%.1f', [AItem.TimeAverage200,
+      AItem.TimeAverage150, AItem.TimeAverage100, AItem.TimeAverage75, AItem.TimeAverage50, AItem.TimeAverage25, AItem.TimeAverage10])
+  else
+    raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
+end;
 
 { TAccountsDataSourceBase }
 
-function TAccountsDataSourceBase.GetColumns : TDataColumns;
+function TAccountsDataSourceBase.GetColumns: TDataColumns;
 begin
   Result := TDataColumns.Create(
-    TDataColumn.From('AccountNumber', true),
+    TDataColumn.From('AccountNumber', True),
     TDataColumn.From('Account'),
     TDataColumn.From('Name'),
     TDataColumn.From('Display'),
@@ -111,19 +381,19 @@ begin
     TDataColumn.From('Price'),
     TDataColumn.From('PriceDecimal'),
     TDataColumn.From('LockedUntil')
-  );
+    );
 end;
 
-function TAccountsDataSourceBase.GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant;
+function TAccountsDataSourceBase.GetItemField(constref AItem: TAccount; const ABindingName: ansistring): variant;
 var
-  index : Integer;
+  index: integer;
 begin
   if ABindingName = 'AccountNumber' then
     Result := AItem.account
   else if ABindingName = 'Account' then
     Result := AItem.AccountString
   else if ABindingName = 'Name' then
-    Result := AItem.name
+    Result := AItem.Name
   else if ABindingName = 'Display' then
     Result := AItem.DisplayString
   else if ABindingName = 'Balance' then
@@ -142,7 +412,8 @@ begin
     Result := TAccountComp.FormatMoneyDecimal(AItem.accountInfo.price)
   else if ABindingName = 'LockedUntil' then
     Result := AItem.accountInfo.locked_until_block
-  else raise Exception.Create(Format('Field not found "%s"', [ABindingName]));
+  else
+    raise Exception.Create(Format('Field not found "%s"', [ABindingName]));
 end;
 
 { TAccountsDataSource }
@@ -158,96 +429,105 @@ begin
   FKeys.Free;
 end;
 
-function TAccountsDataSource.GetFilterKeys : TArray<TAccountKey>;
+function TAccountsDataSource.GetFilterKeys: TArray<TAccountKey>;
 begin
   Result := FKeys.ToArray;
 end;
 
-procedure TAccountsDataSource.SetFilterKeys (const AKeys : TArray<TAccountKey>);
-var i : Integer;
+procedure TAccountsDataSource.SetFilterKeys(const AKeys: TArray<TAccountKey>);
+var
+  i: integer;
 begin
   FKeys.Clear;
   for i := Low(AKeys) to High(AKeys) do
     FKeys.Add(AKeys[i]);
 end;
 
-procedure TAccountsDataSource.FetchAll(const AContainer : TList<TAccount>);
+procedure TAccountsDataSource.FetchAll(const AContainer: TList<TAccount>);
 var
-  i : integer;
-  acc : TAccount;
-  safeBox : TPCSafeBox;
+  i: integer;
+  acc: TAccount;
+  safeBox: TPCSafeBox;
 begin
   safeBox := TNode.Node.Bank.SafeBox;
   safeBox.StartThreadSafe;
   try
-   if FKeys.Count = 0 then
-     for i := 0 to safeBox.AccountsCount - 1 do
-       AContainer.Add(safeBox.Account(i)) // Load all accounts
-   else
-     for i := 0 to safeBox.AccountsCount - 1 do begin // Load key-matching accounts
-       if FIncludePending then
-         acc := TNode.Node.Operations.SafeBoxTransaction.Account(i)
-       else
-         acc := safeBox.Account(i);
-       if FKeys.Contains(acc.accountInfo.accountKey) then
-         AContainer.Add(acc)
-     end;
+    if FKeys.Count = 0 then
+      for i := 0 to safeBox.AccountsCount - 1 do
+        AContainer.Add(safeBox.Account(i)) // Load all accounts
+    else
+      for i := 0 to safeBox.AccountsCount - 1 do
+      begin // Load key-matching accounts
+        if FIncludePending then
+          acc := TNode.Node.Operations.SafeBoxTransaction.Account(i)
+        else
+          acc := safeBox.Account(i);
+        if FKeys.Contains(acc.accountInfo.accountKey) then
+          AContainer.Add(acc);
+      end;
   finally
-   safeBox.EndThreadSave;
+    safeBox.EndThreadSave;
   end;
 end;
 
 { TMyAccountsDataSource }
 
-procedure TMyAccountsDataSource.FetchAll(const AContainer : TList<TAccount>);
+procedure TMyAccountsDataSource.FetchAll(const AContainer: TList<TAccount>);
 var
-  i : integer;
-  LAccs : TArray<TAccount>;
+  i: integer;
+  LAccs: TArray<TAccount>;
 begin
   LAccs := TWallet.Keys.AccountsKeyList.GetAccounts(FIncludePending);
-  if FKeys.Count > 0 then begin
+  if FKeys.Count > 0 then
+  begin
     for i := Low(LAccs) to High(LAccs) do
       if FKeys.Contains(LAccs[i].accountInfo.accountKey) then
         AContainer.Add(LAccs[i]);
-  end else AContainer.AddRange(LAccs);
+  end
+  else
+    AContainer.AddRange(LAccs);
 end;
 
 { TOperationsDataSourceBase }
 
-constructor TOperationsDataSourceBase.Create(AOwner:TComponent);
+constructor TOperationsDataSourceBase.Create(AOwner: TComponent);
 var
-  node : TNode;
+  node: TNode;
 begin
- inherited Create(AOwner);
- node := TNode.Node;
-  if Assigned(Node) then begin
+  inherited Create(AOwner);
+  node := TNode.Node;
+  if Assigned(Node) then
+  begin
     FStart := 0;
     FEnd := node.Bank.BlocksCount - 1;
-  end else begin
+  end
+  else
+  begin
     FStart := 0;
     FEnd := 0;
   end;
 end;
 
-function TOperationsDataSourceBase.GetTimeSpan : TTimeSpan;
+function TOperationsDataSourceBase.GetTimeSpan: TTimeSpan;
 begin
-  Result := TTimeSpan.FromSeconds( CT_NewLineSecondsAvg * (FEnd - FStart + 1) );
+  Result := TTimeSpan.FromSeconds(CT_NewLineSecondsAvg * (FEnd - FStart + 1));
 end;
 
-procedure TOperationsDataSourceBase.SetTimeSpan(const ASpan : TTimeSpan);
+procedure TOperationsDataSourceBase.SetTimeSpan(const ASpan: TTimeSpan);
 var
-  node : TNode;
+  node: TNode;
 begin
- node := TNode.Node;
- if Not Assigned(Node) then exit;
- FEnd := node.Bank.BlocksCount - 1;
- FStart := ClipValue(FEnd - (ASpan.TotalBlockCount + 1), 0, FEnd);
+  node := TNode.Node;
+  if not Assigned(Node) then
+    exit;
+  FEnd := node.Bank.BlocksCount - 1;
+  FStart := ClipValue(FEnd - (ASpan.TotalBlockCount + 1), 0, FEnd);
 end;
 
-function TOperationsDataSourceBase.GetColumns : TDataColumns;
+function TOperationsDataSourceBase.GetColumns: TDataColumns;
 begin
   Result := TDataColumns.Create(
-    TDataColumn.From('OPHASH', true),
+    TDataColumn.From('OPHASH', True),
     TDataColumn.From('UnixTime'),
     TDataColumn.From('Time'),
     TDataColumn.From('Block'),
@@ -266,12 +546,12 @@ begin
     TDataColumn.From('BalanceDecimal'),
     TDataColumn.From('Payload'),
     TDataColumn.From('Description')
-  );
+    );
 end;
 
-function TOperationsDataSourceBase.GetItemField(constref AItem: TOperationResume; const ABindingName : AnsiString) : Variant;
+function TOperationsDataSourceBase.GetItemField(constref AItem: TOperationResume; const ABindingName: ansistring): variant;
 var
-  index : Integer;
+  index: integer;
 begin
   if ABindingName = 'OPHASH' then
     Result := TPCOperation.OperationHashAsHexa(AItem.OperationHash)
@@ -284,7 +564,7 @@ begin
   else if ABindingName = 'Index' then
     Result := AItem.NOpInsideBlock
   else if ABindingName = 'BlockLocation' then
-    Result := IIF(AItem.OpType <> CT_PseudoOp_Reward, Inttostr(AItem.Block) + '/' + Inttostr(AItem.NOpInsideBlock+1), Inttostr(AItem.Block))
+    Result := IIF(AItem.OpType <> CT_PseudoOp_Reward, IntToStr(AItem.Block) + '/' + IntToStr(AItem.NOpInsideBlock + 1), IntToStr(AItem.Block))
   else if ABindingName = 'BlockLocationSortable' then
     Result := IIF(AItem.OpType <> CT_PseudoOp_Reward, UInt64(AItem.Block) * 4294967296 + UInt32(AItem.NOpInsideBlock), 0)  // number pattern = [block][opindex]
   else if ABindingName = 'Account' then
@@ -311,69 +591,74 @@ begin
     Result := AItem.PrintablePayload
   else if ABindingName = 'Description' then
     Result := AItem.OperationTxt
-  else raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
+  else
+    raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
 end;
 
 { TAccountsOperationsDataSource }
 
-constructor TAccountsOperationsDataSource.Create(AOwner:TComponent);
+constructor TAccountsOperationsDataSource.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
-  FAccounts := TSortedHashSet<Cardinal>.Create;
+  FAccounts := TSortedHashSet<cardinal>.Create;
 end;
 
 destructor TAccountsOperationsDataSource.Destroy;
 begin
- Inherited;
- FAccounts.Free;
+  inherited;
+  FAccounts.Free;
 end;
 
-function TAccountsOperationsDataSource.GetAccounts : TArray<Cardinal> ;
+function TAccountsOperationsDataSource.GetAccounts: TArray<cardinal>;
 begin
   Result := FAccounts.ToArray;
 end;
 
-procedure TAccountsOperationsDataSource.SetAccounts(const AAccounts : TArray<Cardinal>);
+procedure TAccountsOperationsDataSource.SetAccounts(const AAccounts: TArray<cardinal>);
 begin
   FAccounts.Clear;
   FAccounts.AddRange(AAccounts);
 end;
 
-procedure TAccountsOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+procedure TAccountsOperationsDataSource.FetchAll(const AContainer: TList<TOperationResume>);
 var
-  block, i, keyIndex : integer;
-  OPR : TOperationResume;
-  accountBlockOps : TOperationsResumeList;
-  node : TNode;
-  list : Classes.TList;
-  Op : TPCOperation;
-  acc : Cardinal;
-  GC : TDisposables;
+  block, i, keyIndex: integer;
+  OPR: TOperationResume;
+  accountBlockOps: TOperationsResumeList;
+  node: TNode;
+  list: Classes.TList;
+  Op: TPCOperation;
+  acc: cardinal;
+  GC: TDisposables;
 begin
-  if FAccounts.Count = 0
-    then exit;
+  if FAccounts.Count = 0 then
+    exit;
   node := TNode.Node;
-  if Not Assigned(Node)
-    then exit;
+  if not Assigned(Node) then
+    exit;
   TNode.Node.Bank.SafeBox.StartThreadSafe;
   try
-    accountBlockOps := GC.AddObject(TOperationsResumeList.Create ) as TOperationsResumeList;
-    list := GC.AddObject( Classes.TList.Create ) as Classes.TList;
-    for acc in FAccounts do begin
+    accountBlockOps := GC.AddObject(TOperationsResumeList.Create) as TOperationsResumeList;
+    list := GC.AddObject(Classes.TList.Create) as Classes.TList;
+    for acc in FAccounts do
+    begin
       // Load pending operations first
       list.Clear;
       accountBlockOps.Clear;
-      Node.Operations.OperationsHashTree.GetOperationsAffectingAccount( acc, list );
+      Node.Operations.OperationsHashTree.GetOperationsAffectingAccount(acc, list);
       if list.Count > 0 then
-        for i := list.Count - 1 downto 0 do begin
-          Op := node.Operations.OperationsHashTree.GetOperation( PtrInt( list[i] ) );
-          If TPCOperation.OperationToOperationResume( 0, Op, false, acc, OPR ) then begin
+        for i := list.Count - 1 downto 0 do
+        begin
+          Op := node.Operations.OperationsHashTree.GetOperation(PtrInt(list[i]));
+          if TPCOperation.OperationToOperationResume(0, Op, False, acc, OPR) then
+          begin
             OPR.NOpInsideBlock := i;
-            OPR.Block := Node.Operations.OperationBlock.block; ;
-            OPR.Balance := Node.Operations.SafeBoxTransaction.Account( acc {Op.SignerAccount} ).balance;
+            OPR.Block := Node.Operations.OperationBlock.block;
+            ;
+            OPR.Balance := Node.Operations.SafeBoxTransaction.Account(acc {Op.SignerAccount}).balance;
             AContainer.Add(OPR);
           end;
-      end;
+        end;
 
       // Load block ops
       Node.GetStoredOperationsFromAccount(accountBlockOps, acc, MaxInt, 0, MaxInt);
@@ -381,24 +666,27 @@ begin
         AContainer.Add(accountBlockOps[i]);
     end;
   finally
-   TNode.Node.Bank.SafeBox.EndThreadSave;
+    TNode.Node.Bank.SafeBox.EndThreadSave;
   end;
 end;
 
 { TPendingOperationsDataSource }
 
-procedure TPendingOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+procedure TPendingOperationsDataSource.FetchAll(const AContainer: TList<TOperationResume>);
 var
-  i : integer;
-  node : TNode;
-  Op : TPCOperation;
-  OPR : TOperationResume;
+  i: integer;
+  node: TNode;
+  Op: TPCOperation;
+  OPR: TOperationResume;
 begin
- node := TNode.Node;
-  if Not Assigned(Node) then exit;
-  for i := Node.Operations.Count - 1 downto 0 do begin
+  node := TNode.Node;
+  if not Assigned(Node) then
+    exit;
+  for i := Node.Operations.Count - 1 downto 0 do
+  begin
     Op := Node.Operations.OperationsHashTree.GetOperation(i);
-    If TPCOperation.OperationToOperationResume(0,Op, false, Op.SignerAccount,OPR) then begin
+    if TPCOperation.OperationToOperationResume(0, Op, False, Op.SignerAccount, OPR) then
+    begin
       OPR.NOpInsideBlock := i;
       OPR.Block := Node.Bank.BlocksCount;
       OPR.Balance := Node.Operations.SafeBoxTransaction.Account(Op.SignerAccount).balance;
@@ -409,34 +697,39 @@ end;
 
 { TOperationsDataSource }
 
-procedure TOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+procedure TOperationsDataSource.FetchAll(const AContainer: TList<TOperationResume>);
 var
-  block, i, j, keyIndex : integer;
-  OPR : TOperationResume;
-  blockOps : TPCOperationsComp;
-  node : TNode;
-  GC : TDisposables;
+  block, i, j, keyIndex: integer;
+  OPR: TOperationResume;
+  blockOps: TPCOperationsComp;
+  node: TNode;
+  GC: TDisposables;
 
 begin
   node := TNode.Node;
-  if Not Assigned(Node) then exit;
-  blockOps := GC.AddObject(TPCOperationsComp.Create(Nil)) as TPCOperationsComp;
-  for block := FEnd downto FStart do begin  /// iterate blocks correctly
+  if not Assigned(Node) then
+    exit;
+  blockOps := GC.AddObject(TPCOperationsComp.Create(nil)) as TPCOperationsComp;
+  for block := FEnd downto FStart do
+  begin  /// iterate blocks correctly
     opr := CT_TOperationResume_NUL;
-    if (Node.Bank.Storage.LoadBlockChainBlock(blockOps, block)) then begin
-      AContainer.Add( blockOps.GetMinerRewardPseudoOperation );
-     // if blockOps.Count = 0 then exit;
-      for i := blockOps.Count - 1 downto 0 do begin    // reverse order
-        if TPCOperation.OperationToOperationResume(block, blockOps.Operation[i], false, blockOps.Operation[i].SignerAccount, opr) then begin
+    if (Node.Bank.Storage.LoadBlockChainBlock(blockOps, block)) then
+    begin
+      AContainer.Add(blockOps.GetMinerRewardPseudoOperation);
+      // if blockOps.Count = 0 then exit;
+      for i := blockOps.Count - 1 downto 0 do
+        if TPCOperation.OperationToOperationResume(block, blockOps.Operation[i], False, blockOps.Operation[i].SignerAccount, opr) then
+        begin
           opr.NOpInsideBlock := i;
           opr.Block := block;
           opr.time := blockOps.OperationBlock.timestamp;
           AContainer.Add(opr);
-        end;
-      end;
-    end else break;
+        end// reverse order
+      ;
+    end
+    else
+      break;
   end;
 end;
 
 end.
-

+ 49 - 35
src/gui/UFRMBlockExplorer.lfm

@@ -1,70 +1,84 @@
 object FRMBlockExplorer: TFRMBlockExplorer
-  Left = 87
-  Height = 444
-  Top = 135
+  Left = 190
+  Height = 424
+  Top = 287
   Width = 864
+  ActiveControl = ebBlockChainBlockStart
   Caption = 'Block Explorer'
   ClientHeight = 424
   ClientWidth = 864
   Menu = BlockExplorerMenu
   OnCreate = FormCreate
-  OnDestroy = FormDestroy
-  Position = poOwnerFormCenter
-  LCLVersion = '1.6.4.0'
-  object Panel2: TPanel
+  Position = poMainFormCenter
+  Visible = False
+  object Panel1: TPanel
     Left = 0
-    Height = 41
+    Height = 56
     Top = 0
     Width = 864
     Align = alTop
     BevelOuter = bvNone
-    ClientHeight = 41
+    ClientHeight = 56
     ClientWidth = 864
     TabOrder = 0
-    object Label9: TLabel
-      Left = 11
+    object Label2: TLabel
+      Left = 16
       Height = 15
       Top = 10
       Width = 112
       Caption = 'Filter by blocks range'
       ParentColor = False
     end
-    object ebBlockChainBlockEnd: TEdit
-      Left = 185
-      Height = 23
-      Top = 7
-      Width = 57
-      OnExit = ebBlockChainBlockStartExit
-      OnKeyPress = ebBlockChainBlockStartKeyPress
-      TabOrder = 0
-    end
   end
-  object dgBlockChainExplorer: TDrawGrid
-    Left = 0
-    Height = 383
-    Top = 41
-    Width = 864
-    Align = alClient
-    ExtendedSelect = False
+  object ebBlockChainBlockStart: TEdit
+    Left = 16
+    Height = 23
+    Top = 32
+    Width = 57
+    OnExit = ebBlockChainBlockExit
+    OnKeyPress = ebBlockChainBlockStartKeyPress
     TabOrder = 1
-    TitleFont.Color = clWindowText
-    TitleFont.Height = -11
-    TitleFont.Name = 'Tahoma'
+    TextHint = 'From'
   end
-  object ebBlockChainBlockStart: TEdit
-    Left = 125
+  object ebBlockChainBlockEnd: TEdit
+    Left = 104
     Height = 23
-    Top = 7
+    Top = 32
     Width = 57
-    OnExit = ebBlockChainBlockStartExit
+    OnExit = ebBlockChainBlockExit
     OnKeyPress = ebBlockChainBlockStartKeyPress
     TabOrder = 2
+    TextHint = 'To'
+  end
+  object grpBlockExplorer: TGroupBox
+    Left = 16
+    Height = 376
+    Top = 72
+    Width = 840
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'All Blocks'
+    ClientHeight = 356
+    ClientWidth = 836
+    TabOrder = 3
+    object paGrid: TPanel
+      Left = 8
+      Height = 310
+      Top = 16
+      Width = 824
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'Block Panel'
+      TabOrder = 0
+    end
   end
   object BlockExplorerMenu: TMainMenu
     left = 560
-    top = 8
     object miTools: TMenuItem
       Caption = 'Tools'
+      object miDecodePayload: TMenuItem
+        Caption = 'Decode Payload'
+        ShortCut = 113
+      end
     end
   end
 end

+ 264 - 43
src/gui/UFRMBlockExplorer.pas

@@ -2,8 +2,6 @@ unit UFRMBlockExplorer;
 
 {$mode delphi}
 
-interface
-
 { Copyright (c) 2018 by Herman Schoenfeld
 
   Distributed under the MIT software license, see the accompanying file LICENSE
@@ -14,80 +12,303 @@ interface
 }
 
 
+interface
+
 {$I ..\config.inc}
 
 uses
-  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
-  StdCtrls, Grids, Menus, UCommon.UI, UGridUtils;
+  LCLIntf, LCLType, SysUtils, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Grids, Menus, Classes,
+  UCommon.UI, UConst, UDataSources, UNode, UVisualGrid, UCellRenderers, UCommon.Data, UCoreUtils;
 
 type
 
   { TFRMBlockExplorer }
 
   TFRMBlockExplorer = class(TApplicationForm)
-    dgBlockChainExplorer: TDrawGrid;
     ebBlockChainBlockEnd: TEdit;
     ebBlockChainBlockStart: TEdit;
-    Label9: TLabel;
+    grpBlockExplorer: TGroupBox;
+    Label2: TLabel;
     BlockExplorerMenu: TMainMenu;
+    miDecodePayload: TMenuItem;
     miTools: TMenuItem;
-    Panel2: TPanel;
-    procedure ebBlockChainBlockStartExit(Sender: TObject);
-    procedure ebBlockChainBlockStartKeyPress(Sender: TObject;
-      var Key: Char);
+    Panel1: TPanel;
+    paGrid: TPanel;
+    procedure ebBlockChainBlockExit(Sender: TObject);
+    procedure ebBlockChainBlockStartKeyPress(Sender: TObject; var Key: char);
     procedure FormCreate(Sender: TObject);
-    procedure FormDestroy(Sender:TObject);
+
+  protected
+    procedure OnNodeNewAccount(Sender: TObject);
   private
-    FUpdating : boolean;
-    FBlockChainGrid : TBlockChainGrid;
+    FMaxBlocks: integer;
+    { private declarations }
+    FUpdating: boolean;
+    FBlockStart, FBlockEnd: int64;
+    FNodeNotifyEvents: TNodeNotifyEvents;
+    FBlockChainGrid: TVisualGrid;
+    FBlockChainDataSource: TBlockChainDataSource;
+
+    FHashRateAs: TShowHashRateAs;
+    FHashRateAverageBlocksCount: integer;
 
+    procedure SetMaxBlocks(AValue: integer);
+    procedure UpdateVisualGridUI();
+    procedure SetBlocks(AStart, AEnd: int64);
   public
     { public declarations }
+    property MaxBlocks: integer read FMaxBlocks write SetMaxBlocks;
   end;
 
+implementation
+
+{$r *.lfm}
+
+uses UUserInterface, UMemory, UFRMPayloadDecoder, UBlockChain, UWallet, Generics.Collections;
+
+procedure TFRMBlockExplorer.FormCreate(Sender: TObject);
+begin
+
+  // event registrations
+  FNodeNotifyEvents := TNodeNotifyEvents.Create(self);
+  FNodeNotifyEvents.OnBlocksChanged := OnNodeNewAccount;
+
+  FUpdating := False;
+  FBlockStart := -1;
+  FBlockEnd := -1;
+  FMaxBlocks := 300;
+  FHashRateAverageBlocksCount := 50;
+  FHashRateAs:={$IFDEF PRODUCTION}hr_Giga{$ELSE}hr_Mega{$ENDIF};
+
+  UpdateVisualGridUI();
+
+end;
+
+procedure TFRMBlockExplorer.OnNodeNewAccount(Sender: TObject);
+begin
+  UpdateVisualGridUI(); //main
+end;
+
+procedure TFRMBlockExplorer.UpdateVisualGridUI();
 var
-  FRMBlockExplorer: TFRMBlockExplorer = nil;
+  LNode: TNode;
+  LStart, LEnd: int64;
+  LHashType: string;
+begin
+  LNode := FNodeNotifyEvents.Node;
+  if FBlockEnd < 0 then
+  begin
+    if LNode.Bank.BlocksCount > 0 then
+      LEnd := LNode.Bank.BlocksCount - 1
+    else
+      LEnd := 0;
+  end
+  else
+    LEnd := FBlockEnd;
+  if FBlockStart < 0 then
+  begin
+    if (LEnd > MaxBlocks) then
+      LStart := LEnd - MaxBlocks
+    else
+      LStart := 0;
+  end
+  else
+    LStart := FBlockStart;
+  if LStart < 0 then
+    LStart := 0;
+  if LEnd >= LNode.Bank.BlocksCount then
+    LEnd := LNode.Bank.BlocksCount;
+  // fields
+  FBlockChainDataSource := TBlockChainDataSource.Create(Self);
 
-implementation
-uses UFRMMainForm, UUserInterface;
+  FBlockChainDataSource.HashRateAs := FHashRateAs;
+  FBlockChainDataSource.HashRateAverageBlocksCount := FHashRateAverageBlocksCount;
+
+  FBlockChainDataSource.StartBlock := LStart;
+  FBlockChainDataSource.EndBlock := LEnd;
+
+  FBlockChainGrid := TVisualGrid.Create(Self);
+  FBlockChainGrid.SortMode := smMultiColumn;
+  FBlockChainGrid.FetchDataInThread := True;
+  FBlockChainGrid.AutoPageSize := True;
+  FBlockChainGrid.DeselectionType := dtDefault;
+  FBlockChainGrid.SelectionType := stRow;
+  FBlockChainGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging, vgoAutoHideSearchPanel];
 
-{$R *.lfm}
-procedure TFRMBlockExplorer.ebBlockChainBlockStartExit(Sender: TObject);
-var bstart,bend : Int64;
+  with FBlockChainGrid.AddColumn('Block') do
+  begin
+    AutoWidth := True;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Time') do
+  begin
+    SortBinding := 'UnixTime';
+    DisplayBinding := 'UnixTime';
+    Renderer := TCellRenderers.OperationTime;
+    Width := 130;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Ops') do
+  begin
+   // Sanitizer := TCellRenderers.OperationTypeSanitizer;
+    Width := 50;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Volume') do
+  begin
+    Width := 100;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
+   // Renderer := TCellRenderers.PASC_CheckPendingBalance;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Reward') do
+  begin
+    Width := 100;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
+    Renderer := TCellRenderers.PASC_CheckPendingBalance;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Fee') do
+  begin
+    Binding := 'FeeDecimal';
+    SortBinding := 'Fee';
+    DisplayBinding := 'Fee';
+    AutoWidth := True;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
+    Renderer := TCellRenderers.PASC_CheckPendingBalance;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FBlockChainGrid.AddColumn('Target') do
+  begin
+    Width := 100;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+  case FBlockChainDataSource.HashRateAs of
+    hr_Kilo: LHashType := 'Kh/s';
+    hr_Mega: LHashType := 'Mh/s';
+    hr_Giga: LHashType := 'Gh/s';
+    hr_Tera: LHashType := 'Th/s';
+    else
+      LHashType := '?h/s';
+  end;
+
+  with FBlockChainGrid.AddColumn(LHashType) do
+  begin
+    Width := 100;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('Miner Payload') do
+  begin
+    Binding := 'MinerPayload';
+    Width := 200;
+    Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('Proof Of Work') do
+  begin
+    Binding := 'POW';
+    Width := 200;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('SafeBox Hash') do
+  begin
+    Binding := 'SBH';
+    Width := 200;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('Protocol') do
+  begin
+    Width := 100;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('Deviation') do
+  begin
+    Width := 100;
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  with FBlockChainGrid.AddColumn('Time Average') do
+  begin
+    AutoWidth := True;
+    Binding := 'TimeAverage';
+   // Renderer := TCellRenderers.Payload;
+    Filters := SORTABLE_TEXT_FILTER;
+  end;
+
+  FBlockChainGrid.Caption.Alignment := taCenter;
+  FBlockChainGrid.Caption.Text := 'All Blocks';
+  FBlockChainGrid.Caption.Visible := True;
+
+  // Add datasources to grid
+  FBlockChainGrid.DataSource := FBlockChainDataSource;
+
+  // Add grid to panels
+  paGrid.AddControlDockCenter(FBlockChainGrid);
+end;
+
+procedure TFRMBlockExplorer.SetMaxBlocks(AValue: integer);
 begin
-  If not FUpdating then
-  Try
-    FUpdating := True;
-    bstart := StrToInt64Def(ebBlockChainBlockStart.Text,-1);
-    bend := StrToInt64Def(ebBlockChainBlockEnd.Text,-1);
-    FBlockChainGrid.SetBlocks(bstart,bend);
-    if FBlockChainGrid.BlockStart>=0 then
-      ebBlockChainBlockStart.Text := Inttostr(FBlockChainGrid.BlockStart) else ebBlockChainBlockStart.Text := '';
-    if FBlockChainGrid.BlockEnd>=0 then
-      ebBlockChainBlockEnd.Text := Inttostr(FBlockChainGrid.BlockEnd) else ebBlockChainBlockEnd.Text := '';
-  Finally
-    FUpdating := false;
-  End;
+  if FMaxBlocks = AValue then
+    Exit;
+  FMaxBlocks := AValue;
+  if (FMaxBlocks <= 0) or (FMaxBlocks > 500) then
+    FMaxBlocks := 300;
+  UpdateVisualGridUI();
 end;
 
-procedure TFRMBlockExplorer.ebBlockChainBlockStartKeyPress(Sender: TObject;
-  var Key: Char);
+procedure TFRMBlockExplorer.SetBlocks(AStart, AEnd: int64);
 begin
-  if key=#13 then  ebBlockChainBlockStartExit(Nil);
+  if (AStart = FBlockStart) and (AEnd = FBlockEnd) then
+    Exit;
+  FBlockStart := AStart;
+  FBlockEnd := AEnd;
+  if (FBlockEnd > 0) and (FBlockStart > FBlockEnd) then
+    FBlockStart := -1;
+  UpdateVisualGridUI();
 end;
 
-procedure TFRMBlockExplorer.FormCreate(Sender: TObject);
+procedure TFRMBlockExplorer.ebBlockChainBlockExit(Sender: TObject);
+var
+  LStart, LEnd: int64;
 begin
-  FBlockChainGrid := TBlockChainGrid.Create(Self);
-  FBlockChainGrid.DrawGrid := dgBlockChainExplorer;
-  FBlockChainGrid.Node := TUserInterface.Node;
-  FBlockChainGrid.ShowTimeAverageColumns:={$IFDEF SHOW_AVERAGE_TIME_STATS}True;{$ELSE}False;{$ENDIF}
-  FUpdating := false;
+  if not FUpdating then
+    try
+      FUpdating := True;// move to finally
+      LStart := StrToInt64Def(ebBlockChainBlockStart.Text, -1);
+      if LStart >= 0 then
+        ebBlockChainBlockStart.Text := IntToStr(LStart)
+      else
+        ebBlockChainBlockStart.Text := '';
+      LEnd := StrToInt64Def(ebBlockChainBlockEnd.Text, -1);
+      if LEnd >= 0 then
+        ebBlockChainBlockEnd.Text := IntToStr(LEnd)
+      else
+        ebBlockChainBlockEnd.Text := '';
+      SetBlocks(LStart, LEnd);
+    finally
+      FUpdating := False;
+    end;
 end;
 
-procedure TFRMBlockExplorer.FormDestroy(Sender:TObject);
+procedure TFRMBlockExplorer.ebBlockChainBlockStartKeyPress(Sender: TObject; var Key: char);
+
 begin
-  FreeAndNil(FBlockChainGrid);
+  if Key = #13 then
+    ebBlockChainBlockExit(nil);
 end;
 
 end.

+ 4 - 3
src/gui/UFRMOperationExplorer.lfm

@@ -9,6 +9,7 @@ object FRMOperationExplorer: TFRMOperationExplorer
   ClientWidth = 864
   Menu = OperationsExplorerMenu
   OnCreate = FormCreate
+  Position = poMainFormCenter
   Visible = False
   object Panel1: TPanel
     Left = 0
@@ -51,17 +52,17 @@ object FRMOperationExplorer: TFRMOperationExplorer
   end
   object grpOperationExplorer: TGroupBox
     Left = 16
-    Height = 336
+    Height = 376
     Top = 72
     Width = 840
     Anchors = [akTop, akLeft, akRight, akBottom]
     Caption = 'All Operations'
-    ClientHeight = 316
+    ClientHeight = 356
     ClientWidth = 836
     TabOrder = 3
     object paGrid: TPanel
       Left = 8
-      Height = 282
+      Height = 310
       Top = 16
       Width = 824
       Anchors = [akTop, akLeft, akRight, akBottom]

+ 20 - 6
src/gui/UFRMOperationExplorer.pas

@@ -42,20 +42,23 @@ type
     procedure miFindOperationByOpHashClick(Sender: TObject);
 
   protected
-    procedure OnNodeBlocksChanged(Sender: TObject);
+    procedure OnNodeNewAccount(Sender: TObject);
     procedure OnNodeNewOperation(Sender: TObject);
     procedure OnOperationSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
   private
     { private declarations }
     FUpdating: boolean;
     FBlockStart, FBlockEnd: int64;
+    FMaxBlocks: integer;
     FNodeNotifyEvents: TNodeNotifyEvents;
     FOperationsGrid: TVisualGrid;
     FOperationsDataSource: TOperationsDataSource;
+    procedure SetMaxBlocks(AValue: integer);
     procedure UpdateVisualGridUI();
     procedure SetBlocks(AStart, AEnd: int64);
   public
     { public declarations }
+    property MaxBlocks: integer read FMaxBlocks write SetMaxBlocks;
   end;
 
 implementation
@@ -69,12 +72,13 @@ begin
 
   // event registrations
   FNodeNotifyEvents := TNodeNotifyEvents.Create(self);
-  FNodeNotifyEvents.OnBlocksChanged := OnNodeBlocksChanged;
+  FNodeNotifyEvents.OnBlocksChanged := OnNodeNewAccount;
   FNodeNotifyEvents.OnOperationsChanged := OnNodeNewOperation;
 
   FUpdating := False;
   FBlockStart := -1;
   FBlockEnd := -1;
+  FMaxBlocks := 300;
 
   UpdateVisualGridUI();
 
@@ -95,7 +99,7 @@ begin
   TUserInterface.ShowOperationInfoDialog(Self, ophash);
 end;
 
-procedure TFRMOperationExplorer.OnNodeBlocksChanged(Sender: TObject);
+procedure TFRMOperationExplorer.OnNodeNewAccount(Sender: TObject);
 begin
   UpdateVisualGridUI(); //main
 end;
@@ -126,7 +130,7 @@ var
   LNode: TNode;
   LStart, LEnd: int64;
 begin
-  LNode := TUserInterface.Node;
+  LNode := FNodeNotifyEvents.Node;
   if FBlockEnd < 0 then
   begin
     if LNode.Bank.BlocksCount > 0 then
@@ -138,8 +142,8 @@ begin
     LEnd := FBlockEnd;
   if FBlockStart < 0 then
   begin
-    if (LEnd > 300) then
-      LStart := LEnd - 300
+    if (LEnd > MaxBlocks) then
+      LStart := LEnd - MaxBlocks
     else
       LStart := 0;
   end
@@ -252,6 +256,16 @@ begin
   UpdateVisualGridUI();
 end;
 
+procedure TFRMOperationExplorer.SetMaxBlocks(AValue: integer);
+begin
+  if FMaxBlocks = AValue then
+    Exit;
+  FMaxBlocks := AValue;
+  if (FMaxBlocks <= 0) or (FMaxBlocks > 500) then
+    FMaxBlocks := 300;
+  UpdateVisualGridUI();
+end;
+
 procedure TFRMOperationExplorer.ebFilterOperationsAccountExit(Sender: TObject);
 var
   LStart, LEnd: int64;