Pending Transaction - save in external datasource

This commit is contained in:
eugene-shevchenko 2015-03-27 12:49:32 +02:00
parent 8fd961ecaf
commit 4ef20af5e0
8 changed files with 235 additions and 106 deletions

View File

@ -3,14 +3,19 @@ package org.ethereum.datasource.redis;
import org.ethereum.core.Transaction;
import org.ethereum.datasource.KeyValueDataSource;
import java.util.Map;
import java.util.Set;
public interface RedisConnection {
public static final String REDISCLOUD_URL = "REDISCLOUD_URL";
boolean isAvailable();
<T> Set<T> createSetFor(Class<T> clazz, String name);
<K,V> Map<K, V> createMapFor(Class<K> keyClass, Class<V> valueClass, String name);
Set<Transaction> createTransactionSet(String name);
KeyValueDataSource createDataSource(String name);

View File

@ -15,6 +15,7 @@ import redis.clients.jedis.Protocol;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import static org.springframework.util.StringUtils.isEmpty;
@ -30,7 +31,7 @@ public class RedisConnectionImpl implements RedisConnection {
public void tryConnect() {
if (!SystemProperties.CONFIG.isRedisEnabled()) return;
String redisCloudUrl = System.getenv("REDISCLOUD_URL");
String redisCloudUrl = System.getenv(REDISCLOUD_URL);
if (isEmpty(redisCloudUrl)) {
logger.info("Cannot connect to Redis. 'REDISCLOUD_URL' environment variable is not defined.");
return;
@ -73,14 +74,16 @@ public class RedisConnectionImpl implements RedisConnection {
public boolean isAvailable() {
boolean available = jedisPool != null;
if (available) {
Jedis jedis = jedisPool.getResource();
try {
available = jedis.isConnected();
Jedis jedis = jedisPool.getResource();
try {
available = jedis.isConnected();
} finally {
jedisPool.returnResource(jedis);
}
} catch (Throwable t) {
logger.warn("Connection testing failed: ", t);
available = false;
} finally {
jedisPool.returnResource(jedis);
}
}
return available;
@ -90,6 +93,11 @@ public class RedisConnectionImpl implements RedisConnection {
public <T> Set<T> createSetFor(Class<T> clazz, String name) {
return new RedisSet<T>(name, jedisPool, Serializers.forClass(clazz));
}
@Override
public <K,V> Map<K, V> createMapFor(Class<K> keyClass, Class<V> valueClass, String name) {
return new RedisMap<K, V>(name, jedisPool, Serializers.forClass(keyClass), Serializers.forClass(valueClass));
}
@Override
public Set<Transaction> createTransactionSet(String name) {

View File

@ -87,7 +87,7 @@ public class RedisSet<T> extends RedisStorage<T> implements Set<T> {
return pooledWithResult(new Function<Jedis, Boolean>() {
@Override
public Boolean apply(Jedis jedis) {
return jedis.sadd(getName(), serialize(c)) == 1;
return jedis.sadd(getName(), serialize(c)) == c.size();
}
});
}
@ -97,7 +97,13 @@ public class RedisSet<T> extends RedisStorage<T> implements Set<T> {
return pooledWithResult(new Function<Jedis, Boolean>() {
@Override
public Boolean apply(Jedis jedis) {
return jedis.sinterstore(getName(), serialize(c)) == c.size();
byte[] tempName = temporaryName();
try {
jedis.sadd(tempName, serialize(c));
return jedis.scard(getName()) != jedis.sinterstore(getName(), getName(), tempName);
} finally {
jedis.del(tempName);
}
}
});
}
@ -107,7 +113,7 @@ public class RedisSet<T> extends RedisStorage<T> implements Set<T> {
return pooledWithResult(new Function<Jedis, Boolean>() {
@Override
public Boolean apply(Jedis jedis) {
return jedis.srem(getName(), serialize(c)) == 1;
return jedis.srem(getName(), serialize(c)) == c.size();
}
});
}

View File

@ -1,5 +1,6 @@
package org.ethereum.datasource.redis;
import com.google.common.primitives.Bytes;
import org.apache.commons.collections4.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,6 +35,14 @@ public abstract class RedisStorage<T> {
this.name = name.getBytes();
}
protected byte[] formatName(String suffix) {
return Bytes.concat(getName(), suffix.getBytes());
}
protected byte[] temporaryName() {
return formatName(":" + Thread.currentThread().getName() + ":" + System.currentTimeMillis());
}
protected byte[] serialize(T o) {
return serializer.serialize(o);
}
@ -89,10 +98,18 @@ public abstract class RedisStorage<T> {
protected <R> R pooledWithResult(Function<Jedis, R> function) {
Jedis jedis = pool.getResource();
Exception operationException = null;
try {
return function.apply(jedis);
} catch (Exception e) {
operationException = e;
throw e;
} finally {
pool.returnResource(jedis);
if (operationException == null) {
pool.returnResource(jedis);
} else {
pool.returnBrokenResource(jedis);
}
}
}
}

View File

@ -1,6 +1,7 @@
package test.ethereum.datasource;
import org.ethereum.datasource.redis.RedisConnection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
@ -8,17 +9,50 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import test.ethereum.TestContext;
import java.net.URI;
import java.net.URISyntaxException;
import static org.junit.Assert.assertFalse;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class AbstractRedisTest {
public abstract class AbstractRedisTest {
@Configuration
@ComponentScan(basePackages = "org.ethereum")
static class ContextConfiguration extends TestContext { }
@Autowired
protected RedisConnection redisConnection;
private RedisConnection redisConnection;
private Boolean connected;
protected RedisConnection getRedisConnection() {
return redisConnection;
}
protected Boolean isConnected() {
if (connected == null) {
String url = System.getenv(RedisConnection.REDISCLOUD_URL);
try {
Jedis jedis = new Jedis(new URI(url));
connected = jedis.ping().equals("PONG");
jedis.close();
} catch (Exception e) {
System.out.printf("Cannot connect to '%s' Redis cloud.\n", url);
}
assertFalse(connected ^ redisConnection.isAvailable());
}
return connected;
}
}

View File

@ -1,91 +0,0 @@
package test.ethereum.datasource;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.Set;
import static org.junit.Assert.*;
public class RedisConnectionTest extends AbstractRedisTest {
@Test
public void test() {
if (!redisConnection.isAvailable()) return;
Set<Pojo> pojos = redisConnection.createSetFor(Pojo.class, "pojos");
Pojo pojo = Pojo.create(1L, "test");
pojos.add(pojo);
assertTrue(pojos.contains(pojo));
assertEquals(1, pojos.size());
Pojo next = pojos.iterator().next();
assertNotNull(next);
assertEquals(pojo.getId(), next.getId());
assertEquals(pojo.getName(), next.getName());
assertTrue(pojos.remove(pojo));
assertTrue(pojos.isEmpty());
}
@Test
public void transactionStorageTest() {
if (!redisConnection.isAvailable()) return;
String namespace = "txnNamespace";
Set<Transaction> transactions = redisConnection.createTransactionSet(namespace);
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "cat"));
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "dog"));
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "rabbit"));
Set<Transaction> transactions1 = redisConnection.createTransactionSet(namespace);
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "duck"));
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "chicken"));
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "cow"));
assertEquals(6, transactions1.size());
transactions.clear();
assertTrue(transactions1.isEmpty());
}
private static class Pojo {
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Pojo create(long id, String name) {
Pojo result = new Pojo();
result.setId(id);
result.setName(name);
return result;
}
}
public static Transaction createTransaction(String gasPrice, String gas, String val, String secret) {
ECKey ecKey = ECKey.fromPrivate(HashUtil.sha3(secret.getBytes()));
// Tn (nonce); Tp(pgas); Tg(gaslimi); Tt(value); Tv(value); Ti(sender); Tw; Tr; Ts
return new Transaction(null, Hex.decode(gasPrice), Hex.decode(gas), ecKey.getAddress(),
new BigInteger(val).toByteArray(),
null);
}
}

