Pending Transaction - save in external datasource
This commit is contained in:
parent
8fd961ecaf
commit
4ef20af5e0
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue