[REALLY UNSTABLE] Started removal of Pigeon::Storage.current

This commit is contained in:
Netscape Navigator 2020-04-16 09:05:10 -05:00
parent a0573b3687
commit 780823ccaf
15 changed files with 149 additions and 141 deletions

View File

@ -56,8 +56,16 @@ Eg: `pigeon identity show` becomes `./pigeon-cli show`.
- [X] Make all methods private except those required for the CLI.
- [X] Add Lipmaa links like the Bamboo folks do.
- [X] Set a max message size.
- [ ] Clean up all singletons / .current hack from storage.rb
- [ ] Clean up all singletons / .current hack from parser.rb
- [ ] Clean up all singletons / .current hack from lexer.rb
- [ ] Clean up all singletons / .current hack from message.rb
- [ ] Clean up all singletons / .current hack from draft_serializer.rb
- [ ] Clean up all singletons / .current hack from message_serializer.rb
- [ ] Clean up all singletons / .current hack from remote_identity.rb
- [ ] Clean up all singletons / .current hack from draft.rb
- [ ] Clean up all singletons / .current hack from local_identity.rb
- [ ] Update README.md. Needs user manual for new `Pigeon::Database` class.
- [ ] Remove all `.current` "singletons" / hacks
- [ ] Make the switch to LevelDB, RocksDB, [UNQLite](https://unqlite.org/features.html) or similar (currently using Ruby PStore).
- [ ] Make CLI names consistent with API names. Eg: find vs. read.
- [ ] Create regexes in ::Lexer using strings and Regexp.new() for cleaner regexes.

6
dist/pigeon.rb vendored
View File

@ -135,12 +135,6 @@ module Pigeon
.join("")
end
def self.create_message(kind, params)
draft = Pigeon::Draft.create(kind: kind)
params.map { |(k, v)| draft[k] = v }
draft.publish
end
def self.verify_string(identity, string_signature, string)
binary_signature = decode_multihash(string_signature)

View File

@ -1,11 +1,28 @@
module Pigeon
class Database
def initialize
attr_reader :local_identity
def initialize(path: PIGEON_DB_PATH,
seed: SecureRandom.random_bytes(Ed25519::KEY_SIZE))
@store = Pigeon::Storage.new(path: path)
init_local_identity(seed)
end
def find_all; store.find_all; end
def put_blob(b); store.put_blob(b); end
def set_config(k, v); store.set_config(k, v); end
def get_config(k); store.get_config(k); end
def reset_current_draft; set_config(CURRENT_DRAFT, nil); end
def reset; store.reset; end
def create_message(kind, params)
draft = Pigeon::Draft.new(kind: kind, db: self)
params.map { |(k, v)| draft[k] = v }
draft.publish
end
def create_bundle(file_path = DEFAULT_BUNDLE_PATH)
s = Pigeon::Storage.current
content = s
content = store
.find_all(Pigeon::LocalIdentity.current.multihash)
.map { |multihash| s.read_message(multihash) }
.sort_by(&:depth)
@ -19,5 +36,32 @@ module Pigeon
tokens = Pigeon::Lexer.tokenize(bundle)
Pigeon::Parser.parse(tokens)
end
def create_draft(kind:, body: {})
save_draft(Draft.new(kind: kind, body: body))
end
def save_draft(draft)
set_config(CURRENT_DRAFT, draft)
draft
end
def current_draft
store.get_config(CURRENT_DRAFT) or raise NO_DRAFT_FOUND
end
private
attr_reader :store
def init_local_identity(new_seed)
key = store.get_config(SEED_CONFIG_KEY)
if key
@local_identity = LocalIdentity.new(key)
else
@local_identity = LocalIdentity.new(new_seed)
set_config(SEED_CONFIG_KEY, new_seed)
end
end
end
end

22
dist/pigeon/draft.rb vendored
View File

@ -5,25 +5,14 @@ module Pigeon
attr_reader :signature, :prev, :lipmaa, :kind, :internal_id,
:depth, :body, :author
def self.create(kind:, body: {})
self.new(kind: kind, body: body).save_as_draft
end
def self.current
Pigeon::Storage.current.get_config(CURRENT_DRAFT) or raise NO_DRAFT_FOUND
end
def self.reset_current
Pigeon::Storage.current.set_config(CURRENT_DRAFT, nil)
end
def discard
if Draft.current&.internal_id == @internal_id
Draft.reset_current
end
end
def initialize(kind:, body: {})
def initialize(kind:, body: {}, db:)
@db = db
@signature = Pigeon::NOTHING
@prev = Pigeon::NOTHING
@kind = kind
@ -50,15 +39,10 @@ module Pigeon
# This might be a bad or good idea. Not sure yet.
self.body[key] = value.inspect
end
self.save_as_draft
@db.save_draft(self)
return self.body[key]
end
def save_as_draft
Pigeon::Storage.current.set_config(CURRENT_DRAFT, self)
self
end
# Author a new message.
def publish
template = MessageSerializer.new(self)

View File

@ -6,22 +6,9 @@ module Pigeon
# help us maintain our sanity when the Gem's API
# changes.
class LocalIdentity
def self.reset
@current = nil
end
def self.current
if @current
@current
else
key = Pigeon::Storage.current.get_config(SEED_CONFIG_KEY)
@current = (key ? self.new(key) : self.new).save!
end
end
# `seed` is a 32-byte seed value from which
# the key should be derived
def initialize(seed = SecureRandom.random_bytes(Ed25519::KEY_SIZE))
def initialize(seed)
@seed = seed
@signing_key = Ed25519::SigningKey.new(@seed)
end
@ -42,10 +29,5 @@ module Pigeon
b64 = Helpers.b32_encode(hex)
return b64 + SIG_FOOTER
end
def save!
Pigeon::Storage.current.set_config(SEED_CONFIG_KEY, @seed)
self
end
end
end

View File

@ -11,14 +11,22 @@ module Pigeon
MSG_SIZE_ERROR = "Messages cannot have more than 64 keys. Got %s."
# Store a message that someone (not the LocalIdentity)
# has authored.
def self.ingest(author:, body:, depth:, kind:, lipmaa:, prev:, signature:)
def self.ingest(author:,
body:,
depth:,
kind:,
lipmaa:,
prev:,
signature:,
db:)
params = { author: RemoteIdentity.new(author),
kind: kind,
body: body,
prev: prev,
lipmaa: lipmaa,
signature: signature,
depth: depth }
depth: depth,
db: db }
# Kind of weird to use `send` but #save! is private,
# and I don't want people calling it directly without going through the
# lexer / parser first.
@ -39,11 +47,11 @@ module Pigeon
private
def save!
return store.read_message(multihash) if store.message?(multihash)
return db.read_message(multihash) if db.message?(multihash)
verify_counted_fields
verify_signature
self.freeze
store.save_message(self)
db.save_message(self)
self
end
@ -60,8 +68,8 @@ module Pigeon
msg = MSG_SIZE_ERROR % key_count
raise MessageSizeError, msg
end
count = store.get_message_count_for(author.multihash)
expected_prev = store.get_message_by_depth(author.multihash, count - 1) || Pigeon::NOTHING
count = db.get_message_count_for(author.multihash)
expected_prev = db.get_message_by_depth(author.multihash, count - 1) || Pigeon::NOTHING
assert("depth", count, depth)
# TODO: Re-visit this. Our current verification method
# is probably too strict and won't allow for partial
@ -81,8 +89,10 @@ module Pigeon
depth:,
prev:,
lipmaa:,
signature:)
signature:,
db:)
raise MISSING_BODY if body.empty?
@db = db
@author = author
@body = body
@depth = depth
@ -95,9 +105,5 @@ module Pigeon
def template
MessageSerializer.new(self)
end
def store
Pigeon::Storage.current
end
end
end

