All files / src/data-package DataPackage.ts

97.56% Statements 40/41
0% Branches 0/1
100% Functions 14/14
97.5% Lines 39/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 1364x             4x         4x 4x 4x   4x                 4x     14x 14x 14x   14x 14x   14x       14x 14x 32x                   28x       29x                 2x 6x             2x 2x               11x 11x 11x         9x     9x 9x   9x         29x 37x 37x 37x     37x       36x   62x       28x             28x             28x            
import {
  arrayify,
  concat,
  hexlify,
  keccak256,
  SigningKey,
} from "ethers/lib/utils";
import {
  DATA_POINT_VALUE_BYTE_SIZE_BS,
  DATA_POINTS_COUNT_BS,
  TIMESTAMP_BS,
} from "../common/redstone-constants";
import { Serializable } from "../common/Serializable";
import { assert, convertIntegerNumberToBytes } from "../common/utils";
import { deserializeDataPointFromObj } from "../data-point/data-point-deserializer";
import { DataPoint, DataPointPlainObj } from "../data-point/DataPoint";
import { SignedDataPackage } from "./SignedDataPackage";
 
export interface DataPackagePlainObj {
  dataPoints: DataPointPlainObj[];
  timestampMilliseconds: number;
  dataPackageId?: string; // temporarly we need both to avoid simultaneous updates of whole infrastructure
  dataFeedId?: string;
}
 
export class DataPackage extends Serializable {
  public dataFeedId?: string;
  constructor(
    public readonly dataPoints: DataPoint[],
    public readonly timestampMilliseconds: number,
    public readonly dataPackageId?: string
  ) {
    super();
    this.dataFeedId = this.dataPackageId;
 
    Iif (dataPoints.length === 0) {
      throw new Error("Can not create a data package with no data points");
    }
 
    const expectedDataPointByteSize = dataPoints[0].getValueByteSize();
    for (const dataPoint of dataPoints) {
      assert(
        dataPoint.getValueByteSize() === expectedDataPointByteSize,
        "Values of all data points in a DataPackage must have the same number of bytes"
      );
    }
  }
 
  // Each data point in this data package can have a different byte size
  // So we set the default byte size to 0
  getEachDataPointByteSize() {
    return this.dataPoints[0].getValueByteSize();
  }
 
  toBytes(): Uint8Array {
    return concat([
      this.serializeDataPoints(),
      this.serializeTimestamp(),
      this.serializeDefaultDataPointByteSize(),
      this.serializeDataPointsCount(),
    ]);
  }
 
  toObj(): DataPackagePlainObj {
    return {
      dataPoints: this.dataPoints.map((dataPoint) => dataPoint.toObj()),
      timestampMilliseconds: this.timestampMilliseconds,
      dataFeedId: this.dataFeedId,
    };
  }
 
  public static fromObj(plainObject: DataPackagePlainObj): DataPackage {
    const dataPoints = plainObject.dataPoints.map(deserializeDataPointFromObj);
    return new DataPackage(
      dataPoints,
      plainObject.timestampMilliseconds,
      plainObject.dataPackageId
    );
  }
 
  getSignableHash(): Uint8Array {
    const serializedDataPackage = this.toBytes();
    const signableHashHex = keccak256(serializedDataPackage);
    return arrayify(signableHashHex);
  }
 
  sign(privateKey: string): SignedDataPackage {
    // Prepare hash for signing
    const signableHashBytes = this.getSignableHash();
 
    // Generating a signature
    const signingKey = new SigningKey(privateKey);
    const fullSignature = signingKey.signDigest(signableHashBytes);
    // Return a signed data package
    return new SignedDataPackage(this, fullSignature);
  }
 
  protected serializeDataPoints(): Uint8Array {
    // Sorting dataPoints by bytes32 representation of dataFeedIds lexicographically
    this.dataPoints.sort((dp1, dp2) => {
      const bytes32dataFeedId1Hexlified = hexlify(dp1.serializeDataFeedId());
      const bytes32dataFeedId2Hexlified = hexlify(dp2.serializeDataFeedId());
      const comparisonResult = bytes32dataFeedId1Hexlified.localeCompare(
        bytes32dataFeedId2Hexlified
      );
      assert(
        comparisonResult !== 0,
        `Duplicated dataFeedId found: ${dp1.dataFeedId}`
      );
      return comparisonResult;
    });
    return concat(this.dataPoints.map((dp) => dp.toBytes()));
  }
 
  protected serializeTimestamp(): Uint8Array {
    return convertIntegerNumberToBytes(
      this.timestampMilliseconds,
      TIMESTAMP_BS
    );
  }
 
  protected serializeDataPointsCount(): Uint8Array {
    return convertIntegerNumberToBytes(
      this.dataPoints.length,
      DATA_POINTS_COUNT_BS
    );
  }
 
  protected serializeDefaultDataPointByteSize(): Uint8Array {
    return convertIntegerNumberToBytes(
      this.getEachDataPointByteSize(),
      DATA_POINT_VALUE_BYTE_SIZE_BS
    );
  }
}