mirror of
https://github.com/alexgo-io/redstone-node.git
synced 2026-06-18 19:57:02 +08:00
EVM signer for single prices implemented (#7)
* evm signer for single prices implemented * commented code removed * minor fixes after Kuba's review and tests improvement
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"arweaveKeysFile": "./.secrets/arweave-keyfile-33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA.json",
|
||||
"useManifestFromSmartContract": "true",
|
||||
"minimumArBalance": 0.1,
|
||||
"addEvmSignature": true,
|
||||
"credentials": {
|
||||
"infuraProjectId": "XXX",
|
||||
"ethereumPrivateKey": "0x1111111111111111111111111111111111111111111111111111111111111111"
|
||||
|
||||
@@ -4,7 +4,6 @@ import Transaction from "arweave/node/lib/transaction";
|
||||
import aggregators from "./aggregators";
|
||||
import broadcaster from "./broadcasters/lambda-broadcaster";
|
||||
import ArweaveProxy from "./arweave/ArweaveProxy";
|
||||
import EvmPriceSigner from "./utils/EvmPriceSigner";
|
||||
import {trackEnd, trackStart, printTrackingState} from "./utils/performance-tracker";
|
||||
import {Manifest, NodeConfig, PriceDataAfterAggregation, PriceDataSigned, SignedPricePackage} from "./types";
|
||||
import mode from "../mode";
|
||||
@@ -12,6 +11,7 @@ import ManifestHelper, {TokensBySource} from "./manifest/ManifestParser";
|
||||
import ArweaveService from "./arweave/ArweaveService";
|
||||
import PricesService, {PricesBeforeAggregation, PricesDataFetched} from "./fetchers/PricesService";
|
||||
import {mergeObjects, readJSON, sleep} from "./utils/objects";
|
||||
import PriceSignerService from "./signers/PriceSignerService";
|
||||
|
||||
const logger = require("./utils/logger")("runner") as Consola;
|
||||
const pjson = require("../package.json") as any;
|
||||
@@ -27,8 +27,8 @@ export default class NodeRunner {
|
||||
private currentManifest?: Manifest;
|
||||
private pricesService?: PricesService;
|
||||
private tokensBySource?: TokensBySource;
|
||||
private evmSigner?: EvmPriceSigner;
|
||||
private newManifest?: Manifest;
|
||||
private priceSignerService?: PriceSignerService;
|
||||
|
||||
private constructor(
|
||||
private readonly arweaveService: ArweaveService,
|
||||
@@ -155,11 +155,21 @@ export default class NodeRunner {
|
||||
private async doProcessTokens(): Promise<void> {
|
||||
logger.info("Processing tokens");
|
||||
|
||||
// Fetching and aggregating
|
||||
const aggregatedPrices: PriceDataAfterAggregation[] = await this.fetchPrices();
|
||||
const arTransaction: Transaction = await this.arweaveService.prepareArweaveTransaction(aggregatedPrices, this.version);
|
||||
const signedPrices: PriceDataSigned[] = await this.arweaveService.signPrices(
|
||||
aggregatedPrices, arTransaction.id, this.providerAddress);
|
||||
const arTransaction: Transaction = await this.arweaveService.prepareArweaveTransaction(
|
||||
aggregatedPrices,
|
||||
this.version);
|
||||
const pricesReadyForSigning = this.pricesService!.preparePricesForSigning(
|
||||
aggregatedPrices,
|
||||
arTransaction.id,
|
||||
this.providerAddress);
|
||||
|
||||
// Signing
|
||||
const signedPrices: PriceDataSigned[] =
|
||||
await this.priceSignerService!.signPrices(pricesReadyForSigning);
|
||||
|
||||
// Broadcasting
|
||||
await NodeRunner.broadcastPrices(signedPrices);
|
||||
await this.broadcastEvmPricePackage(signedPrices);
|
||||
|
||||
@@ -169,7 +179,6 @@ export default class NodeRunner {
|
||||
logger.info(
|
||||
`Transaction posting skipped in non-prod env: ${arTransaction.id}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async fetchPrices(): Promise<PriceDataAfterAggregation[]> {
|
||||
@@ -219,9 +228,7 @@ export default class NodeRunner {
|
||||
logger.info("Broadcasting price package");
|
||||
const packageBroadcastingTrackingId = trackStart("package-broadcasting");
|
||||
try {
|
||||
const signedPackage = this.evmSigner!.getSignedPackage(
|
||||
signedPrices,
|
||||
this.nodeConfig.credentials.ethereumPrivateKey);
|
||||
const signedPackage = this.priceSignerService!.signPricePackage(signedPrices);
|
||||
await this.broadcastSignedPricePackage(signedPackage);
|
||||
logger.info("Package broadcasting completed");
|
||||
} catch (e) {
|
||||
@@ -315,7 +322,13 @@ export default class NodeRunner {
|
||||
this.currentManifest = newManifest;
|
||||
this.pricesService = new PricesService(newManifest, this.nodeConfig.credentials);
|
||||
this.tokensBySource = ManifestHelper.groupTokensBySource(newManifest);
|
||||
this.evmSigner = new EvmPriceSigner(this.version, this.currentManifest.evmChainId);
|
||||
this.priceSignerService = new PriceSignerService({
|
||||
arweaveService: this.arweaveService,
|
||||
ethereumPrivateKey: this.nodeConfig.credentials.ethereumPrivateKey,
|
||||
evmChainId: newManifest.evmChainId,
|
||||
version: this.version,
|
||||
addEvmSignature: Boolean(this.nodeConfig.addEvmSignature),
|
||||
});
|
||||
this.newManifest = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
Manifest,
|
||||
PriceDataAfterAggregation,
|
||||
PriceDataBeforeSigning,
|
||||
PriceDataSigned
|
||||
PriceDataSigned,
|
||||
} from "../types";
|
||||
import ArweaveProxy from "./ArweaveProxy";
|
||||
import {trackEnd, trackStart} from "../utils/performance-tracker";
|
||||
@@ -69,39 +69,13 @@ export default class ArweaveService {
|
||||
}
|
||||
}
|
||||
|
||||
async signPrices(
|
||||
prices: PriceDataAfterAggregation[],
|
||||
idArTransaction: string,
|
||||
providerAddress: string
|
||||
): Promise<PriceDataSigned[]> {
|
||||
const signingTrackingId = trackStart("signing");
|
||||
|
||||
const signedPrices: PriceDataSigned[] = [];
|
||||
|
||||
for (const price of prices) {
|
||||
logger.info(`Signing price: ${price.id}`);
|
||||
|
||||
//TODO: check if signing in parallel would improve performance - https://app.clickup.com/t/k391rf
|
||||
const signed: PriceDataSigned = await this.signPrice({
|
||||
...price,
|
||||
permawebTx: idArTransaction,
|
||||
provider: providerAddress,
|
||||
});
|
||||
|
||||
signedPrices.push(signed);
|
||||
}
|
||||
trackEnd(signingTrackingId);
|
||||
|
||||
return signedPrices;
|
||||
}
|
||||
|
||||
async getCurrentManifest(): Promise<Manifest> {
|
||||
const jwkAddress = await this.arweave.getAddress();
|
||||
const result = await providersRegistry.currentManifest(jwkAddress, false, this.arweave.jwk);
|
||||
return result.manifest.activeManifestContent;
|
||||
}
|
||||
|
||||
private async signPrice(price: PriceDataBeforeSigning): Promise<PriceDataSigned> {
|
||||
async signPrice(price: PriceDataBeforeSigning): Promise<PriceDataSigned> {
|
||||
const priceWithSortedProps = deepSortObject(price);
|
||||
const priceStringified = JSON.stringify(priceWithSortedProps);
|
||||
const signature = await this.arweave.sign(priceStringified);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Manifest,
|
||||
PriceDataAfterAggregation,
|
||||
PriceDataBeforeAggregation,
|
||||
PriceDataBeforeSigning,
|
||||
PriceDataFetched
|
||||
} from "../types";
|
||||
import {trackEnd, trackStart} from "../utils/performance-tracker";
|
||||
@@ -152,6 +153,23 @@ export default class PricesService {
|
||||
return aggregatedPrices;
|
||||
}
|
||||
|
||||
preparePricesForSigning(
|
||||
prices: PriceDataAfterAggregation[],
|
||||
idArTransaction: string,
|
||||
providerAddress: string): PriceDataBeforeSigning[] {
|
||||
const pricesBeforeSigning: PriceDataBeforeSigning[] = [];
|
||||
|
||||
for (const price of prices) {
|
||||
pricesBeforeSigning.push({
|
||||
...price,
|
||||
permawebTx: idArTransaction,
|
||||
provider: providerAddress,
|
||||
});
|
||||
}
|
||||
|
||||
return pricesBeforeSigning;
|
||||
}
|
||||
|
||||
private maxPriceDeviationPercent(priceSymbol: string): number {
|
||||
const result = ManifestHelper.getMaxDeviationForSymbol(priceSymbol, this.manifest);
|
||||
if (result === null) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
PricePackage,
|
||||
ShortSinglePrice,
|
||||
SignedPricePackage,
|
||||
PriceDataSigned,
|
||||
} from "../types";
|
||||
import _ from "lodash";
|
||||
|
||||
@@ -51,21 +50,6 @@ export default class EvmPriceSigner {
|
||||
};
|
||||
}
|
||||
|
||||
getSignedPackage(prices: PriceDataSigned[], privateKey: string) {
|
||||
if (prices.length === 0) {
|
||||
throw new Error("Price package should contain at least one price");
|
||||
}
|
||||
|
||||
const pricePackage = {
|
||||
timestamp: prices[0].timestamp,
|
||||
prices: prices.map(p => _.pick(p, ["symbol", "value"])),
|
||||
};
|
||||
|
||||
return this.signPricePackage(
|
||||
pricePackage,
|
||||
privateKey);
|
||||
}
|
||||
|
||||
signPricePackage(pricePackage: PricePackage, privateKey: string): SignedPricePackage {
|
||||
const data: any = {
|
||||
types: {
|
||||
79
src/signers/PriceSignerService.ts
Normal file
79
src/signers/PriceSignerService.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import {Consola} from "consola";
|
||||
import _ from "lodash";
|
||||
import EvmPriceSigner from "./EvmPriceSigner";
|
||||
import ArweaveService from "../arweave/ArweaveService";
|
||||
import { PriceDataBeforeSigning, PriceDataSigned, SignedPricePackage } from "../types";
|
||||
import { trackStart, trackEnd } from "../utils/performance-tracker";
|
||||
|
||||
const logger = require("../utils/logger")("ArweaveService") as Consola;
|
||||
|
||||
interface PriceSignerConfig {
|
||||
version: string;
|
||||
evmChainId: number;
|
||||
ethereumPrivateKey: string;
|
||||
arweaveService: ArweaveService;
|
||||
addEvmSignature: boolean;
|
||||
};
|
||||
|
||||
// Business service that supplies signing operations required by Redstone-Node
|
||||
export default class PriceSignerService {
|
||||
private arweaveService: ArweaveService;
|
||||
private evmSigner: EvmPriceSigner;
|
||||
private ethereumPrivateKey: string;
|
||||
private addEvmSignature: boolean;
|
||||
|
||||
constructor(config: PriceSignerConfig) {
|
||||
this.evmSigner = new EvmPriceSigner(config.version, config.evmChainId);
|
||||
this.arweaveService = config.arweaveService;
|
||||
this.ethereumPrivateKey = config.ethereumPrivateKey;
|
||||
this.addEvmSignature = config.addEvmSignature;
|
||||
}
|
||||
|
||||
async signPrices(prices: PriceDataBeforeSigning[], ): Promise<PriceDataSigned[]> {
|
||||
const signingTrackingId = trackStart("signing");
|
||||
const signedPrices: PriceDataSigned[] = [];
|
||||
|
||||
try {
|
||||
for (const price of prices) {
|
||||
logger.info(`Signing price: ${price.id}`);
|
||||
const signedPrice = await this.signSinglePrice(price);
|
||||
signedPrices.push(signedPrice);
|
||||
}
|
||||
return signedPrices;
|
||||
} finally {
|
||||
trackEnd(signingTrackingId);
|
||||
}
|
||||
}
|
||||
|
||||
async signSinglePrice(price: PriceDataBeforeSigning): Promise<PriceDataSigned> {
|
||||
logger.info(`Signing price with arweave signer: ${price.id}`);
|
||||
const signedPrice = await this.arweaveService.signPrice(price);
|
||||
|
||||
if (this.addEvmSignature) {
|
||||
logger.info(`Signing price with evm signer: ${price.id}`);
|
||||
const packageWithSinglePrice = this.evmSigner.signPricePackage({
|
||||
prices: [_.pick(price, ["symbol", "value"])],
|
||||
timestamp: price.timestamp,
|
||||
}, this.ethereumPrivateKey);
|
||||
signedPrice.evmSignature = packageWithSinglePrice.signature;
|
||||
}
|
||||
|
||||
|
||||
return signedPrice;
|
||||
}
|
||||
|
||||
signPricePackage(prices: PriceDataSigned[]): SignedPricePackage {
|
||||
if (prices.length === 0) {
|
||||
throw new Error("Price package should contain at least one price");
|
||||
}
|
||||
|
||||
const pricePackage = {
|
||||
timestamp: prices[0].timestamp,
|
||||
prices: prices.map(p => _.pick(p, ["symbol", "value"])),
|
||||
};
|
||||
|
||||
return this.evmSigner.signPricePackage(
|
||||
pricePackage,
|
||||
this.ethereumPrivateKey);
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ export interface PriceDataBeforeSigning extends PriceDataAfterAggregation {
|
||||
|
||||
export interface PriceDataSigned extends PriceDataBeforeSigning {
|
||||
signature: string;
|
||||
evmSignature?: string;
|
||||
};
|
||||
|
||||
export interface ShortSinglePrice {
|
||||
@@ -81,7 +82,7 @@ export interface PricePackage {
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export type SignedPricePackage = {
|
||||
export interface SignedPricePackage {
|
||||
pricePackage: PricePackage;
|
||||
signer: string;
|
||||
signature: string;
|
||||
@@ -95,6 +96,7 @@ export interface ArweaveTransactionTags {
|
||||
export interface NodeConfig {
|
||||
arweaveKeysFile: string;
|
||||
useManifestFromSmartContract?: boolean;
|
||||
addEvmSignature?: boolean;
|
||||
manifestFile: string;
|
||||
minimumArBalance: number;
|
||||
credentials: Credentials;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ethers } from "ethers";
|
||||
import { SignedPricePackage, PricePackage } from "../src/types";
|
||||
import EvmPriceSigner from "../src/utils/EvmPriceSigner";
|
||||
import EvmPriceSigner from "../src/signers/EvmPriceSigner";
|
||||
|
||||
const evmSigner = new EvmPriceSigner();
|
||||
const ethereumPrivateKey = ethers.Wallet.createRandom().privateKey;
|
||||
|
||||
@@ -25,6 +25,18 @@ jest.mock("../../src/arweave/ArweaveProxy", () => {
|
||||
return jest.fn().mockImplementation(() => mockArProxy);
|
||||
});
|
||||
|
||||
jest.mock("../../src/signers/EvmPriceSigner", () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
signPricePackage: (pricePackage: any) => ({
|
||||
signature: "mock_evm_signed",
|
||||
signer: "mock_evm_signer",
|
||||
pricePackage,
|
||||
}),
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
jest.mock("../../src/fetchers/coinbase");
|
||||
jest.mock("../../src/fetchers/kraken");
|
||||
|
||||
@@ -69,6 +81,7 @@ describe("NodeRunner", () => {
|
||||
infuraProjectId: "ipid",
|
||||
ethereumPrivateKey: "0x1111111111111111111111111111111111111111111111111111111111111111"
|
||||
},
|
||||
addEvmSignature: true,
|
||||
manifestFile: "",
|
||||
minimumArBalance: 0.2
|
||||
}
|
||||
@@ -239,6 +252,7 @@ describe("NodeRunner", () => {
|
||||
"http://broadcast.test/prices",
|
||||
[
|
||||
{
|
||||
"evmSignature": "mock_evm_signed",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"permawebTx": "mockArTransactionId",
|
||||
"provider": "mockArAddress",
|
||||
@@ -255,8 +269,8 @@ describe("NodeRunner", () => {
|
||||
"http://broadcast.test/packages",
|
||||
{
|
||||
timestamp: 111111111,
|
||||
signature: "0x5b2dd26ee75261b8a9c25b4f3eb8bd44292f4e1aeae9867b6f9a9a61a0b98e397be8b1eb1c972a58ac1baec3d2caefe273a39ee0606a66b6bd3c2d1b8db471471c",
|
||||
signer: "0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A",
|
||||
signature: "mock_evm_signed",
|
||||
signer: "mock_evm_signer",
|
||||
provider: "mockArAddress"
|
||||
}
|
||||
);
|
||||
@@ -295,27 +309,66 @@ describe("NodeRunner", () => {
|
||||
);
|
||||
|
||||
await sut.run();
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
mode.broadcasterUrl + "/prices",
|
||||
[
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"source": {
|
||||
"coinbase": 444,
|
||||
"kraken": 445
|
||||
},
|
||||
"symbol": "BTC",
|
||||
"timestamp": 111111111,
|
||||
"version": "0.4",
|
||||
"value": 444.5,
|
||||
"permawebTx": "mockArTransactionId",
|
||||
"provider": "mockArAddress",
|
||||
"signature": "mock_signed",
|
||||
"source": {"coinbase": 444, "kraken": 445},
|
||||
"symbol": "BTC",
|
||||
"timestamp": 111111111,
|
||||
"value": 444.5,
|
||||
"version": "0.4"
|
||||
"evmSignature": "mock_evm_signed"
|
||||
}
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
expect(mockArProxy.postTransaction).toHaveBeenCalledWith({
|
||||
"id": "mockArTransactionId"
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("should broadcast prices without evm signature when addEvmSignature is not set", async () => {
|
||||
const sut = await NodeRunner.create(
|
||||
jwk,
|
||||
{
|
||||
...nodeConfig,
|
||||
addEvmSignature: false,
|
||||
}
|
||||
);
|
||||
|
||||
await sut.run();
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
mode.broadcasterUrl + "/prices",
|
||||
[
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"source": {
|
||||
"coinbase": 444,
|
||||
"kraken": 445
|
||||
},
|
||||
"symbol": "BTC",
|
||||
"timestamp": 111111111,
|
||||
"version": "0.4",
|
||||
"value": 444.5,
|
||||
"permawebTx": "mockArTransactionId",
|
||||
"provider": "mockArAddress",
|
||||
"signature": "mock_signed"
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
describe("when useManifestFromSmartContract flag is set", () => {
|
||||
let nodeConfigManifestFromAr: any;
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user