Esta semana pasada, agregué proceso de transacciones a mi proyecto personal de blockchain:
https://github.com/ajlopez/BlockchainSharp
En el anterior post, describí el Trie inmutable que estuve armando. Ahora voy a usarlo para guardar el AccountState por dirección de cuenta:
public class AccountState { private BigInteger balance; public AccountState(BigInteger balance) { if (BigInteger.Compare(BigInteger.Zero, balance) > 0) throw new InvalidOperationException("Invalid balance"); this.balance = balance; } public BigInteger Balance { get { return this.balance; } } public AccountState AddToBalance(BigInteger amount) { return new AccountState(BigInteger.Add(this.balance, amount)); } public AccountState SubtractFromBalance(BigInteger amount) { return new AccountState(BigInteger.Subtract(this.balance, amount)); } }
Decidí implementar cuentas como en Ethereum: tener una cuenta con saldo en luegar de inputs y outputs. Por ahora, la única propiedad es el Balance, pero iré agregando más datos. Vean arriba que los saldos negativos son rechazadas. He agregado un TransactionProcessor:
public class TransactionProcessor { private Trie<AccountState> states; public TransactionProcessor(Trie<AccountState> states) { this.states = states; } public Trie<AccountState> States { get { return this.states; } } public bool ExecuteTransaction(Transaction transaction) { var states = this.states; try { foreach (var av in transaction.Inputs) { var addr = av.Address.ToString(); var state = states.Get(addr); var newstate = state.SubtractFromBalance(av.Value); states = states.Put(addr, newstate); } foreach (var av in transaction.Outputs) { var addr = av.Address.ToString(); var state = states.Get(addr); var newstate = state.AddToBalance(av.Value); states = states.Put(addr, newstate); } this.states = states; return true; } catch (Exception ex) { return false; } } }
Que parte de un estado de cuentas y va construyedo otro. Si la transacción es procesado, un nuevo trie es generado, y ExecuteTransaction retorna true. Si la transacción es rechazada (la causa podría ser que al aplicarla resulte un saldo negativo), el trie inicial se mantiene. Un test típico que escribí:
[TestMethod] public void ExecuteTransaction() { var transaction = CreateTransaction(100); var addr1 = transaction.Inputs.First().Address; var addr2 = transaction.Outputs.First().Address; var states = new Trie<AccountState>(new AccountState(BigInteger.Zero)); states = states.Put(addr1.ToString(), new AccountState(new BigInteger(200))); var processor = new TransactionProcessor(states); Assert.IsTrue(processor.ExecuteTransaction(transaction)); var newstates = processor.States; Assert.IsNotNull(newstates); Assert.AreNotSame(states, newstates); Assert.AreEqual(new BigInteger(200), states.Get(addr1.ToString()).Balance); Assert.AreEqual(BigInteger.Zero, states.Get(addr2.ToString()).Balance); Assert.AreEqual(new BigInteger(100), newstates.Get(addr1.ToString()).Balance); Assert.AreEqual(new BigInteger(100), newstates.Get(addr2.ToString()).Balance); }
El método auxiliar CreateTransaction crea una transacción con un monton, y dos direcciones creadas al azar.
Estoy pensando en tener solamente una cuenta sender y una cuenta receiver por transacción, como en Ethereum. De hecho, ya ayer lo reimplementé así, haciendo rediseño y refactor, ayudado por toda la batería de tests que ya me daba TDD. El cambio fue fácil y apenas tomó unos minutos.
Próximos temas: ejecutar bloques con transacciones, guardar el estado resultante en un almacén persistente, implementación de la máquina virtual y su ejecución de bytecodes, el compilador simple que armé, etc…
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez