Web App
@@ -0,0 +1,8 @@
|
||||
*.dll
|
||||
*.obj
|
||||
*.sig.txt
|
||||
*.json
|
||||
bin/
|
||||
obj/
|
||||
properties/
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,285 @@
|
||||
// This code is designed to be as simple as possible, not pulling in lots of libraries and frameworks such as EF and MVC.
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
|
||||
Args = args,
|
||||
ApplicationName = typeof(Program).Assembly.FullName,
|
||||
ContentRootPath = Directory.GetCurrentDirectory(),
|
||||
WebRootPath = Directory.GetCurrentDirectory()
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
app.UseRouting();
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
string htmlIndexStart = File.ReadAllText("indexStart.html");
|
||||
string htmlError = File.ReadAllText("error.html");
|
||||
string htmlSuccess = File.ReadAllText("success.html");
|
||||
|
||||
// this code connects to SQL as the user of the web app
|
||||
const string connString = @"Server=.\SQL2022;Database=WorldCup;Trusted_Connection=True;";
|
||||
|
||||
// root folder
|
||||
app.MapGet("/", async context => {
|
||||
Console.WriteLine($"Connection from {context.Connection.RemoteIpAddress}");
|
||||
|
||||
// get list of moneylines
|
||||
using var con = new SqlConnection(connString);
|
||||
con.Open();
|
||||
|
||||
using var cmd = new SqlCommand("SELECT * from [dbo].[MoneyLine]", con);
|
||||
var rows = cmd.ExecuteReader();
|
||||
|
||||
// create array of games from the moneyline data
|
||||
List<Game> games = new List<Game>();
|
||||
while(rows.Read()) {
|
||||
var game = new Game((int)rows.GetValue(0),
|
||||
(string)rows.GetValue(1),
|
||||
(int)rows.GetValue(2),
|
||||
(int)rows.GetValue(3),
|
||||
(string)rows.GetValue(4),
|
||||
(int)rows.GetValue(5),
|
||||
(DateTime)rows.GetValue(6));
|
||||
games.Add(game);
|
||||
}
|
||||
|
||||
// Build the resulting HTML on the fly!
|
||||
// One <TR> per game, multiple <TD>s
|
||||
var sbHtml = new StringBuilder(htmlIndexStart);
|
||||
const string TR=@"<TR>", TD = @"<TD>", SpanTD=@"<TD colspan=3>";
|
||||
const string EndTR = @"</TR>", EndTD = @"</TD>";
|
||||
const string Radio = @"<input type='radio' name='bet' value='{0}|{1}|{2}|{3}'>";
|
||||
foreach (var g in games) {
|
||||
|
||||
// Country vs Country heading
|
||||
sbHtml.Append(TR);
|
||||
sbHtml.Append(SpanTD);
|
||||
var imgHomeFlag = @"<img src='img/" + g.HomeCountry.Replace(" ", "") + @".png' width=14> ";
|
||||
var imgVisitFlag = @"<img src='img/" + g.VisitCountry.Replace(" ", "") + @".png' width=14> ";
|
||||
sbHtml.Append(imgHomeFlag + g.HomeCountry + " vs " + imgVisitFlag + g.VisitCountry); // + " on " + g.GameDateTime);
|
||||
sbHtml.Append(EndTD);
|
||||
sbHtml.Append(EndTR);
|
||||
|
||||
// The three sets of odds
|
||||
sbHtml.Append(TR);
|
||||
// Win
|
||||
sbHtml.Append(TD);
|
||||
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "W", g.HomeCountryOdds));
|
||||
sbHtml.Append(g.HomeCountry + " " + g.HomeCountryOdds);
|
||||
sbHtml.Append(EndTD);
|
||||
|
||||
// Draw
|
||||
sbHtml.Append(TD);
|
||||
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "D", g.DrawOdds));
|
||||
sbHtml.Append("Draw " + g.DrawOdds);
|
||||
sbHtml.Append(EndTD);
|
||||
|
||||
// Loss
|
||||
sbHtml.Append(TD);
|
||||
sbHtml.Append(string.Format(Radio, g.MoneyLineID, g.HomeCountry, "L", g.VisitCountryOdds));
|
||||
sbHtml.Append(g.VisitCountry + " " + g.VisitCountryOdds);
|
||||
sbHtml.Append(EndTD);
|
||||
sbHtml.Append(EndTR);
|
||||
|
||||
sbHtml.Append("\n");
|
||||
}
|
||||
|
||||
// close off the HTML page
|
||||
sbHtml.Append("</table><p></p><input type='submit' value='Place Bet' style='height:70px; width:200px; font-size:1.5em; color:#FFFFFF; background-color:#808080'></form></body></html>");
|
||||
|
||||
|
||||
await context.Response.WriteAsync(sbHtml.ToString());
|
||||
});
|
||||
|
||||
// place a bet and insert into SQL
|
||||
|
||||
app.MapGet("/placebet", async context => {
|
||||
//string name = context.Request.Query["flname"];
|
||||
string amount = context.Request.Query["betamount"];
|
||||
string moneyline = context.Request.Query["bet"];
|
||||
string name = "Pieter Vanhove";
|
||||
Console.WriteLine($"Request for bet from {name} for {amount} on {moneyline}");
|
||||
|
||||
string fName = "", lName = "";
|
||||
string Country = "", result = "";
|
||||
var odds = 0;
|
||||
var moneylineId = 0;
|
||||
var amount2 = 0;
|
||||
|
||||
// this validates the three input args and if there're no errors, returns them in the ref args
|
||||
// moneyline is a string of four values separated by a '|'
|
||||
if (!ValidateRequest(name,
|
||||
amount,
|
||||
moneyline,
|
||||
ref fName,
|
||||
ref lName,
|
||||
ref moneylineId,
|
||||
ref Country,
|
||||
ref result,
|
||||
ref odds,
|
||||
ref amount2)) {
|
||||
await context.Response.WriteAsync(htmlError);
|
||||
} else {
|
||||
var cs = connString;
|
||||
|
||||
using var con = new SqlConnection(cs);
|
||||
con.Open();
|
||||
|
||||
using (var cmd = new SqlCommand("usp_PlaceBet", con)) {
|
||||
cmd.Parameters.AddWithValue("@MoneylineID", moneylineId);
|
||||
cmd.Parameters.AddWithValue("@FirstName", fName);
|
||||
cmd.Parameters.AddWithValue("@LastName", lName);
|
||||
cmd.Parameters.AddWithValue("@Country", Country);
|
||||
cmd.Parameters.AddWithValue("@Bet", amount2);
|
||||
cmd.Parameters.AddWithValue("@Odds", odds);
|
||||
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// add the receipt tp the resulting confirmation page
|
||||
string? digest = GetLedgerDigest(con);
|
||||
if (!string.IsNullOrEmpty(digest)) {
|
||||
string sigFilename = CreateDownloadableSigBlock(digest);
|
||||
htmlSuccess = htmlSuccess.Replace("%F%", sigFilename);
|
||||
|
||||
await context.Response.WriteAsync(htmlSuccess);
|
||||
} else {
|
||||
await context.Response.WriteAsync("Unable to download receipt.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get SQL Server version
|
||||
app.MapGet("/version", async context => {
|
||||
string? v = GetSqlVersion();
|
||||
await context.Response.WriteAsync(string.IsNullOrEmpty(v) ? "Unable to get SQL Server version" : v);
|
||||
});
|
||||
|
||||
bool ValidateRequest(string name,
|
||||
string amount,
|
||||
string moneyline,
|
||||
ref string fName,
|
||||
ref string lName,
|
||||
ref int moneylineId,
|
||||
ref string Country,
|
||||
ref string result,
|
||||
ref int odds,
|
||||
ref int amount2) {
|
||||
|
||||
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(amount) && !string.IsNullOrEmpty(moneyline)) {
|
||||
// Get user name
|
||||
string[] flname = name.Split(' ', 2);
|
||||
if (flname.Count() != 2)
|
||||
return false;
|
||||
|
||||
// get bet amount
|
||||
UInt32 intAmount = 0;
|
||||
if (!UInt32.TryParse(amount, out intAmount))
|
||||
return false;
|
||||
|
||||
// moneyline is four fields:
|
||||
// FirstName|LastName|Wager|Odds
|
||||
const int NUM_FIELDS = 4;
|
||||
string[] moneylineItems = moneyline.Split('|', NUM_FIELDS);
|
||||
if (moneylineItems.Count() != NUM_FIELDS)
|
||||
return false;
|
||||
|
||||
if (!Int32.TryParse(amount, out amount2))
|
||||
return false;
|
||||
|
||||
// get user's name
|
||||
fName = flname[0];
|
||||
lName = flname[1];
|
||||
|
||||
// get bet details
|
||||
if (!Int32.TryParse(moneylineItems[0], out moneylineId))
|
||||
return false;
|
||||
|
||||
// Home country and Win, Draw or Loss
|
||||
Country = moneylineItems[1];
|
||||
result = moneylineItems[2];
|
||||
|
||||
// odds
|
||||
if (!Int32.TryParse(moneylineItems[3], out odds))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// SQL query to get SQL Server version
|
||||
string? GetSqlVersion() {
|
||||
using var con = new SqlConnection(connString);
|
||||
con.Open();
|
||||
|
||||
using var cmd = new SqlCommand("SELECT @@VERSION", con);
|
||||
return cmd.ExecuteScalar()?.ToString();
|
||||
}
|
||||
|
||||
// Get the last tx digest
|
||||
string? GetLedgerDigest(SqlConnection con) {
|
||||
using var cmd = new SqlCommand("sp_generate_database_ledger_digest", con);
|
||||
return cmd.ExecuteScalar()?.ToString();
|
||||
}
|
||||
|
||||
/* NOT USED
|
||||
// sign some data, this creates real signatures,
|
||||
// but the keys are ephemeral for demo purposes only
|
||||
string SignBlob(string text) {
|
||||
const string CRYPTO_VERSION = "01";
|
||||
|
||||
using SHA256 alg = SHA256.Create(); // in future add cryptoagility
|
||||
byte[] data = Encoding.ASCII.GetBytes(text);
|
||||
byte[] hash = alg.ComputeHash(data);
|
||||
|
||||
// for demo only, we should really load a cert/key from the cert store
|
||||
using (RSA rsa = RSA.Create()) {
|
||||
RSAPKCS1SignatureFormatter rsaForm = new(rsa);
|
||||
rsaForm.SetHashAlgorithm(nameof(SHA256));
|
||||
byte[] sig = rsaForm.CreateSignature(hash);
|
||||
var sigBase64 = Convert.ToBase64String(sig);
|
||||
return CRYPTO_VERSION + "|" + sigBase64;
|
||||
}
|
||||
}
|
||||
*/
|
||||
// creates a file with a random name
|
||||
string CreateDownloadableSigBlock(string sig) {
|
||||
var filename = Guid.NewGuid() + ".json";
|
||||
using (var fs = File.Create(filename)) {
|
||||
byte[] b = new UTF8Encoding(true).GetBytes(sig);
|
||||
fs.Write(b,0,b.Length);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
// Start the web app
|
||||
app.Run();
|
||||
|
||||
// struct to hold game details
|
||||
public struct Game {
|
||||
public Game(int MoneyLineID, string HomeCountry, int HomeCountryOdds, int DrawOdds, string VisitCountry, int VisitCountryOdds, DateTime GameDateTime) {
|
||||
this.MoneyLineID = MoneyLineID;
|
||||
this.HomeCountry = HomeCountry;
|
||||
this.HomeCountryOdds = HomeCountryOdds;
|
||||
this.DrawOdds = DrawOdds;
|
||||
this.VisitCountry = VisitCountry;
|
||||
this.VisitCountryOdds = VisitCountryOdds;
|
||||
this.GameDateTime = GameDateTime;
|
||||
}
|
||||
public int MoneyLineID;
|
||||
public string HomeCountry;
|
||||
public int HomeCountryOdds;
|
||||
public int DrawOdds;
|
||||
public string VisitCountry;
|
||||
public int VisitCountryOdds;
|
||||
public DateTime GameDateTime;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
CREATE DATABASE WorldCup
|
||||
GO
|
||||
ALTER DATABASE [WorldCup] SET ALLOW_SNAPSHOT_ISOLATION ON
|
||||
GO
|
||||
USE WorldCup
|
||||
GO
|
||||
CREATE TABLE [dbo].[MoneyLine](
|
||||
[MoneyLineID] [int] IDENTITY(1,1) NOT NULL,
|
||||
[HomeCountry] [nvarchar](50) NOT NULL,
|
||||
[HomeCountryOdds] [INT] NOT NULL,
|
||||
[DrawOdds] [INT] NOT NULL,
|
||||
[VisitCountry] [nvarchar](50) NOT NULL,
|
||||
[VisitCountryOdds] [INT] NOT NULL,
|
||||
[GameDateTime] [datetime2] NOT NULL
|
||||
)
|
||||
WITH
|
||||
(
|
||||
SYSTEM_VERSIONING = ON,
|
||||
LEDGER = ON
|
||||
);
|
||||
GO
|
||||
|
||||
--https://sportsbook.draftkings.com/leagues/soccer/world-cup-2022?category=game-lines&subcategory=moneyline-(regular-time)
|
||||
|
||||
INSERT INTO [dbo].[MoneyLine] ([HomeCountry], [HomeCountryOdds], [DrawOdds], [VisitCountry],[VisitCountryOdds],[GameDateTime])
|
||||
VALUES ('Qatar', 250, 245, 'Ecuador',105,'2022-11-20 17:00:00'),
|
||||
('England', -340, 390, 'Iran', 1000, '2022-11-21 14:00:00' ),
|
||||
('Senegal', 475, 270, 'Netherlands',-165, '2022-11-21 17:00:00'),
|
||||
('USA', 145, 205, 'Wales',205, '2022-11-21 20:00:00'),
|
||||
|
||||
('Argentina', -575, 600, 'Saudi Arabia',1500, '2022-11-22 11:00:00'),
|
||||
('Denmark', -230, 340, 'Tunisia',600, '2022-11-22 14:00:00'),
|
||||
('Mexico', 170, 215, 'Poland',165, '2022-11-22 17:00:00'),
|
||||
('France', -550, 600, 'Australia',1300, '2022-11-22 20:00:00'),
|
||||
|
||||
('Morocco', 370, 230, 'Croatia',-125, '2022-11-23 11:00:00'),
|
||||
('Germany', -280, 400, 'Japan',700, '2022-11-23 14:00:00'),
|
||||
('Spain', -380, 475, 'Costa Rica',1000, '2022-11-23 17:00:00'),
|
||||
('Belgium', -350, 450, 'Canada',900, '2022-11-23 20:00:00'),
|
||||
|
||||
('Switzerland', -120, 240, 'Cameroon',350, '2022-11-24 11:00:00'),
|
||||
('Uruguay', -120, 235, 'South Korea',360, '2022-11-24 14:00:00'),
|
||||
('Portugal', -210, 310, 'Ghana',600, '2022-11-24 17:00:00'),
|
||||
('Brazil', -235, 350, 'Serbia',600, '2022-11-24 20:00:00'),
|
||||
|
||||
('Wales', 120, 215, 'Iran',250, '2022-11-25 11:00:00'),
|
||||
('Qatar', 275, 225, 'Senegal',105, '2022-11-25 14:00:00'),
|
||||
('Netherlands', 155, 300, 'Ecuador',390, '2022-11-25 17:00:00'),
|
||||
('England', 140, 255, 'USA',400, '2022-11-25 20:00:00'),
|
||||
|
||||
('Tunisia', 190, 195, 'Australia',165, '2022-11-26 11:00:00'),
|
||||
('Poland', -140, 255, 'Saudi Arabia',390, '2022-11-26 14:00:00'),
|
||||
('France', -110, 240, 'Denmark',310, '2022-11-26 17:00:00'),
|
||||
('Argentina', -170, 285, 'Mexico',475, '2022-11-26 20:00:00'),
|
||||
|
||||
('Japan', -105, 230, 'Costa Rica',310, '2022-11-27 11:00:00'),
|
||||
('Belgium', -195, 310, 'Morocco',500, '2022-11-27 14:00:00'),
|
||||
('Croatia', -130, 275, 'Canada',340, '2022-11-27 17:00:00'),
|
||||
('Spain', 155, 230, 'Germany',170, '2022-11-27 20:00:00'),
|
||||
|
||||
('Cameroon', 300, 235, 'Serbia',-105, '2022-11-28 11:00:00'),
|
||||
('South Korea', 145, 195, 'Ghana',210, '2022-11-28 14:00:00'),
|
||||
('Brazil', -230, 340, 'Switzerland',600, '2022-11-28 17:00:00'),
|
||||
('Portugal', 110, 225, 'Uruguay',260, '2022-11-28 20:00:00');
|
||||
|
||||
CREATE TABLE [dbo].[Bets](
|
||||
[BetID] [int] IDENTITY(1,1) NOT NULL,
|
||||
[MoneylineID] [int] NOT NULL,
|
||||
[FirstName] [nvarchar](50) NOT NULL,
|
||||
[LastName] [nvarchar](50) NOT NULL,
|
||||
[Country] [nvarchar](50),
|
||||
[Bet] [money] NOT NULL,
|
||||
[Payout] [money] NOT NULL,
|
||||
[BetDateTime] [datetime2] NOT NULL
|
||||
)
|
||||
WITH (LEDGER = ON (APPEND_ONLY = ON));
|
||||
GO
|
||||
|
||||
CREATE FUNCTION fn_CalucatePayout
|
||||
(
|
||||
-- Add the parameters for the function here
|
||||
@Stake decimal(8,2), @Odds decimal(8,2)
|
||||
)
|
||||
RETURNS decimal(8,2)
|
||||
AS
|
||||
BEGIN
|
||||
-- Declare the return variable here
|
||||
DECLARE @Payout decimal(8,2)
|
||||
|
||||
-- Add the T-SQL statements to compute the return value here
|
||||
IF @Odds > 0
|
||||
SET @Payout = @Stake * (@Odds/100) + @Stake
|
||||
ELSE
|
||||
SET @Payout = @Stake / (ABS(@Odds)/100) + @Stake
|
||||
|
||||
-- Return the result of the function
|
||||
RETURN @Payout
|
||||
|
||||
END
|
||||
GO
|
||||
|
||||
-- Calculating Payouts From Positive Moneyline Odds ---- Potential Profit = Stake x (Odds/100) + Stake
|
||||
-- Calculating Payouts From Negative Moneyline Odds ---- Potential Profit = Stake / (Odds/100) + Stake
|
||||
|
||||
CREATE PROCEDURE usp_PlaceBet
|
||||
@MoneylineID INT,
|
||||
@FirstName NVARCHAR(50),
|
||||
@LastName NVARCHAR(50),
|
||||
@Country NVARCHAR(50),
|
||||
@Bet MONEY,
|
||||
@Odds INT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
INSERT INTO [dbo].[Bets] ([MoneylineID], [FirstName], [LastName], [Country], [Bet], [Payout],[BetDateTime]) VALUES (@MoneylineID, @FirstName, @LastName, @Country, @Bet, dbo.fn_CalucatePayout(@Odds,@Bet),GETDATE())
|
||||
END
|
||||
GO
|
||||
/*
|
||||
EXEC usp_PlaceBet @MoneylineID=1, @Firstname='Catherine', @LastName='Abel', @Country='Qatar', @Bet=150, @Odds=250
|
||||
EXEC usp_PlaceBet @MoneylineID=2, @Firstname='Brandon', @LastName='Flowers', @Country='England', @Bet=350, @Odds=-340
|
||||
EXEC usp_PlaceBet @MoneylineID=3, @Firstname='Lenny', @LastName='Kravitz', @Country='Netherlands', @Bet=250, @Odds=-165
|
||||
EXEC usp_PlaceBet @MoneylineID=8, @Firstname='Eric', @LastName='Clapton', @Country='France', @Bet=400, @Odds=-550
|
||||
EXEC usp_PlaceBet @MoneylineID=12, @Firstname='Pieter', @LastName='Vanhove', @Country='Belgium', @Bet=300, @Odds=-350
|
||||
*/
|
||||
|
||||
|
||||
CREATE CREDENTIAL [https://ledgerdemostg.blob.core.windows.net/sqldbledgerdigests]
|
||||
WITH IDENTITY='SHARED ACCESS SIGNATURE',
|
||||
SECRET = 'st=2022-10-18T13:24:48Z&se=2023-10-18T21:24:48Z&si=Ledger&spr=https&sv=2021-06-08&sr=c&sig=KhSroFrZ4HHUn%2B7LldOljrTWqPueV72heqYoaFFjfUk%3D'
|
||||
GO
|
||||
ALTER DATABASE SCOPED CONFIGURATION
|
||||
SET LEDGER_DIGEST_STORAGE_ENDPOINT = 'https://ledgerdemostg.blob.core.windows.net';
|
||||
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE sp_TamperWithBet
|
||||
@PageID int,
|
||||
@ID int,
|
||||
@PayOut money
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DROP TABLE IF EXISTS #DBCCPAGE
|
||||
|
||||
CREATE TABLE #DBCCPAGE
|
||||
(ParentObject NVARCHAR(128),
|
||||
Object NVARCHAR(128),
|
||||
Field NVARCHAR(128),
|
||||
Value NVARCHAR(256))
|
||||
|
||||
|
||||
DECLARE @OffsetPayOut INT
|
||||
DECLARE @DBName VARCHAR(256) = DB_NAME()
|
||||
|
||||
|
||||
INSERT INTO #dbccpage EXEC('DBCC TRACEON(3604) WITH NO_INFOMSGS;
|
||||
|
||||
DBCC PAGE(' + @DBName + ', 1, ' + @PageID + ', 3) WITH NO_INFOMSGS,TABLERESULTS
|
||||
');
|
||||
|
||||
WITH DBCCPAGE_Offset_PayOut (ParentObject,Object,Field,Value)
|
||||
AS
|
||||
|
||||
(
|
||||
SELECT ParentObject, Object, Field,Value FROM #DBCCPAGE
|
||||
WHERE ParentObject=(SELECT ParentObject FROM #DBCCPAGE WHERE Field='BetID' AND Value=@ID)
|
||||
)
|
||||
SELECT @OffsetPayOut=
|
||||
CONVERT(INT,CONVERT(VARBINARY,'0x'+ REPLICATE('0', 8-LEN(SUBSTRING(ParentObject,CHARINDEX('0x',ParentObject)+2,CHARINDEX('Length',ParentObject)-CHARINDEX('0x',ParentObject)-3)))+SUBSTRING(ParentObject,CHARINDEX('0x',ParentObject)+2,CHARINDEX('Length',ParentObject)-CHARINDEX('0x',ParentObject)-3),1)) +
|
||||
CONVERT(INT,CONVERT(VARBINARY,'0x'+ REPLICATE('0', 8-LEN(SUBSTRING(Object,CHARINDEX('0x',Object)+2,CHARINDEX('Length',Object)-charindex('0x',Object)-3)))+SUBSTRING(Object,CHARINDEX('0x',Object)+2,CHARINDEX('Length',Object)-CHARINDEX('0x',Object)-3),1))
|
||||
FROM DBCCPAGE_Offset_PayOut
|
||||
WHERE Field='Payout';
|
||||
|
||||
DECLARE @BinaryPayout VARBINARY(8) = CONVERT(binary(8), REVERSE(CONVERT(VARBINARY(8), @Payout)))
|
||||
DBCC WRITEPAGE(@DBName, 1, @PageID, @OffsetPayOut, 8, @BinaryPayout)
|
||||
END
|
||||
@@ -0,0 +1,280 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "SQL",
|
||||
"display_name": "SQL",
|
||||
"language": "sql"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "sql",
|
||||
"version": ""
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# Ledger - World Cup Betting Demo - SQL Server 2022"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "37b80cef-1217-4d8b-a71e-56b950e42727"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Append-Only Ledger Table Bets"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d7582e04-61cc-4a9e-bc80-fdca578d4042"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"CREATE TABLE [dbo].[Bets](\r\n",
|
||||
"\t[BetID] [INT] IDENTITY(1,1) NOT NULL,\r\n",
|
||||
"\t[MoneylineID] [INT] NOT NULL,\r\n",
|
||||
"\t[FirstName] [NVARCHAR](50) NOT NULL,\r\n",
|
||||
"\t[LastName] [NVARCHAR](50) NOT NULL,\r\n",
|
||||
"\t[Country] [NVARCHAR](50),\r\n",
|
||||
"\t[Bet] [MONEY] NOT NULL,\r\n",
|
||||
"\t[Payout] [MONEY] NOT NULL,\r\n",
|
||||
"\t[BetDateTime] [DATETIME2] NOT NULL\r\n",
|
||||
"\t)\r\n",
|
||||
"WITH (LEDGER = ON (APPEND_ONLY = ON));\r\n",
|
||||
"GO"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e6e09374-8b7c-4432-85e7-f6807ba4376c",
|
||||
"language": "sql",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Let's try to modify the bet"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "1b60ac87-7e65-4479-b99d-13065ba889f3"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"<span style=\"font-family: Calibri, sans-serif; font-size: 11pt;\">The malicious DBA tries to manipulate the faulty record but noticed that it’s an append-only ledger table and that data cannot be modified. </span>"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "54f5b25f-bb4a-4da0-9cc7-52d12d7994d8"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"USE WorldCup\r\n",
|
||||
"GO\r\n",
|
||||
"SELECT * from Bets\r\n",
|
||||
"WHERE FirstName='Pieter' and Lastname='Vanhove'"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "faba49e8-5231-4f89-b152-abc391ce2a56",
|
||||
"tags": [],
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"UPDATE Bets\r\n",
|
||||
"SET Payout=-2400\r\n",
|
||||
"WHERE BetID=5"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "e082ee2f-ec59-4525-9aae-39b91034686f",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Different parties can verify the database to be sure the data can be trusted."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "36de65c8-ee60-4820-8ff5-5c04f2294085"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"DECLARE @digest_locations NVARCHAR(MAX) = (SELECT * FROM sys.database_ledger_digest_locations FOR JSON AUTO, INCLUDE_NULL_VALUES);\r\n",
|
||||
" SELECT @digest_locations as digest_locations;\r\n",
|
||||
" BEGIN TRY\r\n",
|
||||
" EXEC sys.sp_verify_database_ledger_from_digest_storage @digest_locations;\r\n",
|
||||
" SELECT 'Ledger verification succeeded.' AS Result;\r\n",
|
||||
" END TRY\r\n",
|
||||
" BEGIN CATCH\r\n",
|
||||
" THROW;\r\n",
|
||||
" END CATCH"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "563e751f-df78-4f23-8618-9e7896316b81",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Let's tamper the data"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d4210ee5-52d9-47fa-b676-a1554f24788b"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"DBA thinks he/she’s smart and tampers with the data directly into the data file by using a stored procedure."
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "703c9df6-e5c0-49e1-b841-522ebd5a088d"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"SELECT sys.fn_PhysLocFormatter(%%physloc%%) PageId, *\r\n",
|
||||
"FROM Bets\r\n",
|
||||
"WHERE BetID=5 --Copy the ID from the previous result set"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "8dc3c14a-a420-4d91-b7c4-6fba20736af5",
|
||||
"language": "sql",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"EXECUTE sp_TamperWithBet\r\n",
|
||||
"\t@PageID=568, \r\n",
|
||||
"\t@ID=5,\r\n",
|
||||
"\t@PayOut=-2400"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "4963b815-b14e-42b7-a0ee-59bbb40e98d8",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"Show that the bet was changed"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "9aa4a76e-28fb-4d39-89a2-335b665606f3"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"SELECT * from Bets\r\n",
|
||||
"WHERE FirstName='Pieter' and Lastname='Vanhove'"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6f32bd7d-debf-4242-ac66-dc7f00257215",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Verify the database again"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "309aba7a-bc54-4971-8e79-400b3933d906"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"DECLARE @digest_locations NVARCHAR(MAX) = (SELECT * FROM sys.database_ledger_digest_locations FOR JSON AUTO, INCLUDE_NULL_VALUES);\r\n",
|
||||
" SELECT @digest_locations as digest_locations;\r\n",
|
||||
" BEGIN TRY\r\n",
|
||||
" EXEC sys.sp_verify_database_ledger_from_digest_storage @digest_locations;\r\n",
|
||||
" SELECT 'Ledger verification succeeded.' AS Result;\r\n",
|
||||
" END TRY\r\n",
|
||||
" BEGIN CATCH\r\n",
|
||||
" THROW;\r\n",
|
||||
" END CATCH"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "3020ef07-2e35-4a9f-abe4-afb3a55e5539",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Review the history of the transaction"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "8a5a03e1-2306-41ec-be89-bc3065d65d30"
|
||||
},
|
||||
"attachments": {}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"SELECT\r\n",
|
||||
" t.[commit_time] AS [CommitTime] \r\n",
|
||||
"\t, t.[principal_name] AS [UserName]\r\n",
|
||||
" ,l.[MoneylineID]\r\n",
|
||||
" ,l.[FirstName]\r\n",
|
||||
" ,l.[LastName]\r\n",
|
||||
" ,l.[Country]\r\n",
|
||||
" ,l.[Bet]\r\n",
|
||||
" ,l.[Payout]\r\n",
|
||||
" ,l.[BetDateTime]\r\n",
|
||||
"\t, l.[ledger_operation_type_desc] AS Operation\r\n",
|
||||
"\tFROM [dbo].[Bets_Ledger] l\r\n",
|
||||
"\tJOIN [sys].[database_ledger_transactions] t\r\n",
|
||||
"\tON t.[transaction_id] = l.[ledger_transaction_id]\r\n",
|
||||
"\tWHERE t.transaction_id=1166;"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d3bf1ca4-c66f-4ba8-a863-0811e120de51",
|
||||
"language": "sql"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
p,h1, label {font-family: "Calibri", "Arial", Gadget, sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
<b>Error</b> Please enter valid items: A full name (first and last), a wager amount in USD, and an actual selected bet.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 641 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {background-color: black;}
|
||||
p,h1, label {font-family: "Century Gothic", Gadget, sans-serif; color: white; }
|
||||
p,h3, label {font-family: "Century Gothic", Gadget, sans-serif; color: white; }
|
||||
tr:nth-child(odd) { background-color: rgb(58, 57, 57); border: 0px; font-weight: bold }
|
||||
tr, th {vertical-align: middle; font-family: "Century Gothic", "Arial", Gadget, sans-serif; border: 0px; padding: 15px; color: white; }
|
||||
td {vertical-align: middle; font-family: "Century Gothic", "Arial", Gadget, sans-serif; border: 0px; width: 200px; padding: 5px}
|
||||
td:hover, td.selected {background-color: #b9b9b9 }
|
||||
input { margin-bottom: 5px; width: 10%;}
|
||||
#form-group label { display: inline-block;width: 150px; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<script>
|
||||
$("td").click(function(){
|
||||
$(this).addClass("selected").siblings().removeClass("selected");
|
||||
});
|
||||
</script>
|
||||
<p style="text-align: right ;">Hello <br> Pieter Vanhove </br></p>
|
||||
|
||||
<center>
|
||||
<img src="img/ball.jpg" width="650">
|
||||
|
||||
<body>
|
||||
<p style="text-align: center ;">Pieter Vanhove</p>
|
||||
<p id="betting">
|
||||
|
||||
<form action="/placebet" id="formMakeBet" method="get">
|
||||
<!--
|
||||
<div id='form-group'>
|
||||
<label for="flname">Your Name: </label>
|
||||
<input type="text" id="flname" name="flname" required>
|
||||
</div>
|
||||
-->
|
||||
<div id='form-group'>
|
||||
<label for="betamount">Bet Amount: </label>
|
||||
<input type="text" id="betamount" name="betamount" required>
|
||||
</div>
|
||||
<table border=1>
|
||||
</center>
|
||||
<!-- <tr>
|
||||
<th>Home Country</th>
|
||||
<th>Home Country Odds</th>
|
||||
<th>Draw Odds</th>
|
||||
<th>Visiting Country</th>
|
||||
<th>Visiting Country Odds</th>
|
||||
<th>Game Date</th>
|
||||
</tr> -->
|
||||
|
||||
<!-- Stuff below is added by C# code -->
|
||||
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {background-color: black;}
|
||||
p,h1, label {font-family: "Century Gothic", Gadget, sans-serif; color: white;}
|
||||
tr:nth-child(even) { background-color: #D6EEEE; }
|
||||
td, tr, th {vertical-align: middle; font-family: "Century Gothic", Gadget, sans-serif; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<center>
|
||||
<img src="img/Congratulations.jpg" width="650">
|
||||
<body>
|
||||
<p>Click <a href='%F%' download>here</a> to download a cryptographic receipt of your bet.</p>
|
||||
<input type="button" onclick="location.href='http://localhost:3000'" value="Place another bet" style='height:70px; width:220px; font-size:1.5em; color:white; background-color:#808080'/>
|
||||
</body>
|
||||
</center>
|
||||
</html>
|
||||