View File

@ -2,12 +2,15 @@ require "pstore"
module Pigeon
class Storage
def self.current
@current ||= self.new
attr_reader :path
def initialize(path: PIGEON_DB_PATH)
@path = path
bootstrap unless bootstrapped?
end
def self.reset
File.delete(PIGEON_DB_PATH) if File.file?(PIGEON_DB_PATH)
def reset
File.delete(path) if bootstrapped?
@current = nil
end
@ -45,7 +48,7 @@ module Pigeon
write { store[CONF_NS][key] = value }
end
def set_blob(data)
def put_blob(data)
raw_digest = Digest::SHA256.digest(data)
b64_digest = Helpers.b32_encode(raw_digest)
multihash = [BLOB_SIGIL, b64_digest, BLOB_FOOTER].join("")
@ -146,5 +149,9 @@ module Pigeon
def write(&blk); transaction(false, &blk); end
def read(&blk); transaction(true, &blk); end
def bootstrapped?
File.file?(path)
end
end
end

View File

@ -47,7 +47,7 @@ module Pigeon
def set(data = "")
blob = (data != "") ? data : STDIN.read
puts Pigeon::Storage.current.set_blob(blob)
puts Pigeon::Storage.current.put_blob(blob)
end
desc "get", "Read arbitrary data from the database"
@ -87,7 +87,7 @@ module Pigeon
desc "create", "Begin a new Pigeon message"
def create(kind)
puts Pigeon::Draft.create(kind: kind).render_as_draft
puts db.create_draft(kind: kind).render_as_draft
end
desc "append", "Add a key/value pair to the current DRAFT"

View File

