SHOWING: First 60
[Unit]
After=network.target
Description=Ord server
StartLimitBurst=120
StartLimitIntervalSec=10m
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
Environment=RUST_BACKTRACE=1
Environment=RUST_LOG=info
ExecStart=/usr/local/bin/ord \
--bitcoin-data-dir /var/lib/bitcoind \
--data-dir /var/lib/ord \
--config-dir /var/lib/ord \
--chain ${CHAIN} \
--index-sats \
server \
--acme-contact mailto:[email protected] \
--http \
--https
Group=ord
LimitNOFILE=65536
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
Restart=on-failure
RestartSec=5s
StateDirectory=ord
StateDirectoryMode=0700
TimeoutStopSec=10m
Type=simple
User=ord
WorkingDirectory=/var/lib/ord
[Install]
WantedBy=multi-user.target
#44,993,089text### Fixed
- Respect locked coins (#2618)
- Set `Cache-Control: no-store` header on 404 responses (#2637)
- Fix statistics table and increment schema version (#2624)
- Fix broken link in README (#2621)
- Speed up indexing of re-inscriptions (#2608)
- Fix docs rendering (#2612)
- Update docs with new position of --enable-json-api (#2601)
- Move `--enable-json-api` flag to server options (#2599)
- Make server_runs_with_rpc_user_and_pass_as_env_vars test less flaky (#2580)
### Runes
- Implement open etchings (#2548)
- Add more info to /rune page and link to rune from /tx (#2528)
- Display inscription on /rune (#2542)
- Add rune numbers (#2557)
- Ignore non push opcodes in runestones (#2553)
- Improve rune minimum at height (#2546)
[0.10.0](https://github.com/ordinals/ord/releases/tag/0.10.0) - 2023-10-23
--------------------------------------------------------------------------
### Added
- Batch inscriptions (#2504)
- Add teleburn command to generate Ethereum teleburn addresses (#1680)
- Add Korean version of handbook (#2560)#44,993,088text- Add --version flag (#236)
- Bump version: 0.0.2 → 0.0.3 (#234)
- Change deploy target in recipe (#232)
- Use default port and set ambient capabilities in ord service (#230)
- Test deploy on vagrant (#229)
- Update slide deck (#227)
- Add link to video (#226)
- Separate deck pages (#225)
- Fix docs HTML (#224)
- Add side deck (#223)
- Change slot notation to AxBxCxD (#222)
- Improve NFT encoding (#221)
- Remove use of sha256d in signature algorithm (#219)
- Use standard formats (#218)
- Use CBOR for serialization/deserialization (#217)
- Add nix flake (#214)
- Build binaries for releases (#213)
[0.0.1](https://github.com/ordinals/ord/releases/tag/0.0.1) - 2022-06-05
---------------------------------------------------------------------
- Add commands to mint and verify NFTs (#211)
- Add legendary sat location hints (#208)
- Re-implement find (#206)
- Add explanation to bounty page (#205)
- Change bounty dir to bounties (#204)
- Add ordinal bounty page (#203)
- Add drawbacks section to BIP (#202)
- Remove log spam (#200)
#44,993,087text- Add links to homepage (#335)
- Styling (#333)
- Remove fluff from BIP (#336)
- Remove old comment from bitcoind.service (#334)
- Add viewport meta tag (#332)
- Add rarity colors (#330)
- Don't let ordinals become telephone numbers (#331)
- Add next and prev links to /ordinal (#329)
- Fix broken link (#328)
- Add header to /range (#325)
- Fix off by one bug in index::blocks (#326)
- Add header to /output (#324)
- Limit blocks (#320)
- Add header to /tx (#322)
- Add header to /block/HASH (#321)
- Convert / to boilerplate template (#317)
- Return BlockHash from Index::all (#319)
- Don't warn about installing bitcoind in deploy/setup (#318)
- Improvements (#298)
- Update rust toolchain when deploying (#311)
- Fix forbidden word check (#313)
- Don't run integration tests on MacOS CI (#316)
- Disable redb checksums (#315)
- Pay a fixed fee when sending transactions (#314)
- Refactor duplicate blockchain code in purse (#312)
- Add `ord wallet send` (#305)
- Add wallet balance subcommand (#271)
- Add wallet utxos subcommand (#259)#44,993,086textchecksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"#44,993,085text- Use bitcoin core node for integration tests (#263)
- List transaction outputs (#292)
- Add `/output/:outpoint` endpoint (#293)
- Add /range/:start/:end endpoint (#291)
- Move /list endpoint to /api/list (#288)
- List block transactions at `/block/:hash` (#286)
- Display ordinals at `/ordinal/:ordinal` (#287)
- Wait for bitcoind and ord to become available (#285)
- List blocks on root page (#276)
- Remove user-facing list page (#275)
- Add network option (#274)
- Serve HTTPS with ACME certs (#256)
- Remove unused functionality (#270)
- Revise homepage (#268)
- Link to blog post (#267)
- Use hour, minute, second, and third terminology (#262)
- Allow passing ordinals in degree and decimal notation (#261)
- Update dependencies (#258)
- Make genesis sat mythic (#260)
- Add wallet (#233)
- Overhaul traits (#255)
- Clarify duplicate transaction rule in BIP (#254)
- Purge LMDB (#231)
- Add justfile with commands for moving ordinals around manually (#238)
- Add links to discord server (#237)
- Make `nft verify` take input as argument (#235)
#44,993,084textname = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
dependencies = [
"event-listener 4.0.0",
"pin-project-lite",
]
[[package]]
name = "executable-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478"
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"#44,993,083textuse super::*;
#[derive(Debug)]
pub(crate) enum Expected {
String(String),
Regex(Regex),
}
impl Expected {
pub(crate) fn regex(pattern: &str) -> Self {
Self::Regex(Regex::new(&format!("^(?s){pattern}$")).unwrap())
}
pub(crate) fn assert_match(&self, output: &str) {
match self {
Self::String(string) => pretty_assert_eq!(output, string),
Self::Regex(regex) => assert!(
regex.is_match(output),
"regex:\n{regex}\ndid not match output:\n{output}",
),
}
}
}
#44,986,643text
#[test]
fn get_inscriptions_in_block() {
let rpc_server = test_bitcoincore_rpc::spawn();
create_wallet(&rpc_server);
rpc_server.mine_blocks(10);
let envelope = envelope(&[b"ord", &[1], b"text/plain;charset=utf-8", &[], b"bar"]);
let txid = rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[
(1, 0, 0, envelope.clone()),
(2, 0, 0, envelope.clone()),
(3, 0, 0, envelope.clone()),
],
..Default::default()
});
rpc_server.mine_blocks(1);
let _ = rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(4, 0, 0, envelope.clone()), (5, 0, 0, envelope.clone())],
..Default::default()
});
rpc_server.mine_blocks(1);
let _ = rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(6, 0, 0, envelope.clone())],
..Default::default()
});
rpc_server.mine_blocks(1);
let server = TestServer::spawn_with_server_args(
&rpc_server,
&["--index-sats", "--first-inscription-height", "0"],
&["--enable-json-api"],
);
// get all inscriptions from block 11
#44,986,641text confirmations: 0,
difficulty: 0.0,
hash: block_hash,
height,
median_time: None,
merkle_root: TxMerkleNode::all_zeros(),
n_tx: 0,
next_block_hash: None,
nonce: 0,
previous_block_hash: None,
time: 0,
version: Version::ONE,
version_hex: Some(vec![0, 0, 0, 0]),
})
.unwrap(),
)
} else {
match self.state().blocks.get(&block_hash) {
Some(block) => Ok(serde_json::to_value(hex::encode(serialize(&block.header))).unwrap()),
None => Err(Self::not_found()),
}
}
}
fn get_block(
&self,
block_hash: BlockHash,
verbosity: u64,
) -> Result<String, jsonrpc_core::Error> {
assert_eq!(verbosity, 0, "Verbosity level {verbosity} is unsupported");
match self.state().blocks.get(&block_hash) {
Some(block) => Ok(hex::encode(serialize(block))),
None => Err(Self::not_found()),
}
}
fn get_block_count(&self) -> Result<u64, jsonrpc_core::Error> {
#44,986,640text}
#[test]
fn inscriptions_page_has_next_and_previous() {
let rpc_server = test_bitcoincore_rpc::spawn();
create_wallet(&rpc_server);
let (a, _) = inscribe(&rpc_server);
let (b, _) = inscribe(&rpc_server);
let (c, _) = inscribe(&rpc_server);
TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex(
format!("/inscription/{b}"),
format!(
".*<h1>Inscription 1</h1>.*
<div class=inscription>
<a class=prev href=/inscription/{a}>❮</a>
<iframe .* src=/preview/{b}></iframe>
<a class=next href=/inscription/{c}>❯</a>
</div>.*",
),
);
}
#[test]
fn expected_sat_time_is_rounded() {
let rpc_server = test_bitcoincore_rpc::spawn();
TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex(
"/sat/2099999997689999",
r".*<dt>timestamp</dt><dd><time>.* \d+:\d+:\d+ UTC</time> \(expected\)</dd>.*",
);
}
#[test]
#[ignore]
fn server_runs_with_rpc_user_and_pass_as_env_vars() {
let rpc_server = test_bitcoincore_rpc::spawn();
rpc_server.mine_blocks(1);
let tempdir = TempDir::new().unwrap();
#44,986,639text
rpc_server.mine_blocks(1);
assert_eq!(ord.wait_with_output().unwrap().status.code(), Some(0));
}
#[test]
fn export_inscription_number_to_id_tsv() {
let rpc_server = test_bitcoincore_rpc::spawn();
let temp_dir = TempDir::new().unwrap();
create_wallet(&rpc_server);
inscribe(&rpc_server);
inscribe(&rpc_server);
let (inscription, _) = inscribe(&rpc_server);
rpc_server.mine_blocks(1);
let tsv = CommandBuilder::new("index export --tsv foo.tsv")
.rpc_server(&rpc_server)
.temp_dir(temp_dir)
.stdout_regex(r"\{\}\n")
.run_and_extract_file("foo.tsv");
let entries: std::collections::BTreeMap<i64, ord::Object> = tsv
.lines()
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.map(|line| {
let value = line.split('\t').collect::<Vec<&str>>();
let inscription_number = i64::from_str(value[0]).unwrap();
let inscription_id = ord::Object::from_str(value[1]).unwrap();
(inscription_number, inscription_id)
})
.collect();
assert_eq!(
#44,986,638text
rpc_server.mine_blocks(1);
let response = TestServer::spawn_with_server_args(&rpc_server, &[], &["--enable-json-api"])
.json_request("/block/0");
assert_eq!(response.status(), StatusCode::OK);
let block_json: BlockJson = serde_json::from_str(&response.text().unwrap()).unwrap();
assert_eq!(
block_json,
BlockJson {
hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
.parse::<BlockHash>()
.unwrap(),
target: "00000000ffff0000000000000000000000000000000000000000000000000000"
.parse::<BlockHash>()
.unwrap(),
best_height: 1,
height: 0,
inscriptions: vec![],
}
);
}
#44,986,637text ..self
}
}
pub(crate) fn expected_exit_code(self, expected_exit_code: i32) -> Self {
Self {
expected_exit_code,
..self
}
}
pub(crate) fn temp_dir(self, tempdir: TempDir) -> Self {
Self { tempdir, ..self }
}
pub(crate) fn command(&self) -> Command {
let mut command = Command::new(executable_path("ord"));
if let Some(rpc_server_url) = &self.rpc_server_url {
let cookiefile = self.tempdir.path().join("cookie");
fs::write(&cookiefile, "username:password").unwrap();
command.args([
"--rpc-url",
rpc_server_url,
"--cookie-file",
cookiefile.to_str().unwrap(),
]);
}
command
.env("ORD_INTEGRATION_TEST", "1")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(&self.tempdir)
.arg("--data-dir")
.arg(self.tempdir.path())
.args(&self.args);
command
}
fn run(self) -> (TempDir, String) {
let child = self.command().spawn().unwrap();
#44,986,636text
let ethereum_teleburn_address = CommandBuilder::new(format!("teleburn {inscription}"))
.rpc_server(&rpc_server)
.run_and_deserialize_output::<ord::subcommand::teleburn::Output>()
.ethereum;
TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex(
format!("/inscription/{inscription}"),
format!(
".*<meta property=og:title content='Inscription 0'>.*
.*<meta property=og:image content='https://.*/favicon.png'>.*
.*<meta property=twitter:card content=summary>.*
<h1>Inscription 0</h1>
.*<iframe .* src=/preview/{inscription}></iframe>.*
<dl>
<dt>id</dt>
<dd class=monospace>{inscription}</dd>
<dt>address</dt>
<dd class=monospace>bc1.*</dd>
<dt>output value</dt>
<dd>10000</dd>
<dt>preview</dt>
<dd><a href=/preview/{inscription}>link</a></dd>
<dt>content</dt>
<dd><a href=/content/{inscription}>link</a></dd>
<dt>content length</dt>
<dd>3 bytes</dd>
<dt>content type</dt>
<dd>text/plain;charset=utf-8</dd>
<dt>timestamp</dt>
<dd><time>1970-01-01 00:00:02 UTC</time></dd>
#44,986,635text tempdir.path().display(),
tempdir.path().display(),
ord_args.join(" "),
server_args.join(" "),
).to_args())
.env("ORD_INTEGRATION_TEST", "1")
.current_dir(&tempdir)
.spawn().unwrap();
for i in 0.. {
match reqwest::blocking::get(format!("http://127.0.0.1:{port}/status")) {
Ok(_) => break,
Err(err) => {
if i == 400 {
panic!("Server failed to start: {err}");
}
}
}
thread::sleep(Duration::from_millis(25));
}
Self {
child,
tempdir,
port,
rpc_url: rpc_server.url(),
}
}
pub(crate) fn url(&self) -> Url {
format!("http://127.0.0.1:{}", self.port).parse().unwrap()
}
pub(crate) fn assert_response_regex(&self, path: impl AsRef<str>, regex: impl AsRef<str>) {
self.sync_server();
let response = reqwest::blocking::get(self.url().join(path.as_ref()).unwrap()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_regex_match!(response.text().unwrap(), regex.as_ref());
#44,986,634text local_relay: false,
time_offset: 0,
connections: 0,
connections_in: None,
connections_out: None,
network_active: true,
networks: Vec::new(),
relay_fee: Amount::from_sat(0),
incremental_fee: Amount::from_sat(0),
local_addresses: Vec::new(),
warnings: String::new(),
})
}
fn get_block_hash(&self, height: usize) -> Result<BlockHash, jsonrpc_core::Error> {
match self.state().hashes.get(height) {
Some(block_hash) => Ok(*block_hash),
None => Err(Self::not_found()),
}
}
fn get_block_header(
&self,
block_hash: BlockHash,
verbose: bool,
) -> Result<Value, jsonrpc_core::Error> {
if verbose {
let height = match self
.state()
.hashes
.iter()
.position(|hash| *hash == block_hash)
{
Some(height) => height,
None => return Err(Self::not_found()),
};
Ok(
serde_json::to_value(GetBlockHeaderResult {
bits: String::new(),
chainwork: Vec::new(),
#44,986,633textdmt-mint.magic#44,743,717tapdmt-mint.magic#44,741,400tapdmt-mint.magic#44,740,697tapdmt-mint.magic#44,740,691tapdmt-mint.magic#44,740,666tapdmt-mint.magic#44,739,942tapdmt-mint.magic#44,739,855tapdmt-mint.magic#44,739,854tapdmt-mint.magic#44,739,853tapdmt-mint.magic#44,739,841tapdmt-mint.magic#44,739,782tapdmt-mint.magic#44,739,727tap