github.com/braveheart12/insolar-09-08-19@v0.8.7/ledger/exporter/exporter.go (about)

     1  /*
     2   *    Copyright 2019 Insolar Technologies
     3   *
     4   *    Licensed under the Apache License, Version 2.0 (the "License");
     5   *    you may not use this file except in compliance with the License.
     6   *    You may obtain a copy of the License at
     7   *
     8   *        http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   *    Unless required by applicable law or agreed to in writing, software
    11   *    distributed under the License is distributed on an "AS IS" BASIS,
    12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *    See the License for the specific language governing permissions and
    14   *    limitations under the License.
    15   */
    16  
    17  package exporter
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"math"
    23  	"strconv"
    24  
    25  	"github.com/insolar/insolar/configuration"
    26  	"github.com/insolar/insolar/core"
    27  	"github.com/insolar/insolar/core/message"
    28  	"github.com/insolar/insolar/instrumentation/inslogger"
    29  	"github.com/insolar/insolar/ledger/storage"
    30  	"github.com/insolar/insolar/ledger/storage/record"
    31  	base58 "github.com/jbenet/go-base58"
    32  	"github.com/pkg/errors"
    33  	"github.com/ugorji/go/codec"
    34  )
    35  
    36  // Exporter provides methods for fetching data view from storage.
    37  type Exporter struct {
    38  	DB            storage.DBContext     `inject:""`
    39  	JetStorage    storage.JetStorage    `inject:""`
    40  	ObjectStorage storage.ObjectStorage `inject:""`
    41  	PulseTracker  storage.PulseTracker  `inject:""`
    42  	PulseStorage  core.PulseStorage     `inject:""`
    43  
    44  	cfg configuration.Exporter
    45  }
    46  
    47  // NewExporter creates new StorageExporter instance.
    48  func NewExporter(cfg configuration.Exporter) *Exporter {
    49  	return &Exporter{
    50  		cfg: cfg,
    51  	}
    52  }
    53  
    54  type payload map[string]interface{}
    55  
    56  // MarshalJSON serializes payload into JSON.
    57  func (p payload) MarshalJSON() ([]byte, error) {
    58  	var buf []byte
    59  	err := codec.NewEncoderBytes(&buf, &codec.JsonHandle{}).Encode(&p)
    60  	return buf, err
    61  }
    62  
    63  type recordData struct {
    64  	Type    string
    65  	Data    record.Record
    66  	Payload payload
    67  }
    68  
    69  type recordsData map[string]recordData
    70  
    71  type pulseData struct {
    72  	Records recordsData
    73  	Pulse   core.Pulse
    74  	JetID   core.RecordID
    75  }
    76  
    77  // Export returns data view from storage.
    78  func (e *Exporter) Export(ctx context.Context, fromPulse core.PulseNumber, size int) (*core.StorageExportResult, error) {
    79  	result := core.StorageExportResult{Data: map[string]interface{}{}}
    80  
    81  	jetIDs, err := e.JetStorage.GetJets(ctx)
    82  	if err != nil {
    83  		return nil, errors.Wrap(err, "failed to fetch jets")
    84  	}
    85  
    86  	currentPulse, err := e.PulseStorage.Current(ctx)
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "failed to get current pulse data")
    89  	}
    90  
    91  	counter := 0
    92  	fromPulsePN := core.PulseNumber(math.Max(float64(fromPulse), float64(core.GenesisPulse.PulseNumber)))
    93  
    94  	if fromPulsePN > currentPulse.PulseNumber {
    95  		return nil, errors.Errorf("failed to fetch data: from-pulse[%v] > current-pulse[%v]",
    96  			fromPulsePN, currentPulse.PulseNumber)
    97  	}
    98  
    99  	_, err = e.PulseTracker.GetPulse(ctx, fromPulsePN)
   100  	if err != nil {
   101  		tryPulse, err := e.PulseTracker.GetPulse(ctx, core.GenesisPulse.PulseNumber)
   102  		if err != nil {
   103  			return nil, errors.Wrap(err, "failed to fetch genesis pulse data")
   104  		}
   105  
   106  		for fromPulsePN > *tryPulse.Next {
   107  			tryPulse, err = e.PulseTracker.GetPulse(ctx, *tryPulse.Next)
   108  			if err != nil {
   109  				return nil, errors.Wrap(err, "failed to iterate through first pulses")
   110  			}
   111  		}
   112  		fromPulsePN = *tryPulse.Next
   113  	}
   114  
   115  	iterPulse := &fromPulsePN
   116  	for iterPulse != nil && counter < size {
   117  		pulse, err := e.PulseTracker.GetPulse(ctx, *iterPulse)
   118  		if err != nil {
   119  			return nil, errors.Wrap(err, "failed to fetch pulse data")
   120  		}
   121  
   122  		// We don't need data from current pulse, because of
   123  		// not all data for this pulse is persisted at this moment
   124  		// @sergey.morozov 20.01.18 - Blocks are synced to Heavy node with a lag.
   125  		// We can't reliably predict this lag so we add threshold of N seconds.
   126  		if pulse.Pulse.PulseNumber >= (currentPulse.PrevPulseNumber - core.PulseNumber(e.cfg.ExportLag)) {
   127  			iterPulse = nil
   128  			break
   129  		}
   130  
   131  		var data []*pulseData
   132  		for jetID := range jetIDs {
   133  			fetchedData, err := e.exportPulse(ctx, jetID, &pulse.Pulse)
   134  			if err != nil {
   135  				return nil, err
   136  			}
   137  			data = append(data, fetchedData)
   138  		}
   139  
   140  		result.Data[strconv.FormatUint(uint64(pulse.Pulse.PulseNumber), 10)] = data
   141  
   142  		iterPulse = pulse.Next
   143  		counter++
   144  	}
   145  
   146  	result.Size = counter
   147  	result.NextFrom = iterPulse
   148  
   149  	return &result, nil
   150  }
   151  
   152  func (e *Exporter) exportPulse(ctx context.Context, jetID core.RecordID, pulse *core.Pulse) (*pulseData, error) {
   153  	records := recordsData{}
   154  	err := e.DB.IterateRecordsOnPulse(ctx, jetID, pulse.PulseNumber, func(id core.RecordID, rec record.Record) error {
   155  		pl := e.getPayload(ctx, jetID, rec)
   156  
   157  		records[string(base58.Encode(id[:]))] = recordData{
   158  			Type:    recordType(rec),
   159  			Data:    rec,
   160  			Payload: pl,
   161  		}
   162  		return nil
   163  	})
   164  	if err != nil {
   165  		return nil, errors.Wrap(err, "exportPulse failed to IterateRecordsOnPulse")
   166  	}
   167  
   168  	data := pulseData{
   169  		Records: records,
   170  		Pulse:   *pulse,
   171  		JetID:   jetID,
   172  	}
   173  
   174  	return &data, nil
   175  }
   176  
   177  func (e *Exporter) getPayload(ctx context.Context, jetID core.RecordID, rec record.Record) payload {
   178  	switch r := rec.(type) {
   179  	case record.ObjectState:
   180  		if r.GetMemory() == nil {
   181  			break
   182  		}
   183  		blob, err := e.ObjectStorage.GetBlob(ctx, jetID, r.GetMemory())
   184  		if err != nil {
   185  			inslogger.FromContext(ctx).Errorf("getPayload failed to GetBlob (jet: %s)", jetID.DebugString())
   186  			return payload{}
   187  		}
   188  		memory := payload{}
   189  		err = codec.NewDecoderBytes(blob, &codec.CborHandle{}).Decode(&memory)
   190  		if err != nil {
   191  			return payload{"MemoryBinary": blob}
   192  		}
   193  		return payload{"Memory": memory}
   194  	case record.Request:
   195  		if r.GetPayload() == nil {
   196  			break
   197  		}
   198  		parcel, err := message.DeserializeParcel(bytes.NewBuffer(r.GetPayload()))
   199  		if err != nil {
   200  			return payload{"PayloadBinary": r.GetPayload()}
   201  		}
   202  
   203  		msg := parcel.Message()
   204  		switch m := parcel.Message().(type) {
   205  		case *message.CallMethod:
   206  			res, err := m.ToMap()
   207  			if err != nil {
   208  				return payload{"Payload": m, "Type": msg.Type().String()}
   209  			}
   210  			return payload{"Payload": res, "Type": msg.Type().String()}
   211  		case *message.CallConstructor:
   212  			res, err := m.ToMap()
   213  			if err != nil {
   214  				return payload{"Payload": m, "Type": msg.Type().String()}
   215  			}
   216  			return payload{"Payload": res, "Type": msg.Type().String()}
   217  		case *message.GenesisRequest:
   218  			return payload{"Payload": m, "Type": msg.Type().String()}
   219  		}
   220  
   221  		return payload{"Payload": msg, "Type": msg.Type().String()}
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  func recordType(rec record.Record) string {
   228  	switch rec.(type) {
   229  	case *record.GenesisRecord:
   230  		return "TypeGenesis"
   231  	case *record.ChildRecord:
   232  		return "TypeChild"
   233  	case *record.JetRecord:
   234  		return "TypeJet"
   235  	case *record.RequestRecord:
   236  		return "TypeCallRequest"
   237  	case *record.ResultRecord:
   238  		return "TypeResult"
   239  	case *record.TypeRecord:
   240  		return "TypeType"
   241  	case *record.CodeRecord:
   242  		return "TypeCode"
   243  	case *record.ObjectActivateRecord:
   244  		return "TypeActivate"
   245  	case *record.ObjectAmendRecord:
   246  		return "TypeAmend"
   247  	case *record.DeactivationRecord:
   248  		return "TypeDeactivate"
   249  	}
   250  
   251  	return record.TypeFromRecord(rec).String()
   252  }