@ -4,17 +4,15 @@ RSpec.describe Pigeon::Message do
before(:each) do
p = Pigeon::DEFAULT_BUNDLE_PATH
File.delete(p) if File.file?(p)
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
end
let(:db) do Pigeon::Database.new end
let(:db) { Pigeon::Database.new }
def create_fake_messages
(1..10)
.to_a
.map do |n| { "foo" => ["bar", "123", SecureRandom.uuid].sample } end
.map do |d| Pigeon::Helpers.create_message(SecureRandom.uuid, d) end
.map do |d| db.create_message(SecureRandom.uuid, d) end
end
it "creates a bundle" do

View File

@ -1,18 +1,19 @@
require "spec_helper"
RSpec.describe Pigeon::Draft do
let(:db) do
Pigeon::Database.new
end
let(:message) do
message = Pigeon::Draft.create(kind: "unit_test")
hash = Pigeon::Storage.current.set_blob(File.read("./logo.png"))
message = db.create_draft(kind: "unit_test")
hash = db.put_blob(File.read("./logo.png"))
message["a"] = "bar"
message["b"] = hash
message
end
before(:each) do
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
end
before(:each) { db.reset }
MSG = [
"author DRAFT",
@ -26,15 +27,15 @@ RSpec.describe Pigeon::Draft do
].join("\n")
it "renders a message" do
pk = Pigeon::LocalIdentity.current.multihash
pk = db.local_identity.multihash
actual = message.render_as_draft
expected = MSG.gsub("___", pk)
expect(actual).to start_with(expected)
end
it "creates a new message" do
message = Pigeon::Draft.create(kind: "unit_test")
hash = Pigeon::Storage.current.set_blob(File.read("./logo.png"))
message = db.create_draft(kind: "unit_test")
hash = db.put_blob(File.read("./logo.png"))
expectations = {
kind: "unit_test",
body: {

View File

@ -115,15 +115,14 @@ RSpec.describe Pigeon::Lexer do
"signature hHvhdvUcrabhFPz52GSGa9_iuudOsGEEE7S0o0WJLqjQyhLfgUy72yppHXsG6T4E21p6EEI6B3yRcjfurxegCA==.sig.ed25519",
].freeze
let(:message) do
draft = Pigeon::Draft.create(kind: "unit_test")
draft["foo"] = "bar"
draft.publish
let(:db) do
Pigeon::Database.new
end
before(:each) do
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
let(:message) do
draft = db.create_draft(kind: "unit_test")
draft["foo"] = "bar"
draft.publish
end
it "tokenizes a bundle" do

View File

@ -32,9 +32,4 @@ RSpec.describe Pigeon::LocalIdentity do
result = Pigeon::Helpers.decode_multihash(example)
expect(result).to eq(Pigeon::Helpers.b32_decode(whatever))
end
it "caches LocalIdentity.current" do
first_kp = Pigeon::LocalIdentity.current
expect(Pigeon::LocalIdentity.current).to be(first_kp) # Need strict equality here!
end
end

View File

@ -1,13 +1,8 @@
require "spec_helper"
RSpec.describe Pigeon::Message do
before(:each) do
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
end
def create_draft(params)
draft = Pigeon::Draft.create(kind: "unit_test")
draft = db.create_draft(kind: "unit_test")
params.each { |(k, v)| draft[k] = v }
draft
end
@ -17,10 +12,13 @@ RSpec.describe Pigeon::Message do
draft.publish
end
let(:db) do
Pigeon::Database.new
end
let(:draft) do
hash = Pigeon::Storage.current.set_blob(File.read("./logo.png"))
create_draft({ "a" => "bar",
"b" => hash })
hash = db.put_blob(File.read("./logo.png"))
create_draft({ "a" => "bar", "b" => hash })
end
let(:templated_message) { create_message({ "a" => "b" }) }
@ -65,7 +63,7 @@ RSpec.describe Pigeon::Message do
it "creates a chain of messages" do
all = []
0.upto(4) do |expected_depth|
draft1 = Pigeon::Draft.create(kind: "unit_test")
draft1 = db.create_draft(kind: "unit_test")
draft1["description"] = "Message number #{expected_depth}"
message = draft1.publish
all.push(message)
@ -106,8 +104,7 @@ RSpec.describe Pigeon::Message do
it "verifies accuracy of signatures" do
# === Initial setup
Pigeon::LocalIdentity.current
secret = Pigeon::Storage.current.get_config(Pigeon::SEED_CONFIG_KEY)
secret = db.get_config(Pigeon::SEED_CONFIG_KEY)
message = templated_message
plaintext = template.render_without_signature
@ -153,7 +150,7 @@ RSpec.describe Pigeon::Message do
WHITESPACE.map do |n|
kind = SecureRandom.alphanumeric(8)
kind[rand(0...8)] = n
draft = Pigeon::Draft.create(kind: kind)
draft = db.create_draft(kind: kind)
draft["body"] = "empty"
boom = ->() { Pigeon::Lexer.tokenize(draft.publish.render) }
expect(boom).to raise_error(Pigeon::Lexer::LexError)
@ -162,7 +159,7 @@ RSpec.describe Pigeon::Message do
it "does not allow whitespace in key names" do
WHITESPACE.map do |n|
draft = Pigeon::Draft.create(kind: "unit_test")
draft = db.create_draft(kind: "unit_test")
key = SecureRandom.alphanumeric(8)
key[rand(0...8)] = n
draft[key] = "should crash"

View File

@ -1,11 +1,6 @@
require "spec_helper"
RSpec.describe Pigeon::Lexer do
before(:each) do
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
end
let(:db) { Pigeon::Database.new }
let(:example_bundle) { File.read("./spec/fixtures/normal.bundle") }
let(:tokens) { Pigeon::Lexer.tokenize(example_bundle) }

View File

@ -6,72 +6,70 @@ RSpec.describe Pigeon::Storage do
@VF0Q4KXQNY6WCAXF17GAZGDPAX8XKM70SB8N7V0NSD1H370ZCJBG.ed25519)
before(:each) do
Pigeon::Storage.reset
Pigeon::LocalIdentity.reset
db.reset
end
let(:s) { Pigeon::Storage.current }
let(:db) { Pigeon::Database.new }
it "sets a config" do
s.set_config("FOO", "BAR")
value = s.get_config("FOO")
db.set_config("FOO", "BAR")
value = db.get_config("FOO")
expect(value).to eq("BAR")
s.set_config("FOO", nil)
value = s.get_config("FOO")
db.set_config("FOO", nil)
value = db.get_config("FOO")
expect(value).to eq(nil)
end
it "manages configs" do
s.set_config("FOO", "BAR")
value = s.get_config("FOO")
db.set_config("FOO", "BAR")
value = db.get_config("FOO")
expect(value).to eq("BAR")
end
it "manages blobs" do
logo_hash = s.set_blob(LOGO_BLOB)
expect(s.get_blob(logo_hash)).to eq(LOGO_BLOB)
logo_hash = db.put_blob(LOGO_BLOB)
expect(db.get_blob(logo_hash)).to eq(LOGO_BLOB)
end
it "manages peers" do
s.add_peer(IDS[0])
s.add_peer(IDS[1])
expect(s.all_peers).to include(IDS[0])
expect(s.all_peers).to include(IDS[1])
db.add_peer(IDS[0])
db.add_peer(IDS[1])
expect(db.all_peers).to include(IDS[0])
expect(db.all_peers).to include(IDS[1])
s.remove_peer(IDS[0])
expect(s.all_peers).not_to include(IDS[0])
expect(s.all_blocks).not_to include(IDS[0])
db.remove_peer(IDS[0])
expect(db.all_peers).not_to include(IDS[0])
expect(db.all_blocks).not_to include(IDS[0])
s.block_peer(IDS[1])
expect(s.all_peers).not_to include(IDS[1])
expect(s.all_blocks).to include(IDS[1])
expect(s.all_blocks.count).to eq(1)
db.block_peer(IDS[1])
expect(db.all_peers).not_to include(IDS[1])
expect(db.all_blocks).to include(IDS[1])
expect(db.all_blockdb.count).to eq(1)
end
it "finds all authored by a particular feed" do
ingested_messages = db.ingest_bundle("./spec/fixtures/normal.bundle")
author = ingested_messages.first.author.multihash
actual_messages = Pigeon::Storage.current.find_all(author)
search_results = Pigeon::Storage.current.find_all(author)
author = ingested_messagedb.first.author.multihash
actual_messages = db.find_all(author)
search_results = db.find_all(author)
end
it "finds all messages" do
msgs = [
Pigeon::Helpers.create_message("strings", {
db.create_message("strings", {
"example_1.1" => "This is a string.",
"example=_." => "A second string.",
}),
Pigeon::Helpers.create_message("d", {
"e" => Pigeon::Storage.current.set_blob(File.read("./logo.png")),
db.create_message("d", {
"e" => db.put_blob(File.read("./logo.png")),
}),
Pigeon::Helpers.create_message("g", {
db.create_message("g", {
"me_myself_and_i" => Pigeon::LocalIdentity.current.multihash,
}),
]
me = Pigeon::LocalIdentity.current.multihash
results = Pigeon::Storage.current.find_all(me)
expect(results.length).to eq(3)
results = db.find_all(me)
expect(resultdb.length).to eq(3)
expect(msgs[0].multihash).to eq(results[0])
expect(msgs[1].multihash).to eq(results[1])
expect(msgs[2].multihash).to eq(results[2])