Initial commit

This commit is contained in:
Pascal Engélibert 2025-06-12 20:10:41 +02:00
commit 0f27dc05f4
12 changed files with 6681 additions and 0 deletions

1
g1bridge/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

5620
g1bridge/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

25
g1bridge/Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "g1bridge"
version = "0.1.0"
edition = "2024"
[dependencies]
argp = "0.4.0"
codec = { package = "parity-scale-codec", version = "3.6.12" }
log = "0.4.27"
scrypt = { version = "0.11.0", default-features = false }
serde = { version = "1.0.219", features = ["derive"] }
simplelog = "0.12.2"
sp-core = { git = "https://github.com/duniter/duniter-polkadot-sdk.git", branch = "duniter-substrate-v1.11.0" }
sp-runtime = { git = "https://github.com/duniter/duniter-polkadot-sdk.git", branch = "duniter-substrate-v1.11.0" }
subxt = { git = 'https://github.com/duniter/subxt', branch = 'subxt-v0.35.3-duniter-substrate-v1.11.0', default-features = false, features = [
"substrate-compat",
"native",
"jsonrpsee",
] }
tiny_http = "0.12.0"
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
typed-sled = "0.2.3"
[profile.release]
lto = "fat"

BIN
g1bridge/res/metadata.scale Normal file

Binary file not shown.

9
g1bridge/rustfmt.toml Normal file
View file

@ -0,0 +1,9 @@
hard_tabs = true
newline_style = "Unix"
imports_granularity = "Crate"
unstable_features = true
format_code_in_doc_comments = true
format_macro_bodies = true
format_macro_matchers = true
format_strings = true

100
g1bridge/src/blockchain.rs Normal file
View file

@ -0,0 +1,100 @@
use crate::{DebtG2B, Opt};
use log::error;
use runtime::balances::calls::types::TransferKeepAlive;
use subxt::{
OnlineClient,
backend::{Backend, BackendExt},
ext::sp_core::{
Pair as _,
ed25519::{Pair, Public},
},
tx::{PairSigner, Signer},
};
use tokio::sync::mpsc::Receiver;
use typed_sled::Tree;
#[subxt::subxt(
runtime_metadata_path = "res/metadata.scale",
derive_for_all_types = "Debug"
)]
pub mod runtime {}
pub type Client = subxt::OnlineClient<Runtime>;
pub type AccountId = subxt::utils::AccountId32;
pub type IdtyId = u32;
pub type BlockNumber = u32;
pub type TxProgress = subxt::tx::TxProgress<Runtime, Client>;
pub type Balance = u64;
pub type AccountData =
runtime::runtime_types::pallet_duniter_account::types::AccountData<Balance, IdtyId>;
pub type AccountInfo = runtime::runtime_types::frame_system::AccountInfo<u32, AccountData>;
pub type Hash = sp_core::H256;
// declare runtime types
pub enum Runtime {}
impl subxt::config::Config for Runtime {
type AssetId = ();
type Hash = Hash;
type AccountId = AccountId;
type Address = sp_runtime::MultiAddress<Self::AccountId, u32>;
type Signature = sp_runtime::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<BlockNumber, Self::Hasher>;
type ExtrinsicParams = subxt::config::DefaultExtrinsicParams<Self>;
}
#[derive(Copy, Clone, Debug, Default, codec::Encode)]
pub struct Tip {
#[codec(compact)]
tip: u64,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
}
}
impl From<u64> for Tip {
fn from(n: u64) -> Self {
Self::new(n)
}
}
pub enum Msg {
NewDebt,
Timer,
}
pub async fn run(
url: String,
pair: Pair,
mut recv: Receiver<DebtG2B>,
tree_debt_g2b: Tree<DebtG2B, ()>,
) {
let mut client = OnlineClient::<Runtime>::from_url(&url).await.ok();
while let Some(msg) = recv.recv().await {
if client.is_none() {
client = OnlineClient::<Runtime>::from_url(&url).await.ok();
}
if let Some(client) = &client {
match client
.tx()
.sign_and_submit_then_watch_default(
&runtime::tx().balances().transfer_keep_alive(
subxt::utils::MultiAddress::Id(msg.address.clone()),
msg.amount,
),
&subxt::tx::PairSigner::new(pair),
)
.await
{
Ok(_) => {
tree_debt_g2b.remove(&msg).ok();
}
Err(e) => {
error!("Submitting transaction: {e:?}");
}
}
}
}
}

