package main import ( "encoding/csv" "encoding/json" "fmt" "io" "os" "boscoin.io/sebak/lib/block" "boscoin.io/sebak/lib/common" "boscoin.io/sebak/lib/node/runner" "boscoin.io/sebak/lib/storage" "boscoin.io/sebak/lib/transaction/operation" "github.com/rs/zerolog/log" "golang.org/x/xerrors" ) var ( flagStorageConfig = "file:///workspace/sebak/db" lastHeight uint64 = 2232370 ) const ( TypeFee = "fee" ) type Operation struct { Type string Height uint64 Source string Target string Amount common.Amount IsLinked bool } func NewOperation(t string, height uint64, source, target string, amount common.Amount, isLinked bool) Operation { return Operation{Type: t, Height: height, Source: source, Target: target, Amount: amount, IsLinked: isLinked} } func (op Operation) CSV(w *csv.Writer) error { return w.Write([]string{ op.Type, fmt.Sprintf("%d", op.Height), op.Source, op.Target, op.Amount.String(), fmt.Sprintf("%v", op.IsLinked), }) } func main() { var st *storage.LevelDBBackend if i, err := storage.NewConfigFromString(flagStorageConfig); err != nil { panic(err) } else if j, err := storage.NewStorage(i); err != nil { panic(err) } else { st = j } if initialBalance, err := runner.GetGenesisBalance(st); err != nil { panic(err) } else { log.Debug().Str("amount", initialBalance.String()).Msg("initial balance found") } w := csv.NewWriter(os.Stdout) w.Write([]string{ "type", "height", "source", "target", "amount", "feezing", }) defer w.Flush() // get target block var height uint64 = common.GenesisBlockHeight for { if i, err := fetch(st, height, w); err != nil { if !xerrors.Is(err, io.EOF) { panic(err) } break } else { height = i + 1 } } w.Flush() } func fetch(st *storage.LevelDBBackend, start uint64, w *csv.Writer) (uint64, error) { toHeight := start + 1000 var height uint64 = start for { if err := getTransactions(st, height, w); err != nil { return 0, err } if height%300 == 0 { w.Flush() } if height == toHeight { break } else if height == lastHeight { return lastHeight, io.EOF } height++ } return toHeight, nil } func getTransactions(st *storage.LevelDBBackend, height uint64, w *csv.Writer) error { var bk block.Block if i, err := block.GetBlockByHeight(st, height); err != nil { log.Error().Err(err).Uint64("height", height).Msg("failed to get block") return err } else { bk = i if height%1000 == 0 || height == lastHeight { log.Debug(). Uint64("height", height). Interface("block", bk). Str("hash", bk.Hash). Msg("found target block of height") } } iterFunc, closeFunc := block.GetBlockTransactionsByBlock(st, bk.Hash, nil) defer closeFunc() for { btx, hasNext, _ := iterFunc() if !hasNext { break } if ops, err := getOperations(st, btx); err != nil { return err } else if len(ops) < 1 { continue } else { for i := range ops { if err := ops[i].CSV(w); err != nil { panic(err) } } } } return nil } func getOperations(st *storage.LevelDBBackend, btx block.BlockTransaction) ([]Operation, error) { var ops []Operation iterFunc, closeFunc := block.GetBlockOperationsByTx(st, btx.Hash, nil) defer closeFunc() foundOps := map[string]bool{} for { bo, hasNext, _ := iterFunc() if !hasNext { break } switch bo.Type { case operation.TypeCreateAccount, operation.TypePayment: if i, err := unmarshalOperation(bo); err != nil { return nil, err } else { key := fmt.Sprintf("%s-%s-%s", i.Type, i.Source, i.Target) if _, found := foundOps[key]; found { continue } else { foundOps[key] = true ops = append(ops, i) } } default: continue } } if len(foundOps) < 1 { return nil, nil } amounts := common.Amount(0) for i := range ops { log.Debug().Interface("operation", ops[i]).Msg("found operation") amounts = amounts.MustAdd(ops[i].Amount) } log.Debug().Str("btx_amount", btx.Amount.String()).Str("collected", amounts.String()).Msg("calculating fee") ops = append(ops, NewOperation(TypeFee, ops[0].Height, btx.Source, "", btx.Amount.MustSub(amounts), false)) return ops, nil } func unmarshalOperation(bo block.BlockOperation) (Operation, error) { var body operation.Body if i, err := operation.UnmarshalBodyJSON(bo.Type, bo.Body); err != nil { return Operation{}, err } else { body = i } var target string var amount common.Amount var isLinked bool switch t := body.(type) { case operation.CreateAccount: target = t.Target amount = t.Amount isLinked = t.Linked != "" case operation.Payment: target = t.Target amount = t.Amount default: return Operation{}, xerrors.Errorf("unsupported type, %T", t) } return NewOperation(bo.Type.String(), bo.Height, bo.Source, target, amount, isLinked), nil } func toString(v interface{}) string { b, err := json.MarshalIndent(v, "", " ") if err != nil { panic(err) } return string(b) }