This commit is contained in:
Pieter
2022-12-01 13:57:08 +01:00
parent bda89c0d3e
commit b1dd92c285
44 changed files with 845 additions and 0 deletions
@@ -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>&nbsp;";
var imgVisitFlag = @"<img src='img/" + g.VisitCountry.Replace(" ", "") + @".png' width=14>&nbsp;";
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 its an append-only ledger table and that data cannot be modified.&nbsp;</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/shes 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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

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:&nbsp;</label>
<input type="text" id="flname" name="flname" required>
</div>
-->
<div id='form-group'>
<label for="betamount">Bet Amount:&nbsp;</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>