147
g1bridge/src/main.rs Normal file
View file

@ -0,0 +1,147 @@
#![feature(try_blocks)]
mod blockchain;
use argp::FromArgs;
use serde::{Deserialize, Serialize, de};
use sp_core::Pair as _;
use std::{
io::{BufRead, Read},
str::FromStr,
sync::Arc,
};
use subxt::{
ext::sp_core::ed25519::{Pair, Public},
utils::{AccountId32, MultiAddress},
};
use tiny_http::{Response, ResponseBox, Server, ServerConfig};
use tokio::sync::mpsc::{Sender, channel};
use typed_sled::Tree;
/// Game to Blockchain
const TREE_DEBT_G2B: &'static str = "debt_g2b";
/// Blob
#[derive(FromArgs)]
struct Opt {
/// path to the database
#[argp(option, short = 'd')]
db: String,
/// secret key is legacy (two passwords on the two first lines)
#[argp(switch, short = 'l')]
legacy: bool,
/// path to the file containing the secret key in Substrate format
#[argp(option, short = 's')]
secret_key: String,
/// duniter node URL (default: ws://127.0.0.1:9944)
#[argp(option, short = 'u', default = "String::from(\"ws://127.0.0.1:9944\")")]
url: String,
}
#[derive(Deserialize, Serialize)]
struct DebtG2B {
address: AccountId32,
amount: u64,
}
#[tokio::main]
async fn main() {
let opt: Opt = argp::parse_args_or_exit(argp::DEFAULT);
let mut sk_file = std::fs::File::open(opt.secret_key).expect("Cannot open secret key file");
let pair: Pair = if opt.legacy {
let sk_reader = std::io::BufReader::new(sk_file);
let mut sk_lines = sk_reader.lines();
let psw1 = sk_lines
.next()
.expect("Secret key file should contain at least 2 lines for the 2 passwords")
.unwrap();
let psw2 = sk_lines
.next()
.expect("Secret key file should contain at least 2 lines for the 2 passwords")
.unwrap();
let params = scrypt::Params::new(12, 16, 1, 32).unwrap();
let mut seed = [0u8; 32];
scrypt::scrypt(
psw2.trim().as_bytes(),
psw1.trim().as_bytes(),
&params,
&mut seed,
)
.unwrap();
Pair::from_seed(&seed)
} else {
let mut sk_content = String::new();
sk_file
.read_to_string(&mut sk_content)
.expect("Cannot read secret key file");
Pair::from_string(sk_content.trim(), None).expect("Cannot decode secret key")
};
simplelog::SimpleLogger::init(log::LevelFilter::Debug, simplelog::Config::default()).unwrap();
let db = typed_sled::open(opt.db).expect("Cannot open database");
let tree_debt_g2b = Tree::<DebtG2B, ()>::open(&db, TREE_DEBT_G2B);
let (bc_send, bc_recv) = channel(128);
tokio::spawn(blockchain::run(
opt.url,
pair,
bc_recv,
tree_debt_g2b.clone(),
));
let server = Server::http("127.0.0.1:30061").expect("Cannot start server");
struct Locals {
tree_debt_g2b: Tree<DebtG2B, ()>,
bc_send: Sender<DebtG2B>,
}
tokio::task_local! {
static LOCALS: Arc<Locals>;
};
LOCALS.scope(
Arc::new(Locals {
tree_debt_g2b,
bc_send,
}),
async move {
for request in server.incoming_requests() {
let locals = LOCALS.get();
tokio::spawn(async move {
let url = request.url();
let mut url_items = url.split('/').filter(|item| !item.is_empty());
let resp: Option<ResponseBox> = try {
match url_items.next() {
Some("send") => {
let address = AccountId32::from_str(url_items.next()?).ok()?;
let amount = url_items.next()?.parse::<u64>().ok()?;
println!("{address} {amount}");
let debt = DebtG2B { address, amount };
locals.tree_debt_g2b.insert(&debt, &()).ok()?;
locals.bc_send.send(debt).await.ok()?;
Response::empty(200).boxed()
}
_ => None?,
}
};
if let Some(resp) = resp {
request.respond(resp).ok();
} else {
request.respond(Response::empty(400)).ok();
}
});
}
},
).await;
}