mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
HP smart contract nodejs library. (#101)
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
FROM hpcore:latest as hpcore-nodejs
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential
|
||||
RUN apt-get install -y curl
|
||||
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
FROM hpcore-nodejs
|
||||
|
||||
RUN /hp/hpcore new /echo_contract
|
||||
COPY ./examples/echo_contract/contract.js /echo_contract/bin/contract.js
|
||||
RUN mv /echo_contract/cfg/hp.cfg /echo_contract/cfg/hp.json
|
||||
RUN node -p "JSON.stringify({...require('/echo_contract/cfg/hp.json'), binary:'/usr/bin/node', binargs:'./bin/contract.js' }, null, 2)" > /echo_contract/cfg/hp.cfg
|
||||
RUN rm /echo_contract/cfg/hp.json
|
||||
|
||||
ENTRYPOINT ["/hp/hpcore", "run", "/echo_contract"]
|
||||
@@ -1,48 +0,0 @@
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('There was an uncaught error', err)
|
||||
})
|
||||
const fs = require('fs')
|
||||
|
||||
//console.log("===Sample contract started===");
|
||||
const hpargs = JSON.parse(fs.readFileSync(0, 'utf8'));
|
||||
//console.log(hpargs);
|
||||
|
||||
// We just save execution args as an example state file change.
|
||||
if (!hpargs.readonly)
|
||||
fs.appendFileSync("exects.txt", "ts:" + hpargs.ts + "\n");
|
||||
|
||||
Object.keys(hpargs.usrfd).forEach(function (key) {
|
||||
const userfds = hpargs.usrfd[key];
|
||||
|
||||
if (userfds[0] != -1) {
|
||||
const userinput = fs.readFileSync(userfds[0], 'utf8');
|
||||
|
||||
// Append user input to a state file if not in read only mode.
|
||||
if (!hpargs.readonly)
|
||||
fs.appendFileSync("userinputs.txt", userinput + "\n");
|
||||
|
||||
if (userinput == "ts")
|
||||
fs.writeSync(userfds[1], fs.readFileSync("exects.txt"));
|
||||
else
|
||||
fs.writeSync(userfds[1], "Echoing: " + userinput);
|
||||
}
|
||||
});
|
||||
|
||||
if (!hpargs.readonly) {
|
||||
|
||||
if (hpargs.nplfd[0] != -1) {
|
||||
const nplinput = fs.readFileSync(hpargs.nplfd[0], 'utf8');
|
||||
console.log("Input received from peers:");
|
||||
console.log(nplinput);
|
||||
fs.writeSync(hpargs.nplfd[1], "Echoing: " + nplinput);
|
||||
}
|
||||
|
||||
if (hpargs.hpfd[0] != -1) {
|
||||
const hpinput = fs.readFileSync(hpargs.hpfd[0], 'utf8');
|
||||
console.log("Input received from hp:");
|
||||
console.log(hpinput);
|
||||
fs.writeSync(hpargs.hpfd[1], "Echoing: " + hpinput);
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("===Sample contract ended===");
|
||||
1
examples/hpclient/.gitignore
vendored
1
examples/hpclient/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
30
examples/nodejs_contract/echo_contract.js
Normal file
30
examples/nodejs_contract/echo_contract.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { HotPocketContract } = require("./hp-contract-lib");
|
||||
const fs = require('fs');
|
||||
|
||||
const hpc = new HotPocketContract();
|
||||
|
||||
//console.log("===Echo contract started===");
|
||||
|
||||
// We just save execution timestamp as an example state file change.
|
||||
if (!hpc.readonly)
|
||||
fs.appendFileSync("exects.txt", "ts:" + hpc.timestamp + "\n");
|
||||
|
||||
Object.keys(hpc.users).forEach(function (key) {
|
||||
|
||||
const user = hpc.users[key];
|
||||
const inputBuf = user.readInput();
|
||||
if (inputBuf) {
|
||||
const userInput = inputBuf.toString("utf8");
|
||||
|
||||
// Append user input to a state file if not in read only mode.
|
||||
if (!hpc.readonly)
|
||||
fs.appendFileSync("userinputs.txt", userInput + "\n");
|
||||
|
||||
if (userInput == "ts")
|
||||
user.sendOutput(fs.readFileSync("exects.txt"));
|
||||
else
|
||||
user.sendOutput("Echoing: " + userInput);
|
||||
}
|
||||
});
|
||||
|
||||
//console.log("===Echo contract ended===");
|
||||
27
examples/nodejs_contract/hp-contract-lib.js
Normal file
27
examples/nodejs_contract/hp-contract-lib.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const fs = require('fs');
|
||||
|
||||
function HotPocketContract() {
|
||||
const hpargs = JSON.parse(fs.readFileSync(0, 'utf8'));
|
||||
this.readonly = hpargs.readonly;
|
||||
this.timestamp = hpargs.ts;
|
||||
this.users = {};
|
||||
|
||||
Object.keys(hpargs.usrfd).forEach((userPubKey) => {
|
||||
const userfds = hpargs.usrfd[userPubKey];
|
||||
this.users[userPubKey] = new HotPocketChannel(userfds[0], userfds[1]);
|
||||
});
|
||||
}
|
||||
|
||||
function HotPocketChannel(infd, outfd) {
|
||||
this.readInput = function () {
|
||||
return infd == -1 ? null : fs.readFileSync(infd);
|
||||
}
|
||||
|
||||
this.sendOutput = function (output) {
|
||||
fs.writeFileSync(outfd, output);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HotPocketContract
|
||||
}
|
||||
2
examples/todo_contract/.gitignore
vendored
2
examples/todo_contract/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
bin
|
||||
obj
|
||||
@@ -1,19 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ToDoContract
|
||||
{
|
||||
public class DataContext : DbContext
|
||||
{
|
||||
public DbSet<ToDoEntry> ToDoEntries { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseSqlite("Data Source=state/todo.db");
|
||||
}
|
||||
|
||||
public class ToDoEntry
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Content { get; set; }
|
||||
public string CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Mono.Unix.Native;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace HotPocket
|
||||
{
|
||||
public static class HotPocketHelper
|
||||
{
|
||||
const int FD_READ_BUFFER_LEN = 1024;
|
||||
|
||||
public static async Task<ContractArgs> GetContractArgsAsync()
|
||||
{
|
||||
using (var s = new StreamReader(Console.OpenStandardInput()))
|
||||
{
|
||||
var input = await s.ReadToEndAsync();
|
||||
var contractArgs = JsonConvert.DeserializeObject<ContractArgs>(input);
|
||||
return contractArgs;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadStringFromFD(int fd)
|
||||
{
|
||||
return Encoding.UTF8.GetString(ReadBytesFromFD(fd));
|
||||
}
|
||||
|
||||
public static void WriteStringToFD(int fd, string str)
|
||||
{
|
||||
WriteBytesToFD(fd, Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
public static unsafe byte[] ReadBytesFromFD(int fd)
|
||||
{
|
||||
// Keep reading the fd bytes and fill the memory stream until no more bytes.
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
int readbytes = 0;
|
||||
|
||||
do
|
||||
{
|
||||
var buffer = new byte[FD_READ_BUFFER_LEN];
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
IntPtr ptr = (IntPtr)p;
|
||||
readbytes = (int)Syscall.read(fd, ptr, FD_READ_BUFFER_LEN);
|
||||
}
|
||||
|
||||
ms.Write(buffer, 0, readbytes);
|
||||
|
||||
} while (readbytes == FD_READ_BUFFER_LEN);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe void WriteBytesToFD(int fd, byte[] buffer)
|
||||
{
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
IntPtr ptr = (IntPtr)p;
|
||||
Syscall.write(fd, ptr, (ulong)buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace HotPocket
|
||||
{
|
||||
public class ContractArgs
|
||||
{
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[JsonProperty("pubkey")]
|
||||
public string PubKey { get; set; }
|
||||
|
||||
[JsonProperty("ts")]
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
[JsonProperty("hpfd")]
|
||||
public IOPipe HotPocketPipe { get; set; }
|
||||
|
||||
[JsonProperty("nplfd")]
|
||||
public IOPipe NplPipe { get; set; }
|
||||
|
||||
[JsonProperty("usrfd")]
|
||||
public Dictionary<string, IOPipe> UserPipes { get; set; }
|
||||
|
||||
[JsonProperty("unl")]
|
||||
public string[] Unl { get; set; }
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(IOPipeJsonConverter))]
|
||||
public class IOPipe
|
||||
{
|
||||
public int ReadFD { get; set; }
|
||||
public int WriteFD { get; set; }
|
||||
}
|
||||
|
||||
public class IOPipeJsonConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(IOPipe);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
var array = JArray.Load(reader);
|
||||
var pipe = (existingValue as IOPipe ?? new IOPipe());
|
||||
pipe.ReadFD = (int)array.ElementAtOrDefault(0);
|
||||
pipe.WriteFD = (int)array.ElementAtOrDefault(1);
|
||||
return pipe;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var pipe = (IOPipe)value;
|
||||
serializer.Serialize(writer, new[] { pipe.ReadFD, pipe.WriteFD });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using ToDoContract;
|
||||
|
||||
namespace ToDoContract.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20200114133142_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.0");
|
||||
|
||||
modelBuilder.Entity("ToDoContract.ToDoEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ToDoEntries");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace ToDoContract.Migrations
|
||||
{
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ToDoEntries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Content = table.Column<string>(nullable: true),
|
||||
CreatedBy = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ToDoEntries", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ToDoEntries");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using ToDoContract;
|
||||
|
||||
namespace ToDoContract.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
partial class DataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.0");
|
||||
|
||||
modelBuilder.Entity("ToDoContract.ToDoEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ToDoEntries");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using HotPocket;
|
||||
using System.Linq;
|
||||
|
||||
namespace ToDoContract
|
||||
{
|
||||
/*
|
||||
* This is a simple multi-user ToDo list contract which uses sqlite database as storage.
|
||||
* In order to run this .Net Core should be installed on the system. If using docker,
|
||||
* mcr.microsoft.com/dotnet/core/sdk:3.1 docker image must be used.
|
||||
*
|
||||
* Produce deployable output with: dotnet publish -c Release
|
||||
*
|
||||
* User inputs can be submitted in the following format.
|
||||
* Insert a new ToDo record: add <title>
|
||||
* Retrieve all records owned by user: get all
|
||||
* Retrieve record by ID: get <id>
|
||||
* Delete all records owned by user: delete all
|
||||
* Delete record by ID: delete <id>
|
||||
*/
|
||||
public class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting .Net ToDo contract");
|
||||
|
||||
using (var dataContext = new DataContext())
|
||||
{
|
||||
dataContext.Database.Migrate();
|
||||
}
|
||||
|
||||
ContractArgs contractArgs = await HotPocketHelper.GetContractArgsAsync();
|
||||
|
||||
foreach (var user in contractArgs.UserPipes)
|
||||
{
|
||||
var pubkey = user.Key;
|
||||
var pipe = user.Value;
|
||||
|
||||
var input = HotPocketHelper.ReadStringFromFD(pipe.ReadFD);
|
||||
if (string.IsNullOrEmpty(input))
|
||||
continue;
|
||||
|
||||
var output = await HandleUserInputAsync(pubkey, input);
|
||||
HotPocketHelper.WriteStringToFD(pipe.WriteFD, output);
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<string> HandleUserInputAsync(string userId, string input)
|
||||
{
|
||||
var parts = input.Trim().Split(' ', 2);
|
||||
if (parts.Length < 2)
|
||||
return "Invalid input format";
|
||||
|
||||
var command = parts[0].ToLower();
|
||||
var param = parts[1];
|
||||
|
||||
using (var dataContext = new DataContext())
|
||||
{
|
||||
if (command == "add") // add new record.
|
||||
{
|
||||
var entry = new ToDoEntry
|
||||
{
|
||||
Content = param,
|
||||
CreatedBy = userId
|
||||
};
|
||||
dataContext.ToDoEntries.Add(entry);
|
||||
|
||||
await dataContext.SaveChangesAsync();
|
||||
|
||||
return "Added entry with id " + entry.Id;
|
||||
}
|
||||
else if (command == "get" || command == "delete")
|
||||
{
|
||||
if (param == "all") // get/delete all records.
|
||||
{
|
||||
// Get all entries belonging to this user.
|
||||
var entries = await dataContext.ToDoEntries.Where(e => e.CreatedBy == userId).OrderBy(e => e.Id).ToListAsync();
|
||||
|
||||
if (command == "get")
|
||||
{
|
||||
return JsonConvert.SerializeObject(entries.Select(e => $"ID-{e.Id}: {e.Content}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete all records for this user.
|
||||
dataContext.RemoveRange(entries);
|
||||
await dataContext.SaveChangesAsync();
|
||||
return $"{entries.Count} record(s) deleted";
|
||||
|
||||
}
|
||||
}
|
||||
else // get/delete by ID.
|
||||
{
|
||||
int id = 0;
|
||||
if (int.TryParse(param, out id))
|
||||
{
|
||||
var entry = await dataContext.ToDoEntries.FirstOrDefaultAsync(e => e.Id == id);
|
||||
if (entry == null)
|
||||
{
|
||||
return $"Record id {id} does not exist";
|
||||
}
|
||||
else if (entry.CreatedBy != userId)
|
||||
{
|
||||
return $"You do not have permission for record id {id}";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (command == "get")
|
||||
{
|
||||
return entry.Content;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataContext.Remove(entry);
|
||||
await dataContext.SaveChangesAsync();
|
||||
return $"Record id {id} deleted";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Invalid record id";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Invalid command";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -176,7 +176,8 @@ namespace sc
|
||||
* "hpfd": [fd0, fd1],
|
||||
* "usrfd":{ "<pkhex>":[fd0, fd1], ... },
|
||||
* "nplfd":[fd0, fd1],
|
||||
* "unl":[ "pkhex", ... ]
|
||||
* "unl":[ "pkhex", ... ],
|
||||
* "readonly": <true|false>
|
||||
* }
|
||||
*/
|
||||
int write_contract_args(const execution_context &ctx)
|
||||
|
||||
@@ -47,12 +47,12 @@ do
|
||||
# Update contract config.
|
||||
node -p "JSON.stringify({...require('./tmp.json'), \
|
||||
binary: '/usr/local/bin/node', \
|
||||
binargs: '/contract/bin/contract.js', \
|
||||
binargs: '/contract/bin/echo_contract.js', \
|
||||
appbill: '', \
|
||||
appbillargs: '', \
|
||||
peerport: ${peerport}, \
|
||||
pubport: ${pubport}, \
|
||||
roundtime: 1000, \
|
||||
roundtime: 2000, \
|
||||
loglevel: 'debug', \
|
||||
loggers:['console', 'file'] \
|
||||
}, null, 2)" > hp.cfg
|
||||
@@ -65,9 +65,12 @@ do
|
||||
|
||||
# Copy the contract executable and appbill.
|
||||
mkdir ./node$n/bin
|
||||
cp ../../../examples/echo_contract/contract.js ./node$n/bin/contract.js
|
||||
cp ../../../examples/nodejs_contract/{package.json,echo_contract.js,hp-contract-lib.js} ./node$n/bin/
|
||||
cp ../bin/appbill ./node$n/bin/
|
||||
# cp -r ../../../examples/todo_contract/bin/Release/netcoreapp3.1/publish/* ./node$n/bin/
|
||||
|
||||
pushd ./node$n/bin > /dev/null 2>&1
|
||||
npm install
|
||||
popd
|
||||
done
|
||||
|
||||
# Function to generate JSON array string while skiping a given index.
|
||||
|
||||
3
test/vm-cluster/.gitignore
vendored
3
test/vm-cluster/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
cfg
|
||||
vmpass.txt
|
||||
iplist.txt
|
||||
iplist.txt
|
||||
hpfiles
|
||||
@@ -1,10 +1,15 @@
|
||||
#!/bin/bash
|
||||
# HotPocket VM cluster setup script.
|
||||
|
||||
# Usage example: ./cluster.sh run 1
|
||||
# Usage examples:
|
||||
# ./cluster.sh new
|
||||
# ./cluster.sh update
|
||||
# ./cluster.sh run 1
|
||||
|
||||
# VM login password must exist in vmpass.txt
|
||||
# All VMs must use same SSH password with username 'geveo'
|
||||
vmpass=$(cat vmpass.txt)
|
||||
# List vm IP addresses of the cluster must exist in iplist.txt
|
||||
# List of vm IP addresses/domain names of the cluster must exist in iplist.txt
|
||||
# (This list will be treated as the node numbers 1,2.3... from topmost IP to the bottom)
|
||||
readarray -t vmips < iplist.txt
|
||||
|
||||
@@ -13,18 +18,19 @@ mode=$1
|
||||
|
||||
hpcore=$(realpath ../..)
|
||||
|
||||
if [ "$mode" = "new" ] || [ "$mode" = "update" ] || [ "$mode" = "run" ] || [ "$mode" = "check" ] || \
|
||||
[ "$mode" = "monitor" ] || [ "$mode" = "kill" ] || [ "$mode" = "reboot" ] || [ "$mode" = "ssh" ] || \
|
||||
[ "$mode" = "dns" ] || [ "$mode" = "ssl" ]; then
|
||||
if [ "$mode" = "new" ] || [ "$mode" = "update" ] || [ "$mode" = "reconfig" ] || [ "$mode" = "run" ] || \
|
||||
[ "$mode" = "check" ] || [ "$mode" = "monitor" ] || [ "$mode" = "kill" ] || [ "$mode" = "reboot" ] || \
|
||||
[ "$mode" = "ssh" ] || [ "$mode" = "dns" ] || [ "$mode" = "ssl" ]; then
|
||||
echo "mode: $mode"
|
||||
else
|
||||
echo "Invalid command. [ new | update | run <N> | check <N> | monitor <N> | kill <N> | reboot <N> | ssh <N> <custom command> | dns <N> <zerossl file> | ssl <N> ] expected."
|
||||
echo "Invalid command. [ new | update | reconfig | run <N> | check <N> | monitor <N> | kill <N> | reboot <N> | ssh <N> <custom command> | dns <N> <zerossl file> | ssl <N> ] expected."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Command modes:
|
||||
# new - Install hot pocket dependencies and hot pocket with example contracts to each vm.
|
||||
# update - Deploy updated hot pocket and example binaries into each vm.
|
||||
# reconfig - Reconfigures the entire cluster using already uploaded HP binaries.
|
||||
# run - Run hot pocket of specified vm node.
|
||||
# check - Check hot pocket running status of specified vm node.
|
||||
# monitor - Monitor streaming hot pocket console output (if running) of specified vm node.
|
||||
@@ -37,7 +43,7 @@ fi
|
||||
if [ $mode = "run" ]; then
|
||||
let nodeid=$2-1
|
||||
vmip=${vmips[$nodeid]}
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip 'nohup sudo ./hpcore run contract'
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip 'nohup sudo ~/hpfiles/bin/hpcore run ~/contract'
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip 'tail -f nohup.out'
|
||||
exit 0
|
||||
fi
|
||||
@@ -108,17 +114,19 @@ if [ $mode = "ssl" ]; then
|
||||
sshpass -f vmpass.txt scp ~/Downloads/$vmip/certs/* geveo@$vmip:~/contract/cfg/
|
||||
|
||||
rm -r ~/Downloads/$vmip
|
||||
echo "Done"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir ./cfg > /dev/null 2>&1
|
||||
|
||||
for (( i=0; i<$vmcount; i++ ))
|
||||
do
|
||||
vmip=${vmips[i]}
|
||||
let n=$i+1
|
||||
/bin/bash ./setup-vm.sh $mode $n $vmpass $vmip $hpcore &
|
||||
done
|
||||
# Run binary file setup for entire cluster.
|
||||
if [ $mode = "new" ] || [ $mode = "update" ]; then
|
||||
for (( i=0; i<$vmcount; i++ ))
|
||||
do
|
||||
vmip=${vmips[i]}
|
||||
let n=$i+1
|
||||
/bin/bash ./setup-vm.sh $mode $n $vmpass $vmip $hpcore &
|
||||
done
|
||||
fi
|
||||
|
||||
wait
|
||||
|
||||
@@ -126,7 +134,22 @@ if [ $mode = "update" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Following code will only be executed in 'new' mode.
|
||||
# All code below this will only execute in 'new' or 'reconfig' mode.
|
||||
# Update all nodes hp.cfg files to be part of the same UNL cluster.
|
||||
|
||||
if [ $mode = "reconfig" ]; then
|
||||
mkdir ./cfg > /dev/null 2>&1
|
||||
for (( i=0; i<$vmcount; i++ ))
|
||||
do
|
||||
# Run hp setup script on the VM and download the generated hp.cfg
|
||||
vmip=${vmips[i]}
|
||||
let nodeid=$i+1
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip '~/hpfiles/setup-hp.sh'
|
||||
sshpass -f vmpass.txt scp geveo@$vmip:~/contract/cfg/hp.cfg ./cfg/node$nodeid.json
|
||||
done
|
||||
fi
|
||||
|
||||
# Locally update values of download hp.cfg files.
|
||||
|
||||
for (( i=0; i<$vmcount; i++ ))
|
||||
do
|
||||
@@ -172,9 +195,17 @@ do
|
||||
mypeers=$(joinarr peers $j)
|
||||
myunl=$(joinarr pubkeys $j)
|
||||
|
||||
node -p "JSON.stringify({...require('./cfg/node$n.json'),binary:'/usr/bin/node',binargs:'/home/geveo/contract.js',peers:${mypeers},unl:${myunl},loggers:['console', 'file']}, null, 2)" > ./cfg/node$n.cfg
|
||||
node -p "JSON.stringify({...require('./cfg/node$n.json'), \
|
||||
binary:'/usr/bin/node', \
|
||||
binargs:'/home/geveo/hpfiles/nodejs_contract/echo_contract.js', \
|
||||
peers:${mypeers}, \
|
||||
unl:${myunl}, \
|
||||
roundtime: 2000, \
|
||||
loglevel: 'debug', \
|
||||
loggers:['console', 'file'] \
|
||||
}, null, 2)" > ./cfg/node$n.cfg
|
||||
|
||||
# Copy local cfg file back to remote vm.
|
||||
# Upload local hp.cfg file back to remote vm.
|
||||
vmip=${vmips[j]}
|
||||
sshpass -f vmpass.txt scp ./cfg/node$n.cfg geveo@$vmip:~/contract/cfg/hp.cfg
|
||||
done
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ ! -f /swapfile ]]
|
||||
then
|
||||
echo "Adding 5GB swap space..."
|
||||
sudo fallocate -l 5G /swapfile
|
||||
sudo chmod 600 /swapfile
|
||||
sudo mkswap /swapfile
|
||||
sudo swapon /swapfile
|
||||
fi
|
||||
|
||||
if [ -x "$(command -v node)" ]; then
|
||||
echo "NodeJs already installed."
|
||||
else
|
||||
@@ -15,14 +24,18 @@ if [ -x "$(command -v fusermount3)" ]; then
|
||||
else
|
||||
echo "Installing FUSE and other shared libraries..."
|
||||
sudo apt-get -y install libgomp1
|
||||
sudo cp ./libfuse3.so.3 ./libb2.so.1 /usr/local/lib/
|
||||
sudo cp ~/hpfiles/bin/{libfuse3.so.3,libb2.so.1} /usr/local/lib/
|
||||
sudo ldconfig
|
||||
sudo cp ./fusermount3 /usr/local/bin/
|
||||
sudo cp ~/hpfiles/bin/fusermount3 /usr/local/bin/
|
||||
fi
|
||||
|
||||
sudo rm -r ~/contract > /dev/null 2>&1
|
||||
./hpcore new ./contract
|
||||
pushd ./contract/cfg > /dev/null 2>&1
|
||||
|
||||
echo "Creating new contract directory..."
|
||||
~/hpfiles/bin/hpcore new ~/contract
|
||||
|
||||
pushd ~/contract/cfg > /dev/null 2>&1
|
||||
echo "Generating default ssl certs..."
|
||||
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem \
|
||||
-subj "/C=AU/ST=ST/L=L/O=O/OU=OU/CN=localhost/emailAddress=hp@example" > /dev/null 2>&1
|
||||
popd > /dev/null 2>&1
|
||||
|
||||
@@ -8,27 +8,27 @@ hpcore=$5
|
||||
|
||||
echo $nodeid. $vmip
|
||||
|
||||
# Copy required files to hpfiles dir.
|
||||
mkdir -p hpfiles/{bin,nodejs_contract}
|
||||
|
||||
strip $hpcore/build/hpcore
|
||||
cp $hpcore/build/hpcore hpfiles/bin/
|
||||
cp $hpcore/examples/nodejs_contract/{package.json,echo_contract.js,hp-contract-lib.js} hpfiles/nodejs_contract/
|
||||
if [ $mode = "new" ]; then
|
||||
cp ../bin/{libfuse3.so.3,libb2.so.1,fusermount3,websocketd,websocat,hpfs} hpfiles/bin/
|
||||
cp ./setup-hp.sh hpfiles/
|
||||
fi
|
||||
|
||||
echo "Uploading hp files..."
|
||||
sshpass -f vmpass.txt scp -rp hpfiles geveo@$vmip:~/
|
||||
echo "Upload finished."
|
||||
|
||||
if [ $mode = "new" ]; then
|
||||
|
||||
sshpass -f vmpass.txt scp $hpcore/build/hpcore \
|
||||
$hpcore/examples/echo_contract/contract.js \
|
||||
../bin/libfuse3.so.3 \
|
||||
../bin/libb2.so.1 \
|
||||
../bin/fusermount3 \
|
||||
../bin/websocketd \
|
||||
../bin/websocat \
|
||||
../bin/hpfs \
|
||||
./consensus-test-continuous.sh \
|
||||
./setup-hp.sh \
|
||||
geveo@$vmip:~/
|
||||
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip 'chmod 700 ~/consensus-test-continuous.sh && chmod 700 ~/setup-hp.sh && ~/setup-hp.sh'
|
||||
# Run hp setup script on the VM and download the generated hp.cfg
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip '~/hpfiles/setup-hp.sh && cd ~/hpfiles/nodejs_contract && npm install'
|
||||
sshpass -f vmpass.txt ssh geveo@$vmip 'echo sudo ~/hpfiles/bin/hpcore run ~/contract > ~/run.sh && sudo chmod +x ~/run.sh'
|
||||
mkdir ./cfg > /dev/null 2>&1
|
||||
sshpass -f vmpass.txt scp geveo@$vmip:~/contract/cfg/hp.cfg ./cfg/node$nodeid.json
|
||||
else
|
||||
sshpass -f vmpass.txt scp $hpcore/build/hpcore \
|
||||
$hpcore/examples/echo_contract/contract.js \
|
||||
./consensus-test-continuous.sh \
|
||||
geveo@$vmip:~/
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -r hpfiles
|
||||
Reference in New Issue
Block a user