
Pełna treść jest dostępna na blogu. Czytaj więcej
Pełna treść jest dostępna na blogu. Czytaj więcej
DokÅ‚adnie dwie dekady temu (21.02.2005), na ówczesnej platformie jogger.pl drżącymi palcami napisaÅ‚em swój pierwszy wpis na blogu. PamiÄ™tam ten moment jakby to byÅ‚o wczoraj – prosty edytor tekstu, podstawowy szablon HTML i ogromne poczucie, że uczestniczÄ™ w czymÅ› przeÅ‚omowym.
Suchar
Spotykają się dwa blogi. Jeden mówi do drugiego:
Pełna treść jest dostępna na blogu. Czytaj więcej
[6.08.22 / 21.05.23]
Nie mogę doczekać się momentu, kiedy zielone ruszy, a ja w ślad za zielonym, może nawet w towarzystwie. Zdecydowanie za rzadko jeżdżę na Cytadelę, gdzie wiśniowe drzewa, tulipany, cmentarze, kasztany, rzeźby, WIEWIÓRKI, pokazy psiej akrobatyki czy zwyczajnie dookoła i przez środek. Gurls, kto ze mną?
Od wydarzeń “Rytmu Harlemu” minęły 4 lata, Carneyowi udało się odciąć od brudnych interesów, doskonale żyje z uczciwej sprzedaży mebli. Jego żona prowadzi biuro podróży dla czarnej klienteli, dodatkowo też wspiera aktywnie czarnego kandydata na burmistrza; żyją z mężem trochę obok siebie, ale w zgodzie. I tylko głupia chęć zaimponowania nastoletniej córce sprawia, że w celu zdobycia biletów na jej koncert jej ukochanego The Jacksons 5, Carney wraca do swoich starych kontaktów, błyskawicznie wikłając się w pełną przemocy historię. Zostaje zmuszony do udziału w finałowym skoku skorumpowanego policjanta, który chce się nachapać i zniknąć, bo wie, że wydział wewnętrzny już jest na jego tropie. Wtem akcja się zamyka, mijają kolejne dwa lata, w sklepie kręcony jest sensacyjny film z cyklu blaxploitation, a znajomy ochroniarz Pepper ma znaleźć zaginioną główną aktorkę, która prawdopodobnie wróciła do nałogu. Część trzecia, znowu jakiś czas później i z pretekstowymi związkami z poprzednimi, to śledztwo prowadzone przez Carneya, który może i nie działa zawsze zgodnie z literą prawa (tu wstaw w zasadzie idiotyczną historię dla żony, że chodzi o kradzione dywany), ale nie potrafi przejść do porządku nad celowymi podpaleniami dla zysku.
I jak bardzo podobała mi się pierwsza część, tak tutaj nie byłam w ogóle w stanie skupić uwagi na czytaniu. Niby nic się nie zmieniło, dalej bohaterowie byli pełnokrwiści, inteligentni, na tyle uczciwi, żeby dać im się przekonać o niskiej szkodliwości ich postępowania, ale już im nie kibicowałam. Może przez szerszy obraz zmian, jakim podlegał Harlem, zmian, wprowadzanych przez Czarnych, ale niekoniecznie prowadzących do pokojowego współistnienia z innymi. Stawiam się, że problem jest u mnie, nie w książce.
Inne tego autora.
#12
Jeżeli mam rano trochę więcej czasu na przygotowanie śniadania i w miarę dobry nastrój (nie oszukujmy się, gdy budzę się z niechęcią do świata, dłubanie przy śniadaniu jest ostatnim, na co mam ochotę), to robię sobie owsiankę.
Podstawę przepisu znalazłem lata temu w jakimś wideo, chyba Jamiego Oliviera. Od tego czasu towarzyszy mi wiernie.
To podstawowa wersja, którą robię najczęściej.
Do mierzenia używam dużego kubka o pojemności 0,4 litra. Z takiej ilości wychodzą trzy przyzwoite porcje owsianki.
* Jeżeli np. od gotowania obiadu poprzedniego dnia zostało mi trochę mleka kokosowego, część wody zastępuję nim. W obu wersjach owsianka wychodzi pyszna, ale z dodatkiem mleka jest wręcz nieprzyzwoita.
Do niedużego garnka wrzucam płatki i zalewam je wodą. Dodaję szczyptę soli i gotuję na średnim ogniu, co jakiś czas mieszając.
Gdy płatki się gotują, przygotowuję „wsad”: widelcem rozgniatam banany (obrane!), odmierzam mak i rodzynki.
Jak powiedział mi kiedyś zaprzyjaźniony ultramaratończyk: im bardziej rozgotujecie płatki, tym szybciej dostarczą paliwa, a te ugotowane mniej, będą was zasilać później. Ja gotuję je do momentu gdy płyn zrobi się gęsty, taki kleisty, ale ciągle widać w nim całe płatki.
Wyłączam grzanie pod garnkiem, wrzucam dodatki, mieszam i próbuję. Im bardziej dojrzałe były banany, tym mniej muszę dosłodzić owsiankę miodem, syropem czy choćby brązowym cukrem.
Gdy jestem zadowolony ze smaku, nakładam i zjadam.
Kojarzy mi się z zimowo-świątecznym wajbem, ale wchodzi dobrze też latem. Wymaga odrobinę więcej pracy niż poprzedniczka, jest jednak tego warta.
Tak jak poprzednio wstawiam płatki z wodą i szczyptą soli. Gdy się gotują, obieram i ścieram marchew na dużych oczkach, rozgniatam widelcem banany, siekam orzechy.
Jeżeli przeszkadza wam chrupiąca marchewka, możecie dodać ja w połowie gotowania płatków, dzięki temu, zanim dojdą to, trochę zmięknie. Ja wrzucam ją na końcu, po ugotowaniu płatków, gdy dodaję cały wsad i przyprawę do pierników, dosładzam miodem lub syropem. Próbuję i ewentualnie dodaję jeszcze trochę przyprawy.
Właściwie ogranicza was tylko fantazja i dostępność świeżych owoców. A i to nawet nie za bardzo, bo można użyć mrożonych.
Bardzo lubię owsiankę z np. jagodami czy wiśniami. Truskawki za to sprawdzają się gorzej, bo trochę za bardzo się rozpadają. Jeżeli wam to nie przeszkadza, to śmiało.
Kapitalna jest owsianka ze śliwkami. Węgierki tnę na ćwiartki i podsmażam je chwilkę na wegańskim maśle z cukrem i cynamonem. Osoby, którym nie chce się bawić, mogą pocięte śliwki przesypać cukrem i cynamonem i zostawić na przynajmniej kwadrans, ale smażone są oczywiście smaczniejsze. Podobnie można potraktować kawałki jabłek, tylko delikatnie, bo się porozpadają.
Smacznego!
Implementing Solana IBC bridge, I had to deal with various constraints of the Solana protocol. Connecting Solana to Composable Foundation’s Picasso network, I needed to develop an on-chain light client capable of validating Tendermint blocks. This meant being able to validate 50 signatures in a single transaction.
Turns out that’s not possible on Solana and it’s not exactly because of the execution time limit. The real culprit is the transaction size limit which I’ve discussed previously. This article describes how signature verification is done on Solana, the limit on the number of signatures that can be verified in a single transaction and how that limit can be worked around.
Like before, this article assumes familiarity with Solana and doesn’t serve as a step-by-step guide for Solana development. Nonetheless, examples from the solana-sigverify
repository can be used as a starting point when using the signature verification mechanism described below.
Solana programs can perform any computation a regular computer can do. It is possible to implement cryptographic functions as part of a smart contract and have them executed on the blockchain. However, that’s a quick way to run into issues.
Calculating a 256-bit SHA2 digest of a 100-byte buffer takes 14 thousand compute units (CU). Meanwhile, Solana programs have a hard limit of 1.4 million CU. In other words, hashing 100 bytes takes up 1% of the absolute maximum computation that a smart contract can perform in a single transaction. Situation is even worse with more advanced cryptographic functions: a signature verification blows through the compute limit.
Thankfully, Solana offers native methods for popular cryptographic primitives. In particular, a sol_sha256
system call (accessible through solana_program::hash::hashv
function) computes 256-bit SHA2 hash with cost of only 267 CU to hash a 100-byte buffer. One might expect a similar sol_ed25519_verify
system call, however signature verification is done in much more convoluted way on Solana.
Solana includes a handful of native programs. Among those are programs, such as Ed25519 program, which perform signature verification. To check a signature, caller creates a transaction including two instructions: one calling the native signature verification program and another calling a smart contract. If a signature is invalid, the whole transaction fails an the smart contract is not called.1
For example, consider transaction 56QjWeDDDX4Re2sX… which has three instructions: The first adjust compute unit limit, the second invokes the Ed25519 program and the final one calls a program which can check that signature has been verified. An annotated instruction data of the Ed25519 program invocation is shown below:
Offset | Bytes | Notes |
---|---|---|
0x00 | 01 00 80 00 ff ff c0 00 ff ff 10 00 70 00 ff ff | Request header |
0x10 | 6f 08 02 11 e5 61 6a 00 00 00 00 00 22 48 0a 20 | Signed message (0x70 bytes) |
a0 c2 78 ea ac 5e ba ce cf f5 6b 0a 33 2b 12 60 | ||
78 8a e9 2c 3e d9 17 14 c0 fe c3 71 ca 79 57 a7 | ||
12 24 08 01 12 20 61 43 1a 05 af 4d 46 64 6f 71 | ||
0b 59 f7 c3 c1 6f ca c6 10 d2 05 63 77 97 d0 4d | ||
ad 15 ed 32 ee b7 2a 0c 08 82 f9 fd b6 06 10 b2 | ||
b7 a5 95 01 32 0a 63 65 6e 74 61 75 72 69 2d 31 | ||
0x80 | 1a fd b3 c7 85 6c 16 82 2a 59 f6 3e d8 d3 fd 7a | Signature |
7b ab bd 8b 77 c1 0a 90 2c 38 8c 06 69 88 62 cd | ||
22 b2 4f 7e b5 cf 13 7c 97 00 d2 4d e3 da 08 1d | ||
f6 ad 3f 05 33 6e 35 47 15 5d 59 b8 fe e9 e6 07 | ||
0xc0 | c2 aa 20 50 7f 78 d5 49 f6 85 50 9d d0 8b 64 89 | Public key |
80 60 5a d2 ad 3e 90 b3 e8 0b 5d 24 b2 14 22 7b |
Since public key, signature and signed message are stored in the instruction data, number of signatures that can be verified is subject to the 1232-byte transaction size limit. Accounting for overhead leaves less than 1100 bytes for the instruction data. To specify a signature for verification, a 14-byte header, 32-byte public key, 64-byte signature and the message are needed. Even assuming an empty message, that’s 110 bytes per signature which means at most ten signatures in a single transaction.2
For Solana IBC I needed to verify Tendermint block signatures. Tendermint validators timestamp their signatures and as a result each signature is for a different message of about 112 bytes. That gives a maximum of ⌊1100 / (14 + 32 + 64 + 112)⌋ = 4 signatures per transaction. Meanwhile, as mentioned at the start, I needed to verify about 50 of them.
sigverify
programTo address this limitation, signatures can be verified in batches with results aggregated into a signatures account that can be inspected later on.3 This scheme needs i) a smart contract capable of doing the aggregation and ii) an interface for other smart contracts to interpret the aggregated data.
The first point is addressed by the sigverify
program. Using regular Solana way of signature verification, it observes what signatures have been checked in the transaction. It then aggregates all that information into a Program Derived Address (PDA) account it owns. As the owner, only the sigverify
program can modify the account thus making sure that aggregated information stored in it is correct.
Even though the sigverify
owns the account, it internally assigns it to the signer such that users cannot interfere with each other’s signatures account.
A convenient way to call the sigverify
program is to use the client library functions packaged alongside it. To use them, first add a new dependency to the RPC client (making sure client
feature is enabled):
[dependencies.solana-sigverify] git = "https://github.com/mina86/solana-sigverify" features = ["client"]
The crate has a UpdateIter
iterator which generates instruction pairs that need to be executed to aggregate the signatures into the signatures account. The account can be reused but in that case each batch of signatures needs to use a different epoch. If account is freed each time, epoch can be set to None. Note that all the instruction pairs returned by UpdateIter
can be executed in parallel transactions.
// Generate list of signatures to verify. let entries: Vec<Entry> = signatures.iter() .map(|sig| Entry { pubkey: &sig.pubkey, signature: &sig.signature, message: &sig.message, }) .collect(); // When signatures account is reused, each use needs // a different epoch value. Otherwise it can be None. let epoch = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() as u64; let epoch = Some(epoch); // Generate all necessary instructions and send them to // Solana. UpdateIter splits signatures into groups as // necessary to call sigverify. let (iter, signatures_account, signatures_bump) = solana_sigverify::instruction::UpdateIter::new( &solana_sigverify::algo::Ed25519::ID, SIGVERIFY_PROGRAM_ID, signer.pubkey(), SIGNATURES_ACCOUNT_SEED, epoch, &entries, )?; // To speed things up, all of those instruction pairs can // be executed in parallel. for insts in iter { let blockhash = client.get_latest_blockhash()?; let message = Message::new_with_blockhash( &insts, Some(&signer.pubkey()), &blockhash, ); send_and_confirm_message( client, signer, blockhash, message)?; } // Invoke the target smart contract. signatures_account is // the account with aggregated signatures. It will need to // be passed to the smart contract. todo!("Invoke the target smart contract"); // Optionally free the account. Depending on usage, the // account can be reused (to save minor amount of gas fees) // with a new epoch as described. let instruction = solana_sigverify::instruction::free( SIGVERIFY_PROGRAM_ID, signer.pubkey(), Some(signatures_account), SIGNATURES_ACCOUNT_SEED, signatures_bump, )?; send_and_confirm_instruction(client, signer, instruction)?;
The signatures are aggregated into an account whose address is stored in signatures_account
. The SIGNATURES_ACCOUNT_SEED
allows a single signer to maintain multiple accounts if necessary.
The target smart contract needs to be altered to support reading the aggregated signatures. Code which helps with that is available in the solana-sigverify
crate as well. First, add a new dependency to the program (making sure lib
feature is enabled)):
[dependencies.solana-sigverify] git = "https://github.com/mina86/solana-sigverify" features = ["lib"]
The crate has a Verifier
family of types which can interface with the Solana’s native signature verification programs (like the Ed25519 program) and the aggregated signatures. This flexibility allows the smart contracts using this type to nearly transparently support normal Solana signature verification method or the signature aggregation through sigverify
program.
/// Address of the sigverify program. This must be set /// correctly or the signature verification won’t work. const SIGVERIFY_PROGRAM_ID: Pubkey = solana_program::pubkey!("/* … */"); let mut verifier = solana_sigverify::Ed25519Verifier::default(); // To check signatures from a call to a native signature // verification program, the verifier must be initialised // with Instructions sysvar. The the native program call // must immediately preceding the current instruction. let instructions_sysvar = /* … */; verifier.set_ix_sysvar(instructions_sysvar)?; // To check signatures aggregated in a signatures account, // the verifier must be initialised with the account. For // security, expected sigverify program ID must be specified // as well. verifier rejects signatures accounts not owned // by the sigverify program. let signatures_account = /* … */; verifier.set_sigverify_account( account, &SIGVERIFY_PROGRAM_ID)?; // To verify a signature, call verify method. if !verifier.verify(message, pubkey, signature)? { panic!("Signature verification failed"); }
It’s perhaps counter intuitive that the transaction size limit constraints how many signatures can be verified in a single Solana transaction, but because of the design of the native signature verification programs, it is indeed the case. Thankfully, with some engineering the restriction can be worked around.
This article introduces a method of aggregating signatures across multiple transactions with the help of a sigverify
program and library functions available in solana-sigverify
repository. The library code supports regular Solana signature verification method as well as the aggregated signatures providing flexibility to the smart contract.
Lastly, the repository also provides solana-native-sigverify
crate which offers APIs for interacting with the Solana native signature verification programs.
1 The native signature verification programs cannot be called through Cross Program Invocation (CPI) making the whole mechanism a bit convoluted. The smart contract must inspect the transaction to make sure that the signature verification has happened which by itself is non-trivial. Thankfully, the solana-native-sigverify
crate includes helper function for interacting with native signature verification programs such as Ed25519 program or Secp255k1 program.
2 With specially crafted messages it is possible to go past that limit. If the same public key is used, 13 signatures can fit in single transaction (⌊(1100 - 32) / (14 + 64)⌋ = 13). However, that’s an unrealistic scenario especially considering that any useful transaction needs to include another instruction.
3 Note that ‘aggregate’ here doesn’t mean zero-knowledge aggregation. Rather, all the verified signatures are stored in the signatures account so another programs can see whether some particular signature has been aggregated. More specifically, as an optimisation, a sha256(algorithm || pubkey || signature || message)
hashes are stored. This is sufficient to check whether particular known signature was verified.
Minął ponad rok, odkąd kupiłem chromebooka (HP chromebook G11). Używanego, prawdopodobnie poleasingowego. Przyczyn było kilka, więc gdy pojawiła się okazja na zakup używanego w dobrej cenie (wtedy 200 zł) – zaryzykowałem. Ryzyko było niewielkie, gdyż gdyby ChromeOS lub wydajność mi nie pasowały, sprzęt skończyłby z zainstalowanym Linuksem jako przenośny do prostych prac. Zalety 4 GB RAM oraz x86, choć z ARM pewnie też bym sobie poradził.
Tak się jednak nie stało, bo sprzęt okazał się bardzo przyjazny w użytkowaniu. Pozytywnie zaskoczyło mnie kilka rzeczy. Pierwszą z nich jest czas pracy na baterii. W tej chwili słucham radia, jestem na WiFi, korzystam z przeglądarki WWW, mam 85% baterii, a przewidywany czas działania to 9,5 godziny. I to na x86 – przypuszczam, że ARMy mają jeszcze lepiej.
Czas pracy nie ma większego znaczenia, gdy pracować się nie da. I tu kolejne pozytywne zaskoczenie. Nie tylko da się korzystać z WWW, ale jest to komfortowe, nic nie zwalnia. Nie mam porównania dla tego procesora z innymi systemami operacyjnymi, ale po sprzęcie z 4 GB RAM spodziewałem się nieco mniej. Czyli nie wiem czy jest to kwestia optymalizacji ChromeOS, czy sprzętu, ale efekt jest bardzo przyjemny.
Klawiatura jest komfortowa. Nieco obawiałem się, że tak mała przekątna ekranu będzie oznaczała małą, niewygodną klawiaturę. Ta jest w zasadzie pełnowymiarowa – nieco węższe są niektóre klawisze, w tym skrajne, natomiast litery i odstępy między nimi są normalnej wielkości – korzysta się komfortowo. Uwaga – w sprzedawanym sprzęcie klawiatura jest tzw. z naklejkami. Zrobione na tyle dobrze, że trzymają się po roku, a zauważyłem po paru miesiącach…
Touchpad – brak fizycznych klawiszy, czyli mamy sytuację podobną jak w sprzęcie Apple. Oczywiście touchpad jest mniejszy i gorszy, niż w makach, ale spokojnie daje się z niego korzystać i mam wrażenie, że jest lepszy niż w wielu laptopach. Choć ja raczej wolę mysz (także na maku). Niemniej i tu, i na maku mogę żyć bez niej.
No i na deser, coś dla użytkowników Linuksa czy też zwolenników konsoli. ChromeOS na wielu sprzętach umożliwia dostęp do wiersza poleceń, czyli tak zwanego terminala. Jednak niezupełnie tego w ChromeOS. Terminal to tak naprawdę osobny system (Debian, prznynajmniej domyślnie) w formie maszyny wirtualnej (kernel jest wspólny, podejrzewam okolice LXC). Świetna integracja między systemami – z poziomu ChromeOS mamy dostęp do dysku Linuksa, co umożliwia łatwe przenoszenie plików między systemami.
Kolejnym zaskoczeniem był fakt, że w „terminalu” działa nie tylko tryb tekstowy, ale i graficzny, w dodatku integrując się z serwerem okien ChromeOS. Czyli nie musimy instalować środowiska graficznego pod Linuksem, by skorzystać z dowolnych programów instalowanych pod Linuksem, typu Firefox czy Gimp. Przyznaję, że poste i wygodne.
Ogólnie korzystanie z chromebooka przywołuje u mnie skojarzenia z macOS – wszystko po prostu działa, jest dopracowane, liczba ustawień znacznie ograniczona, ale wystarczająca. I wszystko dopracowane. Na przykład regulacja jasności to także możliwość włączenia ciemnego motywu czy trybu nocnego (filtrowanie niebieskiego).
System uruchamia się błyskawicznie. Nie jest to praktycznie natychmiast, jak na maku, ale znacznie szybciej niż znane mi systemy pod kontrolą Linuksa czy Windows. Oczywiście mamy też możliwość integracji z całym ekosystemem, czyli usługami Google czy Android – ale nie jest to opcja z której korzystam.
Jest też inaczej niż wszędzie – własne skróty, własne klawisze. Trzeba czasem poszukać instrukcji, ale w sieci wszystko jest dobrze opisane, a liczba opcji jest niewielka. Zresztą, poznanie/dostęp do ekosystemu było dla mnie jednym z powodów zakupu. Ogólnie widać, że Google przyłożyło się do UI i UX i nie jest to po prostu tani sprzęt z Linuksem, tylko przemyślany projekt.
Podsumowując, jestem bardzo zadowolony. Używam do sieci, do pisania tekstów (np. ten wpis). Świetnie sprawdza się w pociągu. Jest dość pancerny, dość mały, a w razie czego to tylko 200 zł. Nie jest moim podstawowym sprzętem, mam osobne konta i nie mam na nim istotniejszych danych.
Pokazałem sprzęt paru znajomym, kupili (do różnych zastosowań, np. dla dzieci) i z tego co słyszałem, po pierwszych wrażeniach zadowoleni. Jeśli komuś nie przeszkadza mocna integracja z usługami Google, ChromeOS – polecam. Link (afiliacyjny) do sprzedawcy na Allegro, gdzie kupowałem na początku wpisu.
Artykuł Chromebook HP G11 pochodzi z serwisu Pomiędzy bitami.
[2023-2024]
Tak to jest, jak człowiek sobie naobiecuje cudności na lato - a to że będzie chodzić na długie spacery, wyjeżdżać na weekend tu i ówdzie, wcześnie wstawać i inne takie. A potem zwyczajnie nie, bo milion wymówek. Na Puszczykowo miałam ambitne plany, nawet wydrukowałam sobie mapki, a przez ostatnie dwa lata wychodziło jak zwykle - czasem zakupy w Arturo, czasem lody u Kostusiaków (czy Kostusiakowej?), czasem bułeczki w piekarni. Zawsze miło. Nie tak jak dziś, kiedy przepiękny puszysty śnieg i oślepiające słońce przesiedziałam przed służbowym komputerem. Po co nam Paryż, poziomki i architektura secesyjna, ech.
Pełna treść jest dostępna na blogu. Czytaj więcej
Dla odmiany, dziś nie skupię się na tekście kartki, ale na cytacie, na którym ów się, dość luźno przyznam, opiera. Wszak w tekście swojego drugiego listu, święty Piotr ostrzega przed fałszywymi prorokami: zgubnymi nauczycielami, którzy będą manić za pomocą chciwości i rozpusty.
Jak dla mnie, kwalifikuje się do tego każdy kościół na Ziemi. Można mówić, że to, czy inne wyzwanie jest prawdziwsze. Wasi księża niewątpliwie sami spieszą pokazywać palcem pogaństwo, ale powiedzmy sobie od razu: to nie ma większego sensu. Nie ma pierwotnego Kościoła. Chrześcijaństwo rozlatywało się na długo przed ustanowieniem papiestwa, schizmami, czy reformacją. Rozpada się ono nawet dzisiaj, w samej tylko Polsce: powstają pomniejsze sekty bardziej, lub mniej oderwane od głównego nurtu Katolicyzmu. Niektóre żyją chwilę, niektóre dłużej.
Skąd mieć więc pewność, że nie daje się ucha fałszywemu prorokowi? Historia uczy nas, że możemy mieć jedynie pewność, że tak właśnie czynimy. Tak więc drodzy wierni, sam święty Piotr przestrzega Was!
Solana transactions are limited to 1232 bytes which was too restrictive when I was implementing Solana IBC bridge while working at Composable Foundation. The smart contract had to be able to ingest signed Tendermint block headers which were a few kilobytes in size.
To overcome this obstacle, I’ve used what I came to call chunking. By sending the instruction data in multiple transactions (similarly to the way Solana programs are deployed), the Solana IBC smart contract is capable of working on arbitrarily-large instructions. This article describes how this process works and how to incorporate it with other smart contracts (including those using the Anchor framework).
This article assumes familiarity with Solana; it’s not a step-by-step guide on creating Solana programs. Nevertheless, code samples are from examples available in the solana-write-account
repository (a chsum-program
Solana program and chsum-client
command line tool used to invoke said program) which can be used as a starting point when incorporating the chunking method described below.
First let’s reproduce the described issue. Consider the following (somewhat contrived) chsum
Solana program which computes a simple parameterised checksum. When called, the first byte of its instruction data is the checksum parameter and the rest is the data to calculate the checksum of.
solana_program::entrypoint!(process_instruction); fn process_instruction<'a>( _program_id: &'a Pubkey, _accounts: &'a [AccountInfo], instruction: &'a [u8], ) -> Result<(), ProgramError> { let (mult, data) = instruction .split_first() .ok_or(ProgramError::InvalidInstructionData)?; let sum = data.chunks(2).map(|pair| { u64::from(pair[0]) * u64::from(*mult) + pair.get(1).copied().map_or(0, u64::from) }).fold(0, u64::wrapping_add); solana_program::msg!("{}", sum); Ok(()) }
The program works on data of arbitrary length which can be easily observed by executing it with progressively longer buffers. However, due to aforementioned transaction size limit, eventually the operation fails:
$ chsum-client 2 ab Program log: 292 $ chsum-client 2 abcdefghijklmnopqrstuvwxyz Program log: 4264 $ data=… $ echo "${#data}" 1062 $ chsum-client 2 "$data" RPC response error -32602: decoded solana_sdk::transaction::versioned::VersionedTransaction too large: 1233 bytes (max: 1232 bytes)
write-account
programTo solve the problem, the overlarge instruction can be split into smaller chunks which can be sent to the blockchain in separate transactions and stored inside of a Solana account. For this to work two things are needed: i) a smart contract which can receive and concatenate all those chunks; and ii) support in the target smart contract for reading instruction data from an account (rather than from transaction’s payload).
The first requirement is addressed by the write-account
program. It copies bytes from its instruction data into a given account at specified offset. Subsequent calls allow arbitrary (and most importantly arbitrarily-long) data to be written into the account.
The simplest way to send the chunks to the smart contract is to use client library functions packaged alongside the write-account
program. First, add a new dependency to the RPC client:
[dependencies.solana-write-account] git = "https://github.com/mina86/solana-write-account" features = ["client"]
And with that, the WriteIter
can be used to split overlong instruction data into chunks and create all the necessary instructions. By default the data
is length-prefixed when it’s written to the account. This simplifies reuse of the account since the length of written data can be decoded without a need to resize the account.
// Write chunks to a new account let (chunks, write_account, write_account_bump) = solana_write_account::instruction::WriteIter::new( &WRITE_ACCOUNT_PROGRAM_ID, signer.pubkey(), WRITE_ACCOUNT_SEED, data, )?; for inst in chunks { send_and_confirm_instruction(client, signer, inst)?; } // Invoke the target smart contract. write_account is the // account with the instruction data. It will need to be // passed to the smart contract as last account. todo!("Invoke the target smart contract"); // Optionally, free the account to recover deposit let inst = solana_write_account::instruction::free( WRITE_ACCOUNT_PROGRAM_ID, signer.pubkey(), Some(write_account), WRITE_ACCOUNT_SEED, write_account_bump, )?; send_and_confirm_instruction(client, signer, inst)?;
The data is copied into a Program Derived Address (PDA) account owned by the write-account
program. The smart contract makes sure that different signers get their own accounts so that they won’t override each other’s work. WRITE_ACCOUNT_SEED
allows a single signer to maintain multiple accounts if necessary.
The address of the account holding the instruction data is saved in the write_account
variable. But before it can be passed to the target smart contract, the smart contract needs to be altered to support such calling convention.
With some care, the instructions returned by WriteIter
can be executed in parallel thus reducing amount of time spent calling the target smart contract. One complication is that the account may need to be resized when chunks are written into it. Since account can be increased by only 10 KiB in a single instruction, this becomes an issue if trying to write a chunk which is over 10 KiB past the end of the account.
One way to avoid this problem, is to group the instructions and executed them ten at a time. Once first batch executes, the next can be send to the blockchain. Furthermore, if the account is being reused, it may already be sufficiently large. And of course, this is not an issue with the data doesn’t exceed 10 KiB.
The Solana runtime has no built-in mechanism for passing instruction data from an account. Smart contract needs to explicitly support such calling method. One approach is to always read data from an account. This may be appropriate if the smart contract usually deals with overlong payloads. A more flexible approach is to read instruction from the account if instruction data in the transaction is empty. This can be done by defining a custom entry point:
/// Solana smart contract entry point. /// /// If the instruction data is empty, reads length-prefixed data /// from the last account and treats it as the instruction data. /// /// # Safety /// /// Must be called with pointer to properly serialised /// instruction such as done by the Solana runtime. See /// [`solana_program::entrypoint::deserialize`]. #[no_mangle] pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { // SAFETY: Guaranteed by the caller. let (prog_id, mut accounts, mut instruction_data) = unsafe { solana_program::entrypoint::deserialize(input) }; // If instruction data is empty, the actual instruction data // comes from the last account passed in the call. if instruction_data.is_empty() { match get_ix_data(&mut accounts) { Ok(data) => instruction_data = data, Err(err) => return err.into(), } } // Process the instruction. process_instruction( prog_id, &accounts, instruction_data, ).map_or_else( |error| error.into(), |()| solana_program::entrypoint::SUCCESS ) } /// Interprets data in the last account as instruction data. fn get_ix_data<'a>( accounts: &mut Vec<AccountInfo<'a>>, ) -> Result<&'a [u8], ProgramError> { let account = accounts.pop() .ok_or(ProgramError::NotEnoughAccountKeys)?; let data = alloc::rc::Rc::try_unwrap(account.data) .ok().unwrap().into_inner(); if data.len() < 4 { return Err(ProgramError::InvalidInstructionData); } let (len, data) = data.split_at(4); .ok_or(ProgramError::InvalidInstructionData)?; let len = u32::from_le_bytes(len.try_into().unwrap()); data.get(..(len as usize)) .ok_or(ProgramError::InvalidInstructionData) } solana_program::custom_heap_default!(); solana_program::custom_panic_default!();
The solana-write-account
crate packages all of that code. Rather than copying the above, a smart contract wanting to accept instruction data from an account can add the necessary dependency (this time with the lib
Cargo feature enabled):
[dependencies.solana-write-account] git = "https://github.com/mina86/solana-write-account" features = ["lib"]
and use entrypoint
macro defined there (in place of solana_program::entrypoint
macro):
solana_write_account::entrypoint!(process_instruction);
This gets slightly more complicated for anyone using the Anchor framework. The framework provides abstractions which are hard to break through when necessary. Any Anchor program has to use the anchor_lang::program
macro which, among other things, defines the entrypoint
function. This leads to conflicts when a smart contract wants to define its own entry point.
Unfortunately, there’s no reliable way to tell Anchor not to introduce that function. To add write-account
support to the Solana IBC bridge I had to fork Anchor and extend it with the following change which introduces support for a new custom-entrypoint
Cargo feature:
diff --git a/lang/syn/src/codegen/program/entry.rs b/lang/syn/src/codegen/program/entry.rs index 4b04da23..093b1813 100644 --- a/lang/syn/src/codegen/program/entry.rs +++ b/lang/syn/src/codegen/program/entry.rs @@ -9,7 +9,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { Err(anchor_lang::error::ErrorCode::InstructionMissing.into()) }); quote! { - #[cfg(not(feature = "no-entrypoint"))] + #[cfg(not(any(feature = "no-entrypoint", feature = "custom-entrypoint")))] anchor_lang::solana_program::entrypoint!(entry); /// The Anchor codegen exposes a programming model where a user defines /// a set of methods inside of a `#[program]` module in a way similar
The feature needs to be enabled in the Cargo.toml
of the Anchor program which wants to take advantage of it:
[features] default = ["custom-entrypoint"] custom-entrypoint = []
To achieve a sub-second finality Solana had to introduce significant constraints on the protocol and smart contract runtime environment. However, with enough ingenuity at least some of those can be worked around.
This article introduces a way to overcome the 1232-byte transaction size limit through the use of a write-account
helper program and library functions available in solana-write-account
repository. The solution is general enough that any Solana program, including those using Anchor framework, can use it.
PS. Interestingly, the Solana size limit also affects how many signatures can be verified in a single transaction. I discuss that problem and its solution in another article.
In September 2022, ‘Théâtre D’opéra Spatial’, a work submitted by Jason Allen won the 2022 Colorado State Fair’s annual fine art competition in the digital art category. What made the success noteworthy was that the image had been AI-generated. Mr Allen eventually tried to register the work with the US Copyright Office but his attempts turned out fruitless. In September 2023 the Office refused his registration.
I didn’t think much of it at the time. I wasn’t (and to be honest I’m still not) that invested in the discussion of what kind of ‘two-dimensional artworks’ are protected by copyright and, more notably, I somewhat agreed with the decision. Perhaps the prompt was protected, but if only minor manual edits were made to the image, it felt like a stretch to say it as a whole could be covered by copyright law.
‘Théâtre D’opéra Spatial’ wasn’t the first time US Copyright Office dealt with computer-generated images. Back in 2016, Steven Thaler’s Creativity Machine created a series of images with one of them — ‘A Recent Entrance to Paradise’ — ending up under Office’s deliberation. The result was the same: in February 2022, the Office refused the registration.1 I was made aware of the image only after Leonard French, a US copyright attorney, made a video about the case. In it, he made the following comparison to photography:
Maybe [Thaler] could have argued that he had to select what images got put into the AI to create the output. Maybe he can argue that there was some creativity in selecting the images and choosing the setting for the AI very much like we have protections for photographs because there is at least minimal human creativity in choosing what to photograph, with what lens, what focal length, what focus, what iris or aperture, what shutter speed, and then what post-processing settings to use. That’s enough. It’s maybe not that much, but it’s enough to reach copyright protection.
This made me wonder what actually is the minimal human creativity that’s required for a work to be subject to copyright.
Specifically, consider a lazy tourist. In his travels, he ends up on a beach and notices a flock of birds in the water. He decides to take a photo of them with his mobile phone. A marvellous device makes taking photos a seamless experience — no need to pick any settings, the phone automatically chooses all of them. The tourist is a lazy one so he’s not going to walk too far to find a perfect framing. He wonders a few steps, points his phone and makes a new work covered by the copyright.
How much human creativity was there in this process? The subject of the photo wasn’t carefully planned. The tourist didn’t wake up one day deciding to make a photo of a flock of birds in the water. He just stumbled upon them. The choice of the subject was highly constraint without room for much creativity.
Similarly, the tourist chose the framing but here again the options were limited. He didn’t use a drone to make an aerial shot, waited patiently for perfect composition nor looked for interesting framing with other elements in the frame. The whole process took him just a couple of minutes.
And with the amount of automation in mobile phones, the tourist had no creative input into the settings of the camera or post-processing steps. And despite all that, the photo is covered by the copyright.
Compare that to someone entering prompt into a text-to-image tool such as Stable Diffusion. There are countless possibilities of subjects to create images of. Flock of birds in the sea? Why not weyr of dragons in space? Or giant frogs on stills devouring humans? Similarly there are many options for framing. Is the subject of the image seen from the ground? From above? Are there foreground elements which obscure the subject?
And on top of that came all the settings that need to be tweaked to produce half-decent AI-generated image. Multiple models to choose from, samplers, guidance scale, control nets and so on.
AI is a controversial topic; there are many aspects of it that can lead to heated discussions. It’s not clear whether using publicly-available data to train the models is fair use or not. It’s also not clear whether a model which can reproduce letter-for-letter copyrighted works isn’t infringing in the first place.
Similarly copyrightability issue is hardly settled. The application for ‘A Recent Entrance to Paradise’ doesn’t tell us much since it claimed computer as the author. And in case of ‘Théâtre D’opéra Spatial’, the US Copyright Office did ‘not foreclose Mr Allen’s ability to file a new application for registration of the Work in which he disclaims the Work’s AI-generated material. In such a case, the Office could consider whether the human-authored elements of the Work can sustain a claim for copyright, an issue we have not decided here.’ Similarly, Leonard French theorised in his video that ‘maybe there is a point where an AI generated work could be protectable if there was enough creative human input on the input side or if you did that on the output side and created something with the AI-generated work.’
We’re many lawsuits away from having a clear picture of what kind of AI-generated works will be covered by copyright, but I don’t see how it’s possible to simultaneously hold that photos made with a phone can be protected while images created with the use of generative models cannot. The latter entails a lot more creativity and skill than majority of copyrighted photograph taken each day around the world.
1 A notable difference is that Dr Thaler didn’t claim he was the author of the work. Rather, he argued that he hired the Creativity Machine to produce the images. Since copyright protections require human creativity, the outcome of the registration attempt was doomed to fail from the onset. I may be reading too much into it, but my guess is that Dr Thaler was more interested in exploring the idea of machines being creative and authoring works rather than getting copyright protections for the image.
Tekst z kartki moje kalendarza „Dobrego Zasiewu”, z 6 stycznia, to chyba przejaw jakiegoś zdania z pisania kreatywnego. Napisać coś wzniosłego, w oparci o cytat, który dotyczy wizyty magów na dworze króla Heroda. Autor wyszedł od wspomnianej sceny, a skończył na tym, że Jezus jest siłą przeciwstawną ciemnej szatańskiej mocy, która włada tym światem. Rozpakujmy.
Zacznijmy od tego, że nie było magów. Królów, czy jak ich tam dzisiaj określa. To chyba wiadome? Nie było też rzezi niewiniątek. Jeśli ktoś nie wierzy historykom mającymi raczej dobre dowody, to wystarczy przeczytać inne święte teksty; apokryfy, czy nawet kanoniczne ewangelie.
Sam Herod (Herod Wielki), nie musiał się bać jakiegoś tam chłopca, którego istnienia pewnie nie był świadomy całe swoje życie. Miał, jakby to ująć, poważne problemy. W końcu był pośrednikiem pomiędzy Rzymem ówczesnym supermocarstwem okupującym, a swoimi poddanymi. A Judea wcale nie była taka pokojowa. Nawet we własnym pałacu władyka nie mógł sobie odpocząć, o czym świadczą doniesienia ówczesnych historyków, skrzętnie notujących różne jego wybryki (czytaj: morderstwa w rodzinie, w wyniku walki o władze, albo paranoi).
Wątpię, aby był też „bezbożnym”, czy „niewierzącym”. W końcu, nawet w cytowanej Ewangelii nie zbył doniesień o nowym królu żydowskim. W rzeczywistości był władcą Żydów i starał się im przymilać (np. projekty odbudowy świątyni, czy adaptację niektórych ich zwyczajów).
A to wszystko sprowadza się do tego, że nasz władca do bicia stał się nagle personifikacją świata, którym rządzi Szatan. Autor kalendarza, wprost stwierdza, że żyjemy w takim właśnie świecie. Czy też może raczej, bo zwraca się do współwyznawców, oni żyją w takim świecie.
I tu prawda jest zupełnie odmienna. Nieznany mi kalendarzopisarz jest przedstawicielem jednej z najpowszechniejszych religii, która ciągle oblepia politykę największych krajów. Tak więc, nie rządzi nami Szatan, rządzimy sami. I zło tej władzy, jeśli jest to zło, ale to temat na inną dyskusję, to wynika z człowieka, nie jakiejś mistycznej siły z Piekła. Co gorsza: ci nasi władcy, często motywują swoje decyzje religią właśnie. Szeroko pojętym chrześcijaństwem. Nawet jeśli czynią to jedynie na pokaz, rzesze wyznawców wielbią i za to, nie widzą hipokryzji. Zło wynika więc i z ich zaślepienia. Ewangelicy, czy jak tam określa się ogół protestantów z bogatych krajów, sami są swoim Szatanem.
No i kolejny rok, kolejna ankieta i po raz kolejny uzasadnione jest nazwanie jej wielką. Tym razem otrzymałem 55 odpowiedzi czyli 3 więcej niż w poprzedniej i 5 więcej niż dwa lata wcześniej.
Zacznijmy od sprawy najważniejszej czyli ile zjedliście/my pączków. No i pączków było sporo bo sumie 143 pączki ale chyba istotniejsze po ile:
Jak widać na powyższym obrazku najpopularniejszą odpowiedzią (16 respondentów – 29% odpowiedzi) były 3 pączki, kolejne najpopularniejsze odpowiedzi to 2 pączki (11 i 20% odpowiedzi) oraz 4 pączki (10 i 18%). Aż 9 (16,4%) osób nie zjadło nawet jednego pączka. To teraz policzmy ile zjedliśmy średnio, na 55 respondentów 143 pączki to daje średnio 2,6 na respondenta, lub 3,1 jeżeli pominiemy respondentów którzy pączków nie jedli w ogóle. Rok temu te wartości wynosiły odpowiednio 3 i 3,3 więc nam trochę średnie spadły.
W tym roku najwięcej to było 8 pączków, drugie miejsce z 6 i trzeci stopień podium z 5 pączkami. Co ciekawe pierwsze i drugie miejsce zajęły kobiety, pierwszy pan a nawet dwóch było dopiero na trzecim miejscu razem z kolejną Panią. Rok wcześniej wykręciliście wyższe wyniki bo podium było za odpowiednio 9, 8 i 7 pączków. Na trzecim stopniu podium było aż 4 zawodników.
Teraz skąd były pączki, znowu większość z was konsumowała pączki pochodzące z piekarni/cukierni (32 respondentów i 68%), następne źródła to sklep spożywczy (8 i 17%) oraz zrobione samodzielnie (5 i 11%), było też kilka odpowiedzi że nie znacie pochodzenia pączków (7 i 15%).
To teraz może kilka słów o samych respondentach. Jak zwykle najwięcej odpowiedzi otrzymałem od blabowiczów bo prawie połowę (26 i 47%), jedna czwarta od Wartkich i BBLowców (14 i 26%), potem po ok 10% z kanału #wtw, Faceboka/messengera oraz planety joggerowej. Jak się komuś by nudziło i sobie ręczni przeliczy odpowiedzi to można było wybrać więcej niż jedną odpowiedź stąd suma wszystkich opcji daje nam 56.
To teraz przyjrzyjmy się poszczególnym grupom pączkożerców. Zacznijmy od blablera, za najliczniejszą reprezentację coś się należy.
Respondenci z blablera w liczbie 26 zjedli razem 48 pączków (średnio 1,8 pączka na blabowicza), to dość sfeminizowana grupa (18 na 26 czyli 69%), co ciekawe więcej mamy odpowiedzi kopytko (4 i 15%) niż mężczyzna (3 i 11%). Kobiety zjadły średnio po 2,4 pączka, panowie po 1 a kopytka po 0,5
Druga grupa to Wartkie/BBL czyli grupy biegowe, zobaczmy czy to miało wpływ na wyniki. Ta grupa liczyła 14 respondentów i średnio zjedli po 3 pączki. Co ciekawe każdy zjadł przynajmniej jednego pączka oraz średnie są takie same i dla kobiet i dla mężczyzn (jak można się domyślić po 3). Jest też zdecydowanie bardziej wyrównana płciowo bo odpowiedziało 8 kobiet i 6 mężczyzn (czyli odpowiednio 57 i 43%).
Trzecia grupa to Facebook/Messenger tutaj znowu mamy przewagę kobiet 5:1 ale i bardzo wysokie spożycie pączków bo średnio 4,5, z czego u kobiet 4,4 a u panów 5.
Kolejna grupa to kanał #wtw i tutaj mamy dość ciekawy przypadek bo mamy 2 panów, 2 kopytka, oraz jeden respondent nie wpisał nic. Średnio WTWowcy zjedli po 2,6 pączka, panowie po 3 a kopytka po 3,5.
Ostatnia grupa to blog/planeta joggerowa, tutaj było 4 respondentów i średnie spożycie pączków wyniosło 3,25. Mamy również pełne równouprawnienie bo po 2 panie i panów, z czego panie zjadły średnio po 4 pączki a panowie po 2,5
Ostatnie pytanie mówiące o nas, czyli płeć. No i większość respondentów to były kobiety(62%), czyli bardzo podobnie jak rok temu (wtedy było 61%). W tym roku więcej respondentów wpisało opcję kopytko (6 i 11%) w porównaniu do roku poprzedniego, wtedy zaobserwowałem tylko 3 takie odpowiedzi. Panie zjadły też najwięcej pączków bo średnio po 2,9, panowie po 2,6 a kopytka po 1,5.
Jeszcze w temacie uwag, prosiłem o podanie wagi/wzrostu i aż 23,6% respondentów podało. Najwięcej podało blabian (7 czyli 27%) oraz Wartkich/BBLowców (3 czyli 21%), z innych grup otrzymałem po jednej takiej odpowiedzi. Co ciekawe najczęściej tą informację podawały kopytka (33%) i kobiety (30%), faceci w tym przypadku są jakoś bardzo wstydliwi (tylko 14% odpowiedziało na to pytanie). Oczywiście to są dane wrażliwe i nie ma możliwości żeby je udostępnić.
Na koniec info, dlaczego tak późno te wyniki. Trochę taki był plan żeby w ten sposób przypomnieć o ankiecie przed kolejną. Ale bardziej po prostu nie za bardzo był na to najpierw czas a potem nastrój. Poprzedni rok od kwietnia obfitował w wydarzenia i opracowanie wyników tej ankiety jak oczywiście jest istotne, tak okazało się że są sprawy znacznie od tego ważniejsze. Ale już wkrótce nowa ankieta, bo przypominam że tłusty czwartek w tym roku wypada 27 lutego.