View File

@ -1,10 +1,16 @@
package test.ethereum.datasource;
import org.ethereum.datasource.KeyValueDataSource;
import org.ethereum.datasource.redis.RedisConnection;
import org.ethereum.datasource.redis.RedisDataSource;
import org.junit.Assert;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
import redis.clients.jedis.Jedis;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Set;
/**
* @author Roman Mandeleil
@ -13,7 +19,7 @@ public class RedisDataSourceTest extends AbstractRedisTest {
@Test
public void testSet1() {
if (!redisConnection.isAvailable()) return;
if (!isConnected()) return;
KeyValueDataSource dataSource = createDataSource("test-state");
try {
@ -29,9 +35,23 @@ public class RedisDataSourceTest extends AbstractRedisTest {
}
}
@Test
public void test() {
try {
Jedis jedis = new Jedis(new URI(System.getenv(RedisConnection.REDISCLOUD_URL)));
Long count = jedis.sinterstore("f", "f", "s");
System.out.println(count);
Set<String> r = jedis.smembers("f");
System.out.println(r);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
@Test
public void testSet2() {
if (!redisConnection.isAvailable()) return;
if (!isConnected()) return;
KeyValueDataSource states = createDataSource("test-state");
KeyValueDataSource details = createDataSource("test-details");
@ -56,7 +76,7 @@ public class RedisDataSourceTest extends AbstractRedisTest {
}
private KeyValueDataSource createDataSource(String name) {
KeyValueDataSource result = redisConnection.createDataSource(name);
KeyValueDataSource result = getRedisConnection().createDataSource(name);
result.setName(name);
result.init();
return result;

View File

@ -0,0 +1,130 @@
package test.ethereum.datasource;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.junit.Assert.*;
public class RedisStorageTest extends AbstractRedisTest {
@Test
public void testRedisSet() {
if (!isConnected()) return;
Pojo elephant = Pojo.create(5L, "elephant");
Pojo lion = Pojo.create(5L, "lion");
Set<Pojo> ranch = getRedisConnection().createSetFor(Pojo.class, "ranch");
Pojo chicken = Pojo.create(1L, "chicken");
Pojo cow = Pojo.create(2L, "cow");
Pojo puppy = Pojo.create(3L, "puppy");
Pojo kitten = Pojo.create(4L, "kitten");
assertTrue(ranch.add(chicken));
assertFalse(ranch.add(chicken));
assertTrue(ranch.contains(chicken));
assertEquals(1, ranch.size());
Pojo next = ranch.iterator().next();
assertNotNull(next);
assertEquals(chicken, next);
assertTrue(ranch.addAll(asList(cow, puppy, kitten)));
assertEquals(4, ranch.size());
assertFalse(ranch.isEmpty());
assertFalse(ranch.remove(elephant));
assertFalse(ranch.removeAll(asList(cow, lion, elephant)));
assertEquals(3, ranch.size());
assertTrue(ranch.retainAll(asList(kitten, puppy)));
assertEquals(2, ranch.size());
ranch.clear();
assertEquals(0, ranch.size());
assertTrue(ranch.isEmpty());
}
@Test
public void testSeveralSetsWithOneName() {
if (!isConnected()) return;
final String name = "testTransactions";
Set<Transaction> transactions = getRedisConnection().createTransactionSet(name);
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "cat"));
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "dog"));
transactions.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "rabbit"));
Set<Transaction> transactions1 = getRedisConnection().createTransactionSet(name);
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "duck"));
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "chicken"));
transactions1.add(createTransaction("09184e72a000", "4255", "1000000000000000000000", "cow"));
assertEquals(6, transactions1.size());
transactions.clear();
assertTrue(transactions1.isEmpty());
}
private static class Pojo {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Pojo create(long id, String name) {
Pojo result = new Pojo();
result.setId(id);
result.setName(name);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !getClass().isInstance(obj)) return false;
if (this == obj) return true;
Pojo another = (Pojo) obj;
return (another.getId() == getId()) && another.getName().equals(getName());
}
@Override
public int hashCode() {
int hashCode = 17;
hashCode += ((getId() == null) ? 0 : getId().hashCode()) * 31;
hashCode += ((getName() == null) ? 0 : getName().hashCode()) * 31;
return hashCode;
}
}
public static Transaction createTransaction(String gasPrice, String gas, String val, String secret) {
ECKey ecKey = ECKey.fromPrivate(HashUtil.sha3(secret.getBytes()));
// Tn (nonce); Tp(pgas); Tg(gaslimi); Tt(value); Tv(value); Ti(sender); Tw; Tr; Ts
return new Transaction(null, Hex.decode(gasPrice), Hex.decode(gas), ecKey.getAddress(),
new BigInteger(val).toByteArray(),
null);